DMA転送の実務とキャッシュ整合性
DMA(Direct Memory Access)は、CPUを介さずにデータを転送する仕組みです。CPU負荷を下げ、処理を高速化できます。ところが現場では、導入後に不具合へ直面することが少なくありません。受信データが壊れる、キャッシュ有効時だけ転送に失敗するといった症状は一例です。その多くは、CPUキャッシュとの整合性に原因があります。
DMAもキャッシュも、処理を高速に行うためには非常に強力な仕組みで、限られたCPUリソースを最大限使用する必要がある組み込みシステムには欠かせません。一方で、再現しにくい不具合や、デバッグしにくい不具合を生みやすいという使用上の難しさもあります。使いこなすときのポイントをしっかりと押さえて、正しく動作するようにしましょう。
この記事ではDMAの活用で起こり得る問題を解説したのち、キャッシュ整合性の仕組み、Clean操作とInvalidate操作の使い分け、DMA完了割り込みや高負荷時の問題、不具合の切り分け方について解説します。
DMAの活用で起こり得る問題とDMA設計の基本
まずDMAの活用で起こり得る問題と、DMAバッファ設計における基本を解説します。
DMAの導入により生じる問題
DMAの活用は、メモリの内容を2つの経路で参照することに起因する問題を引き起こす可能性があります。CPUはキャッシュからメモリの内容を、DMAは物理メモリを直接参照するためです。ここで「CPUはキャッシュに最新の値を持ち、物理メモリは古い値のまま」のケースで、DMAが物理メモリを読むと古いデータが転送されてしまいます。
やっかいなのは、この不具合がキャッシュ有効時だけ再現する点でしょう。キャッシュを無効にすると症状が消えるため、原因の究明が遅れがちになります。
CPUキャッシュとDMAとの関係
不具合の根本は、データが更新されるタイミングがキャッシュと物理メモリで異なる点にあります。キャッシュの書き込み方式はライトスルー方式とライトバック方式の2つです。
ライトスルー方式の場合は、キャッシュと物理メモリの両方を同時に書き換えます。一方でライトバック方式は、キャッシュだけ先に更新し、メモリへの反映を後回しにします。メモリに反映されていない状態でデータを読み込むと、CPUが参照したデータとDMAが参照したデータに相違が発生します。こうした書き込み方式の差が、DMAでの整合性問題を生む正体です。
DMAバッファ設計における基本
整合性問題を抑える第一歩が、DMAバッファの設計です。鍵になるのが、キャッシュラインアラインメントという考え方になります。キャッシュの管理単位の境界に、バッファをそろえることを指します。
Arm系マイコンのCortex-M7では、32バイト境界に合わせ、サイズもその倍数にするのが基本です。あわせてDMA専用領域を分け、リングバッファとダブルバッファを用途で使い分けます。整合性管理を避けたいなら、Non-cacheable領域の活用も有効でしょう。
書き戻し(Clean)と無効化(Invalidate)の使い分け方
キャッシュの不整合は、2つの操作で解消できます。物理メモリへ書き戻すCleanと、古いキャッシュを捨てるInvalidateです。物理メモリのデータとキャッシュのデータのどちらを正しいと扱うかによって、操作を変える必要があります。
書き戻し(Clean)操作の概要
Clean操作は、キャッシュ上の最新データを物理メモリへ書き戻す処理です。キャッシュに保存されたデータを「正」として扱う場合に用います。
ライトバック方式では、書き込み直後のデータがキャッシュに留まります。この状態では物理メモリの内容が古いままのため、Clean操作を行うことでキャッシュの内容を反映させます。Armの資料でも、変更内容をDMAコントローラーから読み取れる状態にする操作としてCleanが定義されています。
無効化(Invalidate)の概要
Invalidate操作は、古いキャッシュを破棄し、CPUに物理メモリから読み直させる処理です。DMAが受信したデータを、CPUが読む場面で使います。キャッシュに保存されたデータを「誤り」として扱う場合に用います。
DMAがバッファへ書き込んでも、キャッシュには受信前の値が残ることがあります。このときCPUが読むと、古い値を取得してしまうのです。これを防ぐため、受信完了後にInvalidate操作で無効化します。実装では、完了割り込みのハンドラ内で呼ぶ構成が一般的でしょう。
双方向DMAとメモリバリア
双方向DMAでは、CleanとInvalidateの双方が要ります。特に同じバッファを再利用する設計では、前回のデータが残り、意図しないデータを読み出す危険があるのです。再利用のたびに、適切な操作を挟まなければなりません。
もう一つ欠かせないのが、メモリバリア命令です。CPUは命令の順序を入れ替えて実行することがあり、これがDMA起動と整合しないと不具合を招きます。ArmのDSBとDMBは、この順序を制御する命令です。DMBはアクセスの順序を保証し、DSBは完了を待ってから次へ進ませます。
DMA完了割り込みと高負荷時の問題
整合性を保つ操作は、転送の完了にあわせて実行する必要があります。その完了をCPUへどう伝えるのでしょうか。手段となるのが、DMA完了割り込みです。ポーリングとの違いから見ていきます。
DMA完了割り込み
DMA完了割り込みは、転送終了のタイミングでCPUへ通知する仕組みです。転送完了割り込みとも呼ばれます。対するポーリングは、CPUが状態レジスタを繰り返し読んで完了を待ちます。CPUが待ち続けるため、効率は下がってしまうのです。完了割り込みなら、CPUは転送中に他の処理を進められます。リアルタイムOSとは、ハンドラからタスクへ通知する構成で連携します。
高負荷時の問題
完了割り込みは万能ではありません。転送が高頻度になると、割り込み処理そのものがCPUを圧迫します。1回ごとの負荷は軽くても、回数が増えれば負荷は積み上がるのです。注意したいのは、低負荷の検証では見えにくい点でしょう。試験では正常でも、本番の高負荷環境では処理が間に合わなくなるおそれがあります。
DMA転送の不具合の切り分け
DMAの不具合には、症状にいくつかのパターンがあります。不具合のパターンを知っていれば、原因の見当をつけやすくなるでしょう。症状と切り分け手順を見ていきます。
DMA転送不具合における主な症状
DMAの不具合には、特徴的な症状があります。代表的なのは3つの型でしょう。最初だけ成功し2回目以降に失敗するもの、一定サイズを超えると壊れるもの、キャッシュ有効時だけ失敗するものです。さらに、デバッガを接続するとステップ実行で再現せず、正しく動作してしまう、というケースもあります。こうした症状のパターンを知ることが、原因究明の出発点になるでしょう。
原因の切り分け方
原因を絞り込むには、症状から逆算します。まず試したいのが、キャッシュを無効にした転送です。これで症状が消えれば、整合性の問題が原因の可能性が高いと判断できます。次に確認するのは、DMAディスクリプタの設定内容になります。ディスクリプタとは、転送元や転送先のアドレスを記述した制御情報です。記述ミスは、転送異常へ直結します。それでも特定できないときは、ロジックアナライザやメモリダンプを活用しましょう。


