16

私は過去 3 ~ 4 日間、これに頭を悩ませていましたが、(ARM または非公式の) DECENT の説明文書が見つかりません。私はODROID-XU ボード (big.LITTLE 2 x Cortex-A15 + 2 x Cortex-A7)ボードを持っており、ARM アーキテクチャについてもう少し理解しようとしています。私の「実験的」コードでは、WFI (割り込み待機) 状態から他のコアをウェイクアップしたい段階に到達しました。

私がまだ見つけようとしている不足している情報は次のとおりです。

1.メモリ マップド GIC のベース アドレスを取得するときに、CBAR を読み取る必要があることを理解しています。しかし、最終的な GIC ベース アドレスに到達するために CBAR のビット (2 つの PERIPHBASE 値) をどのように配置する必要があるかを説明しているドキュメントはありません。

2. GICD_SGIR レジスタを介して SGI を送信する場合、0 ~ 15 のどの割り込み ID を選択すればよいですか? それは問題ですか?

3. GICD_SGIR レジスタを介して SGI を送信する場合、どこから実行を開始するかを他のコアに伝えるにはどうすればよいですか?

4.コードが U-BOOT ブートローダーによってロードされるという事実は、この状況にどのように影響しますか?

Cortex-A Series Programmer's Guide v3.0 (ここにあります:リンク)セクション 22.5.2 (Linux での SMP ブート、271ページ)に次のように記載されています。

プライマリ コアが起動している間、セカンダリ コアは WFI 命令を使用してスタンバイ状態に保持されます。それ (プライマリ コア) は、セカンダリ コアにスタートアップ アドレスを提供し、プロセッサ間割り込み (IPI) を使用してそれらをウェイクアップします。

Linuxはどのようにそれを行いますか? ドキュメント- Sは、「セカンダリコアにスタートアップアドレスを提供します」に関する他の詳細を提供していません。

私の欲求不満は高まっており、答えにとても感謝しています. 事前にどうもありがとうございました!

追加の詳細

私が使用するドキュメント:

  • ARMv7-A&R アーキテクチャ リファレンス マニュアル
  • Cortex-A15 TRM (テクニカル リファレンス マニュアル)
  • Cortex-A15 MPCore TRM
  • Cortex-A シリーズ プログラマーズ ガイド v3.0
  • GICv2 アーキテクチャ仕様

私が今までにやったこと:

  • UBOOT は 0x40008000 にロードします。変換テーブル (TTB) をセットアップし、それに応じて TTBR0 と TTBCR を書き込み、0x40008000 を 0x8000_0000 (2GB) にマップしたので、MMU も有効にしました
  • 独自の例外ハンドラをセットアップする
  • シリアル経由で Printf 機能を利用できます (ODROID-XU の UART2)

上記のすべてが適切に機能しているようです。

私が今やろうとしていること:

  • GIC ベース アドレスを取得 => CBAR を読み取り、その値を 0xFFFF8000 と論理積 (&) し、これを GIC ベース アドレスとして使用しますが、これが正しくないことはほぼ確実です。
  • GICD_CTLR に値 0x1 を書き込むことにより、GIC ディストリビュータを有効にします (GIC ベース アドレスからのオフセット 0x1000?)。
  • 次のパラメーターを使用して SGI を構築します: Group = 0、ID = 0、TargetListFilter = "All CPUs Except Me" および GICD_SGIR GIC レジスターを介して送信 (書き込み)
  • 他のコアには実行開始アドレスを渡していないので、このままでは何も起こりません。

....アップデート....

答えを求めて、Linux カーネルと QEMU のソース コードを調べ始めました。これが私が見つけたものです(間違っている場合は修正してください):

  • ボードの電源を入れると、すべてのコアがリセットベクターから実行を開始します
  • ソフトウェア (ファームウェア)コンポーネントは、セカンダリ コアで WFI を実行し、これらのセカンダリ コアとプライマリ コアの間のプロトコルとして機能するその他のコードを実行します。
  • たとえば、EnergyCore ECX-1000 (Highbank)ボードで使用されるプロトコルは次のとおりです。

**(1)** the secondary cores enter WFI and when

**(2)** the primary core sends an SGI to wake them up

**(3)** they check if the value at address (0x40 + 0x10 * coreid) is non-null;

**(4)** if it is non-null, they use it as an address to jump to (execute a BX)

**(5)** otherwise, they re-enter standby state, by re-executing WFI

**(6)** So, if I had an EnergyCore ECX-1000 board, I should write (0x40 + 0x10 * coreid) with the address I want each of the cores to jump to and send an SGI

質問:

  • 1. これを行うソフトウェア コンポーネントは何ですか? SD カードに書き込んだ BL1 バイナリですか、それとも U-BOOT ですか?
  • 2. 私の理解では、このソフトウェア プロトコルはボードごとに異なります。そうですか、それとも基礎となるプロセッサにのみ依存していますか?
  • 3. ピックワン ARM ボードのこのプロトコルに関する情報はどこにありますか? - ARM の公式 Web サイトまたはボードの Web ページで見つけることができますか?
4

4 に答える 4

9

わかりました、私は赤ちゃんに戻ってきました。結論は次のとおりです。

  • CPU をスリープ状態にするソフトウェア コンポーネントはブートローダー (私の場合は U-Boot) です。
  • Linux は、ブートローダーがこれを行う方法 (各ボードの Linux カーネルにハードコードされている) を何らかの形で知っており、それらを再び起動する方法を知っています。

私の ODROID-XU ボードの場合、このプロセスを説明するソースはUBOOT ODROID-v2012.07であり、Linux カーネルは次の場所にあります: LINUX ODROIDXU-3.4.y (ブランチodroid-3.12からカーネル バージョンを調べた方がよかったでしょう) 。 y前者は 8 つのプロセッサーのすべてを開始するわけではなく、そのうちの 4 つだけですが、後者は開始するためです)。

とにかく、これが私が思いついたソースコードです。後でこのコードを書くのに役立った上記のソースコードツリーから関連するソースファイルを投稿します。

typedef unsigned int DWORD;
typedef unsigned char BOOLEAN;
#define FAILURE (0)
#define SUCCESS (1)
#define NR_EXTRA_CPUS (3) // actually 7, but this kernel version can't wake them up all -> check kernel version 3.12 if you need this

// Hardcoded in the kernel and in U-Boot; here I've put the physical addresses for ease
// In my code (and in the linux kernel) these addresses are actually virtual
// (thus the 'VA' part in S5P_VA_...); note: mapped with memory type DEVICE
#define S5P_VA_CHIPID (0x10000000)
#define S5P_VA_SYSRAM_NS (0x02073000)
#define S5P_VA_PMU (0x10040000)
#define EXYNOS_SWRESET ((DWORD) S5P_VA_PMU + 0x0400)
// Other hardcoded values
#define EXYNOS5410_REV_1_0 (0x10)
#define EXYNOS_CORE_LOCAL_PWR_EN (0x3)

BOOLEAN BootAllSecondaryCPUs(void* CPUExecutionAddress){

// 1. Get bootBase (the address where we need to write the address where the woken CPUs will jump to)
//    and powerBase (we also need to power up the cpus before waking them up (?))
DWORD bootBase, powerBase, powerOffset, clusterID;

asm volatile ("mrc p15, 0, %0, c0, c0, 5" : "=r" (clusterID));
clusterID = (clusterID >> 8);
powerOffset = 0;
if( (*(DWORD*)S5P_VA_CHIPID & 0xFF) < EXYNOS5410_REV_1_0 )
{
    if( (clusterID & 0x1) == 0 ) powerOffset = 4;
}
else if( (clusterID & 0x1) != 0 ) powerOffset = 4;

bootBase = S5P_VA_SYSRAM_NS + 0x1C;
powerBase = (S5P_VA_PMU + 0x2000) + (powerOffset * 0x80);

// 2. Power up each CPU, write bootBase and send a SEV (they are in WFE [wait-for-event] standby state)
for (i = 1; i <= NR_EXTRA_CPUS; i++)
{
    // 2.1 Power up this CPU
    powerBase += 0x80;
    DWORD powerStatus = *(DWORD*)( (DWORD) powerBase + 0x4);

    if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == 0)
    {
        *(DWORD*) powerBase = EXYNOS_CORE_LOCAL_PWR_EN;
        for (i = 0; i < 10; i++) // 10 millis timeout
        {
            powerStatus = *(DWORD*)((DWORD) powerBase + 0x4);
            if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) == EXYNOS_CORE_LOCAL_PWR_EN)
                break;
            DelayMilliseconds(1); // not implemented here, if you need this, post a comment request 
        }
        if ((powerStatus & EXYNOS_CORE_LOCAL_PWR_EN) != EXYNOS_CORE_LOCAL_PWR_EN)
            return FAILURE;
    }
    if ( (clusterID & 0x0F) != 0 )
    {
        if ( *(DWORD*)(S5P_VA_PMU + 0x0908) == 0 )
        do { DelayMicroseconds(10); } // not implemented here, if you need this, post a comment request
        while (*(DWORD*)(S5P_VA_PMU + 0x0908) == 0);
        *(DWORD*) EXYNOS_SWRESET = (DWORD)(((1 << 20) | (1 << 8)) << i);
    }

    // 2.2 Write bootBase and execute a SEV to finally wake up the CPUs
    asm volatile ("dmb" : : : "memory");
    *(DWORD*) bootBase = (DWORD) CPUExecutionAddress;
    asm volatile ("isb");
    asm volatile ("\n   dsb\n   sev\n   nop\n");
}
return SUCCESS;
}

これにより、セカンダリ CPU の 7 つのうち 3 つが正常に起動されます

次に、u-boot と Linux カーネルに関連するソース ファイルの短いリストを示します。

  • UBOOT: lowlevel_init.S -363-369に注意してください。セカンダリ CPU が WFE で_hotplug_addrの値が非ゼロになり、そこにジャンプするのを待機する方法。上記のコードでは、_hotplug_addr は実際には bootBase です。また、行282 ~ 285 は、_hotplug_addr がCONFIG_PHY_IRAM_NS_BASE + _hotplug_addr - nscode_baseに再配置されることを示しています(_hotplug_addr - nscode_base は0x1Cであり、CONFIG_PHY_IRAM_NS_BASE は 0x02073000であるため、Linux カーネルで上記のハードコーディングが行われます) 。

  • LINUX KERNEL: ジェネリック - smp.c (関数__cpu_upを参照)、プラットフォーム固有 (odroid-xu): platsmp.c (関数boot_secondary、ジェネリック __cpu_up によって呼び出される; platform_smp_prepare_cpus [一番下] も参照 => これは、実際にブートベースとパワーベースの値を設定します)

于 2013-11-25T10:38:51.273 に答える
2

明確さと将来の参考のために、Exynos ブート プロトコルの適切なドキュメントがないため、ここでは微妙な情報が欠落しています (この質問は、実際には「Cortex-A15」ではなく「Exynos 5」とマークする必要があります。これは SoC です。具体的なことであり、ARM の発言は一般的な推奨事項にすぎません)。コールド ブートから、セカンダリ コアは WFI になく、まだ電源がオフになっています

XU でハイパーバイザーを実行するためのブート shim を作成する過程で考案した、より単純な最小限のソリューション (Linux のホットプラグの機能に基づく) は、次の 2 つの手順を実行します。

  1. まずエントリポイントアドレスをExynos保持ペンに書き込みます(0x02073000 + 0x1c)
  2. 次に、電源コントローラーを突き刺して、関連するコアをオンにします。こうすることで、セキュア ブート パスからホールディング ペンにドロップアウトして、待機しているエントリ ポイントを見つけ、WFI ループをスキップして、触れる必要さえなくします。 GICはまったく。

完全な CPU ホットプラグの実装を計画している場合を除き、クラスタ ID のチェックをスキップできます。起動している場合は、クラスタ 0 にあり、それ以外の場所にはありません (逆方向のクラスタ レジスタを備えた生産前のチップのチェックは不要です)。 Odroidでも-確かに私にとってはそうでした)。

私の調査によると、A7 の起動はもう少し複雑です。Exynos big.LITTLE switcher driverから判断すると、最初にクラスター 1 を有効にするには、別の電源コントローラー レジスターのセットを突く必要があるようです (特に MMU とキャッシュをオンにするために、CCI もいじる必要があるかもしれません)。 - その時点で「実際の仕事をすること」よりも「楽しむこと」の方が多かったので、それ以上は進みませんでした...

余談ですが、Samsung の 5410 の CPU ホットプラグ用メインライン パッチにより、コアの電源制御がダウンストリーム コードの IMO の混乱よりも明確になります。

于 2014-01-03T15:32:52.457 に答える
0

www.arm.comにアクセスして、DS-5 開発スイートの評価版をダウンロードしてください。インストールが完了すると、例の下にstartup_Cortex-A15MPCore directory. を見てくださいstartup.s

于 2013-11-21T21:48:59.863 に答える