割り込みを処理し、WFI risc-v cpu命令を使用するための意図された/正しい方法は何ですか?
私はベアメタルプログラミングに非常に慣れておらず、これまで割り込みを使ったことがありませんでしたが、RISC-V FE310-G002SOC搭載の開発ボードで学習してきました。
RISC-V WFI(Wait for Interrupt)の説明を読んでいますが、マニュアルからは、実際にコアをスリープさせるために信頼できるとは思えません。代わりに、システムに対して実行を停止できること、および命令をNOPのように扱う必要があることを示唆しているだけです。しかし、これは私にはかなり役に立たないようです。次のASMプログラムスニペットについて考えてみます。
wfi_loop:
WFI
J wfi_loop
WFIは信頼できないため、これを行う必要があります。ただし、割り込みハンドラからのMRETの場合でも、ループに巻き込まれます。したがって、割り込みハンドラで値が更新されるグローバル変数に対して条件付きにする必要があります。これは非常に厄介なようです。
また、実装が実際にWFI命令を尊重し、割り込みがWFI命令の実行の直前にトリガーされた場合、コア全体は、WFI命令の前に戻るため、他の割り込みがトリガーされるまでストールします。
実行する作業がない場合、命令の正しい使用法はカーネルスケジューラ内でのみであるように思われます。しかし、それでも、割り込みハンドラーからそのようなコードに戻るのではなく、スケジューラーアルゴリズムを最初からやり直したいとは思わないでしょう。しかし、どういうわけかスタックをロールバックする必要があるなどの理由で、それも問題になります。
私はこれを頭の中でぐるぐる回っていますが、安全な使い方がわからないようです。たぶん、アトミックに、CSRRSで割り込みを有効にしてから、すぐに次のようにWFIを呼び出します。
CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP
次に、割り込みハンドラからMRETを呼び出す前に、mepcレジスタを8バイトインクリメントしてください。割り込みは、戻る前に、割り込みハンドラ内のmieレジスタで再度無効にする必要があります。このソリューションは、圧縮命令が使用されているかどうかに関係なく、WFI、J、およびNOPがすべて4バイト命令としてエンコードされている場合にのみ安全です。また、CSRRSI命令によって有効にされた後、割り込みがトリガーされる前にWFI命令に到達するプログラムカウンタにも依存します。これにより、コード内の安全な場所で割り込みをトリガーし、それを待っていたループから抜け出すような方法で割り込みを返すことができます。
ハードウェアに期待できる動作を理解しようとしているだけだと思います。したがって、割り込みを正しく呼び出して戻り、WFI命令を使用する方法を教えてください。
回答
アイドリング用のタスク/スレッド/プロセスが1つあり、それはコードの最初のビットのように見えるはずです。
アイドルスレッドは最低の優先度を持つように設定されているため、アイドルスレッドが実行されている場合は、実行する他のスレッドがないか、他のすべてのスレッドがブロックされていることを意味します。
他のスレッドのブロックを解除する割り込みが発生した場合、割り込みサービスルーチンは、中断されたアイドルスレッドではなく、ブロックされたスレッドを再開する必要があります。
IOでブロックするスレッド自体も中断されることに注意してください—それ自体がecall
。を使用することで中断されます。その例外はIOの要求であり、このスレッドをブロックします—IO要求が満たされるまで再開できません。
したがって、IOでブロックされたスレッドは、中断された場合とまったく同じように中断されます。クロック割り込みまたはIO割り込みは、アイドルプロセスの場合に発生する、すぐに中断されたプロセスとは異なるプロセスを再開できます。が実行されていて、プロセスが待機していたイベントが発生しました。
私がしていることは、scratch
csrを使用して、現在実行中のプロセス/スレッドのコンテキストブロックを指すことです。割り込み時に、割り込みの処理(開始)に必要な最小限のレジスタを保存します。割り込みによって他のプロセス/スレッドが実行可能になった場合、割り込みから再開するときに、プロセスの優先順位を確認し、中断されたものを再開する代わりにコンテキストスイッチを選択する場合があります。中断されたものを再開すると、すぐに復元されます。そして、コンテキストを切り替えるために、中断されたスレッドのCPUコンテキストの保存を終了してから、別のプロセス/スレッドを再開し、scratch
レジスタを切り替えます。
(ネストされた割り込みの場合、再開時にコンテキストスイッチを許可しませんが、現在のコンテキストを保存した後の割り込みでは、scratch
優先度の高い割り込みを再度有効にする前に、コンテキストブロックの割り込みスタックにcsrを設定します。また、非常にマイナーなものとして最適化では、カスタムで記述されたアイドルスレッドは、そのPCを保存/復元する以外に何も必要ないと想定できます。)
したがって、割り込みハンドラで値が更新されるグローバル変数に対して条件付きにする必要があります。
wfi
どのイベントがハートを目覚めさせたのかわからないので、実装に関係なくそれをしなければなりません。
あなたは持っているかもしれn個の実行時に割り込みが有効になりwfi
、そのうちのいずれかが提起されている可能性があります。
wfi
は最適化であり、何かが起こるまで電力を節約します。お気づきのように、OSスケジューラーは、スケジュール可能なスレッドがない(たとえば、すべてIOを待機する、または単にスレッドがない)状態にある可能性があります。その場合、(必要なすべての可視性とアトミック性のセマンティクスを使用して)次のようなことを行う必要があります。
while ( ! is_there_a_schedulable_thread());
それはただ待っています。
ただし、(パフォーマンスとパワーを損なう可能性がある)タイトなループを回転させるのではなく、スケジューラーは以下を使用できます。
while ( ! is_there_a_schedulable_thread())
{
__wfi();
}
最悪の場合、それはタイトループのようであり、せいぜい外部割り込みが発生するまでハートを一時停止します(つまり、IOが完了し、スレッドが自由に実行できる可能性があります)。
スレッドがない場合でも、(タイマー割り込みのために)xマイクロ秒ごとにウェイクアップする方が、電力ループを無駄にするよりも優れています。
wfi
割り込みハンドラーですべての作業を行っている場合(ボタンが押されたときなど)、プログラミングの埋め込みにも役立ちます。
この場合、main
関数はスケジューラーと同じように永久ループしますが、終了条件はありません。命令は大幅にバッテリ寿命を向上します。wfi
wfi
ただし、どこでも使用することはできません。そうしないと、発生しない割り込みを自分で待っていることに気付く場合があります(実際、これは特権命令です)。
ハードウェアとの調整のための最適化と考えてください。
特に、割り込みが発生したことを確認する方法としては設計されていません。
void wait_for_int(int int_num)
{
//Leave only interrupt int_num enabled
enable_only_int(int_num);
__wfi();
restore_interrupts();
}
RISC-Vの特定の実装があれば、そのように使用できますが、擬似コードからわかるように、それほど便利ではありません。
1つを除くすべての割り込みを無効にすることは、通常、OSには余裕がないことです。
ただし、組み込みアプリケーションは可能です。