SW:AGXドライバノート - asfdrwe/asahi-linux-translations GitHub Wiki

2023/3/8時点のAGX driver notesの翻訳


一般的な文書

Gitリンク

UAPIデザイン

UAPIのデザインは、今後公開されるIntel Xe UAPIに触発されています。

  • ファイル:DRMデバイスへのオープンファイル記述子
  • VM: GPUのアドレス空間
  • バインド: GEMオブジェクトからVMへのマッピング
  • キュー: GPU上の論理的な実行キュー(複数のファームウェアキューが背後に存在)

GEM オブジェクトと VM

GEMオブジェクトは、あるVMに対してプライベートで作成することができます。つまり、そのオブジェクトは決してエクスポートすることができず、そのVMにのみ バインドすることが可能です。これにより、ドライバはオブジェクトのロックを最適化することができます(まだ実装されていませんが、shrinkerを 導入すれば必要になるでしょう。)

  • TODO:プライベートオブジェクトを共有オブジェクトに変換するioctlを提供することは意味ある?ドライバはいくつかのGLフローでこれを必要だが、 今は理想的ではない再割り当てとコピーに従事...

VMバインドioctlはGEMオブジェクトの任意の範囲をVMにバインド/アンバインドすることに対応しますが、ドライバはまだこれを実装していません (オブジェクト全体をVMの空き領域にバインドする必要があり、オブジェクトはVMA範囲ではなく、オブジェクト名によってのみ完全にアンバインド することができます)。これは将来的に変更されるでしょう。UAPIはすでに準備済みです。

キュー

読者のみなさまご注意ください。これはトリッキーです!!

UAPI は任意の数のユーザー・キューを公開します。各ユーザー・キューは親VMを持ちます。ユーザー・キューは実行のためのジョブを受け取り、 (特定のファームウェアの制限を超えないことを保証するため)各ジョブは最大64個のGPUコマンドで構成することが可能です。ジョブは drm_sched スケジューラによってスケジューリングされ、明示的な同期を行うために、任意のイン/アウト同期オブジェクトリストに対応します。ジョブは すべての同期オブジェクトがシグナルされるまで実行を開始せず、ジョブ内のすべてのコマンドが完全に完了した時点ですべての同期オブジェクトがシグナルされます。 ジョブはすべての同期オブジェクトがシグナルされるとすぐにファームウェアに送信されます。ジョブ間に直接的な依存関係がない場合、ジョブの境界は CPUラウンドトリップを意味しません(ただし、カーネルがリソースをクリーンアップする必要があるため、GPUは各ジョブの完了を非同期にシグナルします)。 けれども、ジョブは常に順番に投入されます。あるジョブの同期オブジェクトが満たされていないと、ファームウェアへの後続のすべてのジョブの投入が ブロックされます(これは drm_sched によって暗示され、また現在のドライバー設計のハード要件でもあります)。真の意味での任意の同時実行/並べ替えには、 複数のキューを使用する必要があります。

ジョブ内でコマンドはCPUが関与することなくファームウェアによる実行のために直接キューに入れられます。各ユーザーキューは、 2つの論理的なキューから構成されています。レンダーとコンピュートです。レンダーコマンドとコンピュートコマンドは順番に送信されますが、 1つのユーザーキュー内で並列に実行できます。

論理的なレンダーキューは、2つのファームウェアキュー(頂点とフラグメント)が背後に存在します。カーネルはそれらの間にバリアを設けて、 ファームウェアコマンドを常にペアにして提出します。このバリアは、部分的なレンダリングとプリエンプションが部分的な頂点とフラグメントの実行を インターリーブできるという点で、透過性を備えています(ここに手垢のついたファームウェアのマジックを挿入✨)。個々の描画コマンド間の 頂点/フラグメントバリアは、必要であれば(GPU コマンドストリーム内の)VDM レベルで処理され、特定のキャッシュ関連バリアと同様に処理されます。

ユーザーキュー内では、オプションでコマンドの依存関係を追加することができます。これらは、各論理キュー(コンピュート、レンダー)ごとに、 コマンドごとに待機するコマンドの境界インデックス(Pythonスライスインデックスを考えてください)として指定されます。インデックスは0 (このサブミッションからのコマンドの前のポイント)から始まり、与えられたタイプのサブミッションのコマンドごとに1つずつ増えていきます。 将来的にコマンドの境界で待機することは許されません。与えられた論理キューのインデックス0(ジョブの始まり)で待つことは、そのタイプの 先行ジョブのすべてのコマンド部分の完了を待つことを意味し、つまり、それは先行提出の終了境界で待つことと同等です。論理キューは 常に順番にコマンドを開始・完了するので、与えられたインデックスで待つことは、その論理キューに対するすべての先行コマンドを待つことを意味します。

明示的なバリアがない限り、レンダーコマンドの頂点部分は、先行コマンドのフラグメント部分と同時に実行することができます。つまり、頂点処理が フラグメント処理より『先に実行』されてしまうことがあります。これを防ぐには、以前のコマンドのインデックスのコマンドバリアを指定します。 この場合、直列化されたファームウェアキューが1つしかないためコンピュートでは無意味ですが、レンダーコマンドでは意味があり、将来 より多くの並列化が可能になった場合に備えて、コンピュートコマンドで推奨されています。

次のサブミッションを考えてみましょう。

#0  R1 barrier=[__, C0]
#1  C1 barrier=[__, __]
#2  C2 barrier=[__, __]
#3  R2 barrier=[R1, C2]
#4  R3 barrier=[__, __] # [R1, C2] implied
#5  R4 barrier=[R3, __] # [R3, C2] implied

これは、次のような依存関係グラフを記述しています。

[C*]->[R1]----+----\
   [C1]------+|---vv
   [C2]-----+||->[R3]-v
            vvv      [R4]
            [R2]------^

なお、キューの直列化により、実際にはC1<-C2の順序が存在しますが(訳注:コンピュートキューが1本で直列化されているからC1の次にC2の順のはず)、 これはバリアで明示的に指定されたものではありません。しかし、R2とR3は、C2と、C1を含むすべての先行するコンピュートコマンドの後に 順序付けられ、さらにR1の後に順序付けられます。また、R2/R3は同時に実行することができますが(つまり、R3の頂点処理はR2の フラグメント処理と重ねたり先行することが可能)、R4はR3が完全に完了するまで頂点処理を開始しません(これは、キューの順序により、 R2も完了したことを意味します)。

[C*]は、(前のジョブから)最後に投入されたコンピュートコマンドです。0バリアを指定することは、まだコマンドが投入されていない場合でも 有効であることに注意してください(先行するすべてのコマンドがドライバで完全に完了し、関連する順序付けプリミティブが 解放された場合と同等であるため、無視されます)。この場合、R1 は、前のサブミッションのフラグメント処理が完了する前に頂点処理を 開始することができますが(レンダーバリアなし)、前のコンピュートコマンドの完了を待つことになります(C0 バリア)。

同じジョブのファームウェアキュービューは以下の通りです。

コンピュート   頂点    フラグメント
RUN C1     WAIT C0   WAIT R1v
RUN C2     RUN  R1v  RUN R1f
           WAIT R1f  WAIT R2v
           WAIT C2   RUN R2f
           RUN  R2v  WAIT R3v
           RUN  R3v  RUN R3f
           WAIT R3f  WAIT R4v
           RUN  R4v  RUN R4f

言い換えれば、すべてのレンダーコマンドに対して、明示的なバリアのよる待機は頂点部分の前に頂点キューに行き、それから フラグメントキューは常にフラグメント部分の前に暗黙のバリアを取得します。

Blitコマンドのサポートは、将来のドライバで追加される予定です。これらは事実上フラグメントのみのレンダーコマンドであるため、 明示的なバリアと実行の両方がフラグメントキュー内に存在することになります。

現在、Galliumドライバはシングルコマンドのサブミッションを発行するだけで、常に[C0, R0] バリアを使用してすべてを完全に 直列化しています。これは将来的に改善されるかもしれませんが、このすべての主なユースケースはVulkanキューです。

結果

ユーザ空間は、オプションでコマンド実行のフィードバックを要求することができます。これは、結果バッファBOを指定し、 そのバッファにコマンドごとにオフセットとサイズを指定することによって行われます。カーネルは、コマンドが完了すると、 そこにコマンドの結果情報を書き込みます。現在、これには基本的なタイミング情報、タイル状頂点バッファ統計と 部分的なレンダリングカウント(レンダーコマンド用)、詳細なフォルト情報(フォルトコマンド用、mesa の BO ルックアップ論理と組み合わせると非常に便利!)などがあります。

FAQ

  • 構造体 drm_asahi_cmd_render がゴチャゴチャなのはどうなっているのでしょうか? ファームウェアのコマンドはこれらの低レベルのレンダリングの詳細をすべて直接指定しそれらの構造体を ユーザー空間に直接公開することはできないので、そうするのは安全ではありません。他に方法はありませんし、 macOS も同じことをしています...(少なくとも PowerVR よりはゴチャゴチャではないです!)。

TODO

上流へのブロッカー(現在のドラフトUAPIに影響を与えます):

  • アタッチメントフラグを把握し、これが何をするしどのように機能するのかを正確に確認
  • compute preemption kernel のものと、それが使用するコマンドフィールドを整理 (あるいは、取り下げて将来の rev/feature flag のために残す)
  • 未知のバッファが1つか2つあり、UAPI に入るべきか把握 (02345 の件...)
  • mmu.rs (memremap 関連)に不足している抽象化を追加

その他

  • DRM_IOCTL_ASAHI_GET_TIMEを実装
  • RTKit runtime-PMを実装(GPUがアイドル状態の時にASCをスリープさせる、macOSはノートPCでこれを実現)
  • Blit コマンドのリバースエンジニアリングと実装
  • ファームウェア KTrace を Linux のトレース用にフックアップ
  • VM管理(任意のサブレンジマップ/アンマップ)を完了
  • [ ]パフォーマンスカウンター
  • M2 Pro/Max対応