Vengineerの戯言

人生は短いけど、長いです。人生を楽しみましょう!

Apple M1 Mac mini の PCIe 関連を調べてみた

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

昨日のブログでは、Apple M1 Mac mini 内の アドレスマップを調べてみました。その中で、PCIe 関連が気になったので、今日は PCIe について調べていきます。

Apple M1 Mac mini の PCIe 関連

dts ファイル では、次の部分が PCIe 部分になります。
デバイスドライバは、apple,pcie-m1 で、ここ にあります。

        pcie: pcie@690000000 {
            compatible = "apple,pcie-m1";
            reg = <0x6 0x90000000 0x0 0x1000000                                                                 /* config */
                   0x6 0x80000000 0x0 0x100000  0x6 0x8c000000 0x0 0x100000                                     /* core and AXI bridge */
                   0x6 0x81000000 0x0 0x20000   0x6 0x82000000 0x0 0x20000   0x6 0x83000000 0x0 0x20000>;       /* ports */
            interrupt-parent = <&aic>;
            interrupts = <0 695 4   0 698 4   0 701 4                                                           /* state */
                          0 704 1   0 705 1   0 706 1   0 707 1   0 708 1   0 709 1   0 710 1   0 711 1         /* MSI */
                          0 712 1   0 713 1   0 714 1   0 715 1   0 716 1   0 717 1   0 718 1   0 719 1
                          0 720 1   0 721 1   0 722 1   0 723 1   0 724 1   0 725 1   0 726 1   0 727 1
                          0 728 1   0 729 1   0 730 1   0 731 1   0 732 1   0 733 1   0 734 1   0 735 1>;
            clocks = <&pcie_gp_clk &pcie_clk &pcie_refclk>;
            clock-names = "core", "aux", "ref";
            pinctrl-0 = <&pcie_clkreq_pins>;
            pinctrl-names = "default";
            perst-gpios = <&gpio 152 0   &gpio 153 0   &gpio 33 0>;
            clkreq-gpios = <&gpio 150 0   &gpio 151 0   &gpio 32 0>;
            devpwr-gpios = <&smc 13 0>;
            devpwr-on-0 = <0 1>;
            devpwr-on-1 = <>;
            devpwr-on-2 = <>;
            #address-cells = <3>;
            #size-cells = <2>;
            #interrupt-cells = <1>;
            device_type = "pci";
            msi-controller;
            msi-parent = <&pcie>;
            msi-doorbell = <0x0 0xfffff000>;
            ranges = <0x43000000   0x6 0xa0000000   0x6 0xa0000000 0x0 0x20000000
                      0x02000000   0x0 0xc0000000   0x6 0xc0000000 0x0 0x40000000>;
            bus-range = <0x00 0x0f>;
            iommu-map = <0x0000 &pcie_dart0 0x8000 0x0100>, /* fake, and should never be used as RC bridges don't DMA */
                        <0x0100 &pcie_dart0 0x0000 0x0100>,
                        <0x0200 &pcie_dart1 0x0000 0x0100>,
                        <0x0300 &pcie_dart2 0x0000 0x0100>;
            refclk-always-on-2;
            max-speed-2 = <1>; /* 2.5 GT/s */
        };

アドレス空間は、この部分で、全部で6個です。最初の 0x6_9000_0000 からの 16MB は PCI Configuration 空間です。2番目の 0x6_8000_0000 からの 1MB と 0x6_8c00_0000 からの1MBが PCIe RootComplexのコアと AXI bridge 回路用です(もしこれが本当なら 内部バスは、AXI なんですね)。最後の3つはポートで、0x6_8100_0000, 0x6_8200_0000, 0x6_8300_0000 でそれぞれ128KBの空間です。

            reg = <0x6 0x90000000 0x0 0x1000000                                                                 /* config */
                   0x6 0x80000000 0x0 0x100000  0x6 0x8c000000 0x0 0x100000                                     /* core and AXI bridge */
                   0x6 0x81000000 0x0 0x20000   0x6 0x82000000 0x0 0x20000   0x6 0x83000000 0x0 0x20000>;       /* ports */

次の2つは、割り込み関連です。interrupt-parent は、PCIe から出る割り込み線がどこに繋がっているかを示しています。aic に繋がっています。
割り込み線は、全部で35本出ています。最初の3個(0 695 4 0 698 4 0 701 4)は、この回路の割り込み用です。残りの32個は、MSI用の割り込み線です。

            interrupt-parent = <&aic>;
            interrupts = <0 695 4   0 698 4   0 701 4                                                           /* state */
                          0 704 1   0 705 1   0 706 1   0 707 1   0 708 1   0 709 1   0 710 1   0 711 1         /* MSI */
                          0 712 1   0 713 1   0 714 1   0 715 1   0 716 1   0 717 1   0 718 1   0 719 1
                          0 720 1   0 721 1   0 722 1   0 723 1   0 724 1   0 725 1   0 726 1   0 727 1
                          0 728 1   0 729 1   0 730 1   0 731 1   0 732 1   0 733 1   0 734 1   0 735 1>;

aic は、apple,aicアドレス空間 (0x2_3b10_0000 から32KB)で、fast-ipi オプションが設定されています。

        aic: interrupt-controller@23b100000 {
            compatible = "apple,aic";
            #interrupt-cells = <3>;
            interrupt-controller;
            reg = <0x2 0x3b100000 0x0 0x8000>;
            fast-ipi;
        };

MSIは、下記の部分です。msi-doorbel は、ドアベル用の64ビットアドレスでここでは、0x_ffff_f000 を設定しています。

            msi-controller;
            msi-parent = <&pcie>;
            msi-doorbell = <0x0 0xfffff000>;

下記の ranges の部分は、アドレス変換です。

  • 1番目のエントリは、non-prefetchable 32-bit アドレス(0x4300_0000)、64-bitアドレス(0x6_0xa000_0000)は、0x6_a000_0000 から512MBに
  • 2番目のエントリは、non-prefetchable 32-bit アドレス(0x0200_0000)、64-bitアドレス(0x0_0xc000_0000)は、0x6_c000_0000 から1GBに

マッピングされます。

            ranges = <0x43000000   0x6 0xa0000000   0x6 0xa0000000 0x0 0x20000000
                      0x02000000   0x0 0xc0000000   0x6 0xc0000000 0x0 0x40000000>;

次は、クロック部分です。3つのクロックが接続していて、core, aux, ref という名前が付いています。

            clocks = <&pcie_gp_clk &pcie_clk &pcie_refclk>;
            clock-names = "core", "aux", "ref";

3つのクロック、pcie_gp_clk, pcie_clk, pcie_refclk は、次のような接続になっています。refclk100mhz (100MHzのクロックから)、pcie_refclk と imx_clk に接続しています。この pcie_reflk と imx_clk は、apple,pmgr-clk-gate が示すように、Gated Clock です。ここでクロックを止めることができます。pcie_refclk は、pcie に接続されています。imx_clk は、pcie_clk に繋がっています。ここでも apple,pmgr-clk-gate なので Gated Clock になります。この pcie_clk も pcie に接続しています。そして、pcie_gp_clk を経由して、pcie と pcie_dart0, 1, 2 に接続しています。pcie_gp_clk も Gated Clockになります。Gated Clockにすることで使っていない場合は、この部分を Off にすればいいだけです。Apple M1 Mac mini は PCIe を使っていますが、Apple M1 Macbook AirApple M1 Macbook Pro では PCIe を使っていないのでこの大本となる pcie_refclk と pcie_clk を Off にしていると思います。

  • refclk100mhz
    • pcie_refclk (apple,pmgr-clk-gate)
      • pcie
    • imx_clk (apple,pmgr-clk-gate)
      • pcie_clk (apple,pmgr-clk-gate)
        • pcie
        • pcie_gp_clk (apple,pmgr-clk-gate)
          • pcie
          • pcie_dart0
          • pcie_dart1
          • pcie_dart2

次の perst-gpios は、PCIe の PERST の制御に使うものだと思います。3つありますので、ポートはやっぱり、3ポートなんでしょうね。

            perst-gpios = <&gpio 152 0   &gpio 153 0   &gpio 33 0>;

次の clkreq-gpios は、クロックに関係する何かなんではないでしょうか?

            clkreq-gpios = <&gpio 150 0   &gpio 151 0   &gpio 32 0>;

gpio の 150, 151, 32 は、gpio:pinctrl にも、pcie_clkreq_pins: pcie_clkreq_pins とあります。

        gpio: pinctrl@23c100000 {
            compatible = "apple,gpio-v0";
            reg = <0x2 0x3c100000 0x0 0x100000>;
            pin-count = <212>;

            interrupts = <0 190 4   0 191 4   0 192 4   0 193 4   0 194 4   0 195 4   0 196 4>;

            clocks = <&gpio_clk>;

            gpio-controller;
            #gpio-cells = <2>;

            interrupt-controller;
            #interrupt-cells = <2>;

            i2c0_pins: i2c0_pins {
                pins = "gpio192", "gpio188";
                function = "periph";
            };

            pcie_clkreq_pins: pcie_clkreq_pins {
                pins = "gpio150", "gpio151", "gpio32";
                function = "periph";
            };
        };

次の devpwr-gpios と devpwr-on-X は電源関連だと思います。

            devpwr-gpios = <&smc 13 0>;
            devpwr-on-0 = <0 1>;
            devpwr-on-1 = <>;
            devpwr-on-2 = <>;

下記のコードにあるように、ポート番号(0, 1, 2) の内、0のみ、<0 1> を設定しています。ポート番号0のみ電源をONにするのでしょうか?

		sprintf(name, "devpwr-on-%d", i);
		ret = of_property_count_elems_of_size(node, name, 8);
		pcie->devpwron[i].num = ret;
		if(ret <= 0)
			continue;
		pcie->devpwron[i].seq = devm_kzalloc(dev, ret * 8, GFP_KERNEL);
		if(!pcie->devpwron[i].seq)
			return -ENOMEM;
		ret = of_property_read_variable_u32_array(node, name, pcie->devpwron[i].seq, ret * 2, ret * 2);
		if(ret < 0)
			return ret;

bus-range は、PCIe の バス番号は 0x00から0x0f、iommu-map は、pcie_dart0,1,2 に繋がっています。

            bus-range = <0x00 0x0f>;
            iommu-map = <0x0000 &pcie_dart0 0x8000 0x0100>, /* fake, and should never be used as RC bridges don't DMA */
                        <0x0100 &pcie_dart0 0x0000 0x0100>,
                        <0x0200 &pcie_dart1 0x0000 0x0100>,
                        <0x0300 &pcie_dart2 0x0000 0x0100>;
            refclk-always-on-2;
            max-speed-2 = <1>; /* 2.5 GT/s */

refclk-always-on-2 と max-speed-2 は、PCIe のデバイスドライバ のこの部分でパースしています。refclk-always-on-2 は、2の部分をパースしています。

		sprintf(name, "refclk-always-on-%d", i);
		pcie->refclk_always_on[i] = of_property_read_bool(node, name);

パースした値がポート番号なので、2番目のポートの REFCLK を設定しています。

	if(!pcie->refclk_always_on[idx])
		rmwl(PORT_REFCLK_CGDIS, 0, pcie->base_port[idx] + PORT_REFCLK);
	rmwl(PORT_APPCLK_CGDIS, 0, pcie->base_port[idx] + PORT_APPCLK);

また、max-speed-2 も 2 の部分をパースしています。この部分はポート番号はポート2に 1 (2.5GT/s、Gen1)を設定しています。設定していない場合はデフォルト値の 2 (5.0GT/s、Gen2)のようです。

		sprintf(name, "max-speed-%d", i);
		if(of_property_read_u32(node, name, &pcie->max_speed[i]) < 0)
			pcie->max_speed[i] = 2;

結果的に、ポート0には電源を供給し、ポート0はGen2、ポート1はGen2、ポート2はGen1に設定しているようです。ポート2をGen1にしているのは接続しているデバイスがGen1対応のみだからでしょうか?

終わりに

Apple M1 Mac mini の dts ファイルから PCIe 関連を調べてみました。
dts ファイルについてもっと知りたいのなら、下記の平田豊さんの「私はどのようにしてLinuxカーネルを学んだか Device Tree編」がいいと思います。Kindle版なら550円とお得です。
www.amazon.co.jp