やあみんな、ビリーだよ。
今回は、USBコントローラを例にして、いよいよDMA転送の具体的な動作について説明していくよ。
USBコントローラの動作例
例として、DMAスレーブ型のUSBコントローラの内蔵FIFOに対して、汎用DMACで書き込みを行うケースを考えてみよう。通常、USBコントローラに内蔵されるFIFOの容量はそれほど大きくなくて、せいぜいパケット2個分(Bulkの場合、512バイト*2個=1Kバイト)くらいが一般的なんだ。
転送のパフォーマンスアップのためには、DMACを使って転送するサイズを、できるだけ(少なくともFIFOの容量よりも)大きくしたいよね。でも、FIFOが一杯になったらどうするんだろう?この場合、USBコントローラがDMACに連絡してDMA転送の進み具合をコントロールしないといけない。
一般に、DMAスレーブ機能を持つI/Oデバイスは、DMA転送要求(DREQ)という出力信号を持っているんだ。DMACは、DREQ信号が有効な間はDMA転送を行うけど、I/Oデバイス側がDREQ信号を取り下げると転送を中断する。このような動作が、前回書いた「デマンド転送」だよ。
今回のUSBコントローラの例では、「FIFOが一杯になった」らDREQを取り下げ、USBコントローラがデータを読み出して「FIFOに空きができた」らDREQを有効にするという制御が行われているんだ。読み出しの場合も同じで、「FIFOが空になった」らDREQを取り下げるということになるよね。
簡単なタイミングチャートはこんな感じ。
デマンド転送を行いたい場合は、DREQ信号をDMACと接続するようにハードウェアの回路を設計しないといけないんだよ。デバイスドライバを作る側も回路図とにらめっこして接続を確認する必要があるね。
最近は、I/OデバイスとDMACの両方を内蔵したCPUが増えてきているんだけど、内蔵I/Oデバイスと内蔵DMACの間でDMA転送をするのであれば、外部回路の設計は考えなくていいよね。この場合は、どのような方式でDMA転送を行うかを設定するレジスタが用意されているんだ。ただ、その内蔵モジュールの構成や仕様はCPUごとに異なるので、データシートをよく調べないといけないね。
割り込みタイミングの違い
DMAバスマスタになることができるI/Oデバイスの場合は、1系統の割込みをハンドリングするだけでかまわないんだよ。デバイスドライバは、内部で自動的に行われるDMA転送を意識する必要がないんだ。
でも、外部の汎用DMACを使用するスレーブデバイスの場合は、通常、対象I/Oデバイスの割込みに加えて、汎用DMACの転送完了割込みもハンドリングする必要があるんだ。つまり、2系統の割込みが入ることを考慮して、 デバイスドライバの処理シーケンスを設計しなくてはならないということだね。
ここで注意しなければならないポイントが、それぞれの割込みが通知されるタイミングの違いなんだ。先ほどのUSBコントローラのデータ送信(FIFO書き込み)の例を考えてみよう。下記2つのイベントが発生することになるけど、これらのタイミングは同時におきるわけではないんだ。
- (a)DMACのDMA転送完了割込み
- (b)USBコントローラのUSB転送完了割込み
(a)は、DMACが指定したサイズのデータをFIFOへ書き込んだ時点で通知されるよね。でも、この時点では、まだUSBバス上にデータが送出されていないかもしれない。USBコントローラがFIFOからデータを読み出して、実際にUSBバス上に送り出し終えるまでのタイムラグがあるよね。
つまり、パケット送信完了を保証するためには、(b)のタイミングでデバイスドライバからアプリケーションに通知する必要があるんだ。
※補足
パフォーマンスを重視する場合は、(a)のタイミングで送信完了とみなす場合もあります。この場合は、コントローラがUSBパケット処理を行っている間に、並行してデバイスドライバ(アプリケーション)が次の送信準備(データコピーなど)を行うことができ、処理効率が向上します。ただし、実際に次のDMA転送を起動するタイミングでは、直前のUSB転送完了を待ち合わせる必要があります。
以下に簡単なシーケンス図を示してみるね。
逆に、データ受信(FIFOからの読み出し)の場合は、(a)のタイミングに同期させる必要があるんだよ。なんでなのか分かるかな?
この場合は、USBコントローラによって受信データがFIFOに格納された後でDMACがそれを読み出すという順番になるからだね。このような、タイミングに起因する不具合はよくあるんだ。具体的にはこんなものがある。
転送方向 | 不具合現象の例 | 想定される原因 |
---|---|---|
送信 | Write関数が戻ってきた直後に、次のデータを書き込んだらUSBバス上のパケットの内容が化けていた。あるいはサイズが異なっていた。 | USBコントローラによるパケット送信処理が完了していなかったため。 |
受信 | Read関数が戻ってきた直後に、バッファを参照すると受信データの内容が化けていた。あるいは欠落していた。 | DMACによるDMA転送が完了していなかったため。 |
実際に不具合となるかどうかは、USBコントローラのアーキテクチャにもよるんだけど、少なくともタイミングの違いがあることは頭においておかないといけないね。
次に、DMA転送データを格納するバッファ(以下、DMAバッファ)に関して、注意しなければならない点について説明してみよう。
DMAバッファとキャッシュ
DMAバッファは、一般的に「キャッシュ不可領域」に配置するんだ。なぜ、CPUのキャッシュメモリを意識しなければならないかわかるかな。
DMAバッファは、CPUとDMAC(あるいはDMAバスマスタデバイス)の両方からアクセスされるメモリ領域だよね。仮に、CPUのデータキャッシュがコピーバック(ライトバック)モードで動作している状態で、 DMAバッファをキャッシュ可能な領域に確保したとしてみよう。
※補足
キャッシュの動作モードについては、「拾弐の巻:キャッシュは諸刃の剣」も参照ください。
CPUからDMAバッファに対してデータを書き込んだ後でDMACを起動しても、おそらく正しいデータは転送されない。書き込んだデータはキャッシュメモリにだけ格納されて、実メモリであるDMAバッファに反映されていない可能性が高いんだ。逆に読み出しの場合、DMACがDMAバッファにデータを格納した後でCPUがDMAバッファを参照しても、CPUはキャッシュされているデータを読み出してしまうので、ゴミデータしか読めないかもしれないね。
このようなメモリの不整合を防ぐために、DMAバッファを「キャッシュ不可領域」に確保するんだ。
実は、パフォーマンス向上のために、あえてDMAバッファを「キャッシュ可能領域」に確保する場合もある。大量のデータをDMAバッファにコピーする場合などは、キャッシュ可能な方が処理時間を短縮できるはずだよね。ただし、この場合は、デバイスドライバが明示的にキャッシュメモリの制御を行う必要があるんだ。
操作 | 機能 | 概要 |
---|---|---|
flush | キャッシュの吐き出し | 指定した領域に該当するキャッシュメモリの内容を実メモリに書き出す。 |
invalidate | キャッシュの無効化 | 指定した領域に該当するキャッシュメモリの内容を無効にする。 |
具体的には、以下のように実装するよ。
書き込み |
|
---|---|
読み出し |
|
要は、DMACはキャッシュメモリの存在を知らないので、デバイスドライバの側で適切にケアしてやる必要があるということだね。このような処理を「キャッシュコヒーレンシをとる」というんだ。
キャッシュメモリの仕様はCPUアーキテクチャに依存しているから、一般的にその制御ライブラリはBSPと呼ばれるCPU初期化モジュールの中で実装されることが多い。 デバイスドライバを設計する時に、使用する開発環境で利用可能かどうかを確認しておく必要があるから、気をつけないとね。
DMAバッファとアドレスアライメント
DMAバッファに関しては、もうひとつ「アドレスアライメント」の制約に注意しないといけないんだ。アライメント(alignment)はアラインメントと書くこともあって、「整列」という意味の単語なんだけど、ここでは「境界」という意味で使われているよ。アドレスアライメント制約は2通りある。
(1) DMACアーキテクチャに依存した制約
汎用DMACあるいはDMAバスマスタデバイスのデータシートには、たいてい「●KB境界のバッファを指定してください」という注意書きが記載されている。DMAバッファはこれに従って配置しなくてはならないんだ。例えば、EHCIというUSBホストコントローラの仕様では「4KB境界とせよ」という規定があるんだよ。
(2) キャッシュラインサイズに依存した制約
DMAバッファをキャッシュ可能領域に配置する場合は、そのアドレスをキャッシュラインサイズの境界に一致させなくてはいけない。この場合は、キャッシュのinvalidate/flushが必要ということを言ったばかりだけど、このflushの動作に関係する制約なんだ。
キャッシュのflushは、「キャッシュライン」単位に行われるんだ。DMAバッファがキャッシュラインサイズの境界に一致していないと、キャッシュをflushしたときにDMAバッファ以外のメモリ領域を壊してしまう恐れがあるんだ。
同じことなんだけど、DMAバッファのサイズは「キャッシュラインサイズの整数倍」としておく必要がある。これは、DMAバッファの終端付近のアドレスに対するflushが発生したときに、やっぱりメモリ破壊が起きてしまうからだね。
キャッシュラインのサイズはCPUアーキテクチャによって違うんだ。例えば、同じルネサスエレクトロニクスのSHシリーズでも、SH-2Aでは16バイト、SH-4Aでは32バイトなんだよ。注意しておく必要があるね。
終わりに
これで「DMA対応と言われたら」の巻は完結だよ。どうだったかな?DMACとかキャッシュメモリとか他のハードウェアのこともよく調べないといけないんだな、ということを感じてくれたらいいんだけど。
次回からは新シリーズになるよ。何の話をしようかな・・・
-
- 第1回 組込みシステムのこれから
- 第2回 IoTの成功はセキュリティ次第
- 第3回 組込みでもGPUやFPGAと早めに親しんでおこう
- 第4回 電子産業の紅白歌合戦、CEATECで垣間見えた未来
- 第5回 小口開発案件の集合市場、IoTの歩き方(上)
- 第6回 小口開発案件の集合市場、IoTの歩き方(下)
- 第7回 徹底予習:AI時代の組込みシステム開発のお仕事
- 第8回 いまどきのセンサー(上):ありのままの状態を知る
- 第9回 いまどきのセンサー(下):データを賢く取捨選択する
- 第10回 組込みブロックチェーンの衝撃(上)
- 第11回 組込みブロックチェーンの衝撃(下)
- 第12回 エネルギーハーベスティングの使い所、使い方
- 第13回 「人を育てる」から「道具を育てる」へ、農業から学ぶAI有効活用法
- 第14回 CPS時代に組込みシステム開発に求められることとは
- 第15回 次世代車のE/Eアーキテクチャに見る組込みの進む道
- 第16回 RISC-Vが拓く専用プロセッサの時代
- 第17回 振動計測の大進化で、熟練エンジニアのスキルを広く身近に
-
- 零の巻:組込みというお仕事
- 壱の巻:2進数と16進数を覚えよう!
- 弐の巻:割り込みとポーリング
- 参の巻:printf()が使えない?
- 四の巻:これにもIntelが入ってるの?
- 五の巻:Endianってなに?
- 六の巻:マルチタスクとは
- 七の巻:スタックってなあに?(1)
- 七の巻:スタックってなあに?(2)
- 八の巻:メモリを壊してみましょう
- 九の巻:コードが消える?~最適化の罠~
- 拾の巻:例外が発生しました
- 拾壱の巻:コードサイズを聞かれたら
- 拾弐の巻:キャッシュは諸刃の剣
- 拾参の巻:デバイスにアクセスするには
- 拾四の巻:セキュリティってなに?(1)
- 拾四の巻:セキュリティってなに?(2)
- 拾四の巻:セキュリティってなに?(3)
- 拾五の巻 :DMA対応と言われたら(1)
- 拾五の巻 :DMA対応と言われたら(2)
- 拾六の巻:ヒープとスタック
- 拾七の巻:フラグメンテーション
- 拾八の巻:CPU起動とブートローダ
- 拾九の巻:kmとKByteの「kとK」
- ビリーへの質問:DMAとキャッシュの関係
- ビリーへの質問:スタックオーバーフローについて
- ビリーへの質問:CPUレジスタについて