0から作るソフトウェア開発

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 DMAドライバ

前回までの内容

これまでで、 についてわかりました。それではカーネルを0から開発していきましょう!

DMAという言葉を聞いたことはありますでしょうか?情報処理試験などに出てきますがイマイチよく

わからない言葉だと思います。今回はよく耳にするけどよくわからないDMAについて見ていきます

前回までに見てきましたフロッピーディスクドライブと連動しますので、あわせてDMAについて見ていきます。

DMA(Direct Memory Access)の概要

コンピューターでデータを転送する方法としては3種類の方法があります

それぞれについて少しだけ見ていきたいと思います

ポーリングによるデータ転送

ポーリングによるデータ転送は現在でもよく使用されています。ポーリングではデバイスからデータを

読み込める/書き込みるかどうかを常に監視して、転送可能であれば読み/書きする方法となります

この方法では、CPUが主にIN命令OUT命令でデータをメモリに転送します。

(メモリマップドI/OではMOV命令

割り込みを受けてからのデータ転送

ポーリングによるデータ転送では、CPUの処理速度(デバイスに読み/書きできるかどうか確認する処理速度)

に対して、デバイスの動作が非常に遅いため無駄なウェイト処理が生じてしまいます。そこで、デバイスが

読み/書きできる状態になるまでは、別の処理を行い、デバイスの準備ができたときに割り込みとして

信号を受け取り、データ転送を行う方式が考えられてきました。しかし、この方式は低速なデバイス(キーボードなど)には

有用となりますが、高速に読み/書きできるデバイスでは割り込みが頻繁に発生してしまいます。

割り込みが発生するとCPUは現在の処理を一旦セーブして割り込み処理を行いますが、この一旦中断する

処理にある程度の時間がかかるため、高速なデバイスだとネックになってきました

DMA(Direct Memory Access)による転送

HDDやフロッピーディスクの台頭でより速いスピードで大容量のデータを転送できるようになりました。

このような大容量データを高速読み書きできるデバイスに対して、データ転送を割り込みで行っていては、

他のプロセスの処理などができなくなってきました。そこで、考えだされたのが、DMAとなります

DMAは旧来CPUが行ってきたデータ転送処理を一手に引き受けてくれます。ある程度のデータの

転送が終わると割り込みで知らせてくれますので、頻繁に割り込みが入ることはありません

(バックグラウンドでデータ転送を行ってくれるイメージとなります)



DMAはデータ転送だけを主に行うのでポーリング、割り込みを受けてからのデータ転送と比べて非常に高速です。

DMAがデータ転送を行ってくれますので、CPUはIN命令、OUT命令、MOV命令を処理する時間すら省くことができます

組み込み機器(携帯電話など)の小さなデバイスからスーパーコンピューターなどの大きなシステムで使用されています。

DMAの特徴

DMAの特徴として:

となります

ISA(Industry Standard Architecture)とDMA

DMAの仕様としてISAで規定されていて、

DMAコントローラとして主に使用されていた代表的なICとして8237Aがあります

8237Aのデータシート がダウンロードできますので興味の有る方はダウンロードしてみてください)

DMAコントローラ8237Aが導入されたのはIBM PCからとなります。

DMAコントローラ8237Aは4つのチャンネルがありますので、4つのデバイスのデータ転送が可能となります

ATXマザーボードでは1つのDMAコントローラのみでしたが、現在のPCでは2つのICで合計8のデバイスの

データ転送が可能となっています。 割り込みその2で見てきたPICと同じように2つのICが連結されています

2つのDMAコントローラは4MHzのクロックで動作しています。



現在のパソコンでは、ISAのDMAでは8つのデバイスしか転送できない、また64kBまでのメモリまでしか

アクセスできない(理由は後述します)、転送速度が遅いためHDDなどの大容量デバイスかつ高速な

デバイスはUDMA(Ultra Direct Memory Access)が使用されています。

しかし、フロッピーディスクなどの昔からあるデバイスをサポートするためにISAのDMAは

現在でも残っていますので、ISA仕様のDMAは現在のパソコンでも残っています。

このサイトではISA仕様のDMAをISA DMAと表記します



デバイスとDMAコントローラの各チャンネルはDACK(DMA Acknowledge:DMA応答)ラインと

DRQ(DMA Request:DMA要求)ラインで結ばれています。DMAの各チャンネル(2つのICで8チャンネル)は

次の表の用にデバイスが割り当てられています。

DMAのチャンネルとデバイスの割り当て
チャンネル 仕様 割り当てられるデバス
チャンネル0 XTとAT システムによって予約されていますので利用できません。(DRAMのリフレッシュに使用されています)
チャンネル1 XTとAT 標準仕様のデバイスは割り当てられていませんので、自由に利用可能です
チャンネル2 XTとAT フロッピーディスクドライブが割り当てられています
チャンネル3 XTとAT ハードディスクドライブが割り当てられています。(通常はUDMAを使用します)
チャンネル4 ATのみ DMAコントローラのスレーブとマスターが接続されています
チャンネル5 ATのみ 標準仕様のデバイスは割り当てられていませんので、自由に利用可能です
チャンネル6 ATのみ 標準仕様のデバイスは割り当てられていませんので、自由に利用可能です
チャンネル7 ATのみ 標準仕様のデバイスは割り当てられていませんので、自由に利用可能です


チャンネルには優先度があります。優先度はチャンネル0が最も高く、チャンネル7が最も低くなります。

DMAを使用してデータ転送を始めるには、DMAコントローラにメモリアドレス(ISA DMAは64KBまで)と

転送データサイズ(データカウントといいます)を設定します。

DMA(Direct Memory Access)の動作

FDDコントローラがDMA転送が必要だと判断するとDREQピンでDMAコントローラに対して

DMA転送の要求を通知します。この要求を受けてDMAコントローラは転送の準備を開始します

DMAコントローラがデータ転送を行うときにはCPUに対してDMA転送の要求を出します

この要求はHOLDピンで出します。要求に対してCPUはHLDAピンでメモリとのバスを使用する許可を

応答します。これにより、DMAコントローラはメモリを操作することが可能となります

転送が可能となりましたので、格納するアドレスをアドレスバスに出力して、メモリに通知します

これで転送準備ができたので、DMAコントローラはFDDコントローラに転送準備ができたことを

DACKピンで通知します。これが一連のおおまかな流れとなります。



DMA転送しているときと、していないときに各バスの制御はどうなっているのでしょうか?

バス制御について見ていきます。

DMA転送を行っていないときのバス制御

DMA転送を行っていないときは、CPUはメモリとアドレスバス、制御バスとデータバスでつながっています

この状態ではCPUはメモリにデータを書き込んだり、読み込んだりすることができますが、

一方DMAコントローラはメモリとつながっていません

DMA転送していないとき

DMA転送を行っているときのバス制御

DMA転送を行っているときにはCPUとメモリはアドレスバス、制御バスとデータバスもつながっていない

状態となります。この状態ではCPUはメモリにデータを書き込んだり、読み込んだりすることができません。

一方でDMAコントローラはアドレスバスと制御バスが接続され、DMA転送できるようになります



DMA転送しているとき

この操作をDMA転送が必要なときに行い、CPUからバスの使用許可をその都度もらいます

もちろん、DMAコントローラがバスを専有していてはCPUがメモリを使用できませんので、

1バイト転送毎(ワード転送毎)にバスをCPUに明け渡します。(デバイスのリード/ライトは

メカ動作が伴いますので非常に遅いのでこのような操作となります)

DMA転送するまでの手順

なんとなく仕組みが分かりましたので、実際にフロッピーディスクからデータを読み取るときに

行う手順について見ていきたいと思います。次の図がその手順となります

DMA転送までの動作

DMA転送を開始する前にDMAコントローラに指示(プログラム)しておきます

そして、設定したアドレスから設定したバイト数分だけDMA転送が完了した後は

DMA転送を要求したデバイスがIRQを発生させて、CPUに転送が終わったことを通知します

(IRQはPIC経由でCPUに通知されます)

PCIについて

PCIバスに接続するデバイスもDMAを使用しますが、ISA DMAを使用することはありません

(転送速度が遅いためです。同様にHDDもISA DMAを使用しません)

ISA DMAを使用しない代わりに、PCIバスにつながっているデバイスはPCIバスコントローラ

(PCIバスマスター)に転送を要求します。PCIのデータ転送は4GBまでのメモリアクセスが可能ですが

ダブルアドレスサイクル(DAC:Double Address Cycle)などの技術で4GB以上のメモリにも

アクセス可能となります



DMA転送が行われるときの動作がなんとなく分かってきましので、DMAコントローラについて

詳細を見ていきます。先ほど見てきましたように、DMAコントローラのICとしては8237Aが

代表的なICとなりますので、8237Aについて詳細を見ていきます

DMAコントローラ8237A

ISA仕様ではインテルの8237Aを基本的なDMAコントローラとしていま策定されています

8237A以後に新機能を持ったDMAコントローラICが開発されていますが、8237Aとの互換性は

保たれています。DMAコントローラ8237Aのブロック図を見ていきます

8237A

ISAのDMAはインテル8237A DMAチップを基にしています。その基となった8237Aは次のような

ICとなります。パソコンには更に改良されたDMAコントローラが載っていますが、基本はまったく変わりません

8237Aのブロック図

このICについてこれからプログラムを行っていきます。各ピンの役割について見て行きましょう

8237Aのピンアサイン
シンボル ピン番号 I/O 名称 説明
Vcc 31 電源 +5V電源です。
Vss 20 グラウンド グラウンドです
CLK 12 I クロック入力 8237Aを駆動するクロック及び、データ転送に使用するクロックです。 8237Aは3MHzまでのクロックに対応します(8237A-2は5MHz)
CS 11 I チップセレクト Lowアクティブのピンです。 IN/ OUT命令でCPUが8237Aに アクセスするときにLowにします。
RESET 13 I リセット リセット端子をHighにすることで コマンドレジスターステータスレジスターリクエストレジスターテンポラリーレジスターをクリアします。 また、ファースト/ラストフリップフロップもクリアし、マスクレジスターを セットします。リセット後8237Aはアイドル状態となります。
READY 6 I レディ レディピンに入力があると8237Aが出力するメモリー読み込み/書き込みパルス間隔を 広げます(遅くします)。これにより、低速なメモリーやデバイスとのデータ転送が可能となります。
HLDA 7 I ホールド応答 CPUがDMAコントローラにバスを解放するときにこのピンをHighにします。
DREQ0-DREQ3 16-19 I DMA要求 DMAコントローラにある4つある各チャンネルに対してDMA転送を要求するピンとなります。 各ピンはデバイスコントローラと接続されていて、DMA転送が必要なときにデバイスのコントローラが このピンをアクティブにします。DMA要求には優先度があり、DREQ0が最も高く、DREQ3が最も低くなります。 DREQ0-3がアクティブになってDMA要求を受けた後、応答としてDACK0-3ピンをアクティブにします。
DB0-DB7 21-23, 26-30 I/O データバス 8ビットの双方向データバスです。
IOR 1 I/O I/Oリード Lowアクティブで双方向のピンです。アイドル状態のときには CPUが8237Aのレジスターを読み込むときにLowにします。 DMA転送時には8237Aが、デバイスからデータを読み込むときにLowにします。
IOW 2 I/O I/Oライト Lowアクティブで双方向のピンです。アイドル状態のときには CPUが8237Aのレジスターにデータを書き込むときにLowにします。 DMA転送時には8237Aが、デバイスにデータを書き込むときにLowにします。
EOP 36 I/O 処理終了 Lowアクティブで双方向のピンです。アイドル状態のときには Lowの場合、DMA転送が完了している(DMA転送が可能な)ことを示します。 DMA転送中にこのピンがLowになるとDMA転送を中止します。 また、DMA転送バイト数がTC(ターミナルカウント)に に達したときに8237AがこのピンをLowにします。このときに自動初期化機能が 有効になているとベースレジスターに現在のレジスター値が書き換えられます。 自動初期化機能が無効の場合は、 ステータスレジスターのTCビットと マスクビットがセットされます。(マスクビットはクリアされた状態となります)。
A0-A3 32-35 I/O アドレス 下位のアドレスビットで双方向のラインです。 アイドル状態の場合8237Aのレジスターを指定するのに使用します。 DMA転送中は、8237Aがメモリーなどのアドレスを指定するのに使用します。
A4-A7 37-40 O アドレス 上位のアドレスで出力専有です。 DMA転送時のみ有効です。
HRQ 10 O ホールド要求 システムバスの使用許可をCPUに要求するピンです。 各チャンネルに対応するマスクビットが クリアされている場合にDREQピンに要求があるとこのピンをアクティブにします。 HLDAピンの入力が入る前にこのピンをアクティブにします。
DACK0-DACK3 14-15, 24-25 O DMA応答 各チャンネルのDMA転送が許可された場合にこの応答ピンをHighにします。 リセット時にはLowになっています。
AEN 9 O アドレス有効 上位のアドレスビットが有効かどうかを示すピンです。 DMA転送中にこのピンがアクティブになっていると アドレスバス以外のシステムバスが無効となります。 Highアクティブです。
ADSTB 8 O アドレスストローブ Highアクティブで外部ラッチに上位アドレスビットをストローブするときに使用します。 DMA転送中にこのピンがアクティブになることでDB0-DB7がアドレスの上位となります。 (DMAの転送アドレスは16ビットで下位はA0-A7で指定します)
MEMR 3 O メモリー読み込み DMA読み込み転送中かメモリーからメモリーの転送中にアクティブにします。 Lowアクティブで指定したメモリーアドレスからデータを読み込むときに使用します。
MEMW 4 O メモリー書き込み DMA書き込み転送中かメモリーからメモリーの転送中にアクティブにします。 Lowアクティブで指定したメモリーアドレスにデータを書き込むときに使用します。


DMAコントローラICの特徴

いままで見てきましたICと同じようにグラウンド、電源、クロックピンがあります。これらのピンはICが

動作する上でどのICでも必須のピンとなります。またチップセレクト、A0-A7アドレスピン、D0-D7データピンに

ついてもCPUとデータのやり取りを行うためにどのICにもあります。

DMAコントローラに特有のピンとしてはHLDAピンとHRQ、DREQピンとDACKがあります。



DMA転送したいときにコントローラはHRQをHighにすることでCPUにシステムバスの使用許可を求めます

一方CPUは要求を受けると、システムバスを使用していない場合に、DMA転送のためにシステムバスを

開放し、コントローラにHLDAピンで許可を出します。(DMA転送はCPUがシステムバスを使用していない時のみ

可能となります)。



一旦DMA転送を開始しますが、CPUの処理は速いので、すぐにメモリーにアクセスします。(キャッシュのミスが

発生すると、メモリーから命令やデータを取り出す必要がでてきます)。この場合には、HLDAピンでコントローラに

出しているシステムバス使用許可をとりあげてCPUがバスを使用します。(転送の同期はとります)

DMAコントローラはこの間もHRQピンでバス使用許可を求めていますので、バスアクセスが終わるとCPUは

バスの使用許可をだします。

DMAコントローラとフロッピーディスクコントローラ

さきほど見てきましたように、DMAコントローラのICの特徴としてDREQピンとDACKピンがありました。

DMAコントローラとフロッピーディスクコントローラはDREQ2ピンとDACK2ピン(DMAチャンネル2)で接続されています。

ですので、フロッピーディスクドライバでフロッピーディスクコントローラにDMA転送するように制御コマンドを

書き込むとフロッピーディスクコントローラはDREQ2ピンでDMAコントローラにDMA転送を要求します

DMA転送のすべての起点はデバイスが要求するDREQピンからとなります



デバイスがDMA転送要求を出すと、DMAコントローラがCPUにシステムバス使用権を求めます

フロッピーディスクコントローラでも同様にD0-D7データバスがありました。このデータピンは

DMAコントローラのデータピンD0-D7とも接続されていますし、メモリーにも接続されています

ですので、CPUがいなくてもこの3つのデバイス間でデータのやり取りが可能となっています



フロッピーディスクに書き込みする場合には、MEMRをアクティブにして、DMAコントローラは

転送元メモリアドレスをアドレスバスに出力します。メモリーコントローラはアドレスバスを読み込み、

メモリーに格納されているデータをデータバスに出力します。フロッピーディスクコントローラは

WRITE制御コマンド処理を行いますので、データバスからデータを読み取り、ドライブにデータを

送ることでディスクに書き込みを行います。

DMA転送とアドレス

DMA転送をするアドレスは16ビットで指定されます。DMA転送中はADSTBがアクティブになったときに

16ビットアドレスの上位8ビットとしてD0-D7から出力されます。また下位8ビットはA0-A7から出力されます

これによりメモリーアドレスを16ビットで指定することになりますので、DMA転送できるメモリーアドレスの

範囲は0x00000000から16ビットの最大値0x0000FFFFまでの64KBとなります


DMA転送中でもCPUはメモリーにアクセスしたいときにはいつでもHLDAピンでシステムバスを取り戻します

ISA DMAとシステムバス

ISA DMAコントローラICはIBM仕様で4.77MHzで動作するように規定されています。

一方で現在のパソコンではシステムバス(フロントサイドバス)は133MHz以上で動作するものもあります

そして、DMAコントローラはこのシステムバスに接続されていますが、どのように頑張ってもDMAコントローラは

133MHz以上もあるクロックの速度まではだせません(仮に速いクロックを入力してもまともに動作しません)

そこで、DMA転送中(DMAに限らず低速なデバイスにアクセスするIN/OUT命令時も)はシステムバスの

クロックを落とし、デバイスのクロックで動作しますので、バスアクセスは予想外に遅いです。

(互換性を保つために仕方ありませんが。。。)

x86のDMAコントローラ

DMAコントローラはパソコン上に2つあります。これまで見てきました PIC と同じように

マスターとスレーブがあります。

8237A DMAコントローラのマスターとスレーブ

このように、マスターのDREQ0とHRQが、スレーブのHLDAとDACK0がつながっています

各ICのDREQにプログラムからアクセスするチャンネルを図に記載しています

スレーブのDREQ0をチャンネル0としてマスターのDREQ3をチャンネル7とする合計8チャンネルとなります

ここでチャンネル4がマスターとスレーブを接続するチャンネルとなります。

このように接続するとスレーブのDREQ0-DREQ3に要求が入ると、HRQがアクティブになり

マスターのDREQ0に対して要求します。DREQ0に要求が入ると今度はマスターのHRQが

CPUに対してシステムバス解放要求を出します。これにより、スレーブのDMA転送要求が

CPUまで伝達されることになります。



またTC(ターミナルカウント)については、マスターとスレーブのTCのORをとります

これにより、マスターかスレーブのDMAのブロック転送が終わればTCピンがアクティブとなります

x86のDMAコントローラの特徴

それでは、DMAコントローラについてプログラムインターフェイス詳細を見ていきましょう

DMAコントローラ8237Aのレジスター制御

DMAコントローラにはマスターとスレーブのコントローラがあります。それぞれにポートアドレスがありますので、

個別に制御できます。各レジスターはIN命令 OUT命令で読み書きします

制御レジスター

まずは、IC自体の制御を行うレジスターについて見ていきます。

DMAコントローラ0の制御レジスター(スレーブ)
ポートアドレス Read/Write 説明
0x008 Read ステータスレジスター
0x008 Write コマンドレジスター
0x009 Write リクエストレジスター
0x00A Write シングルチャンネルマスクレジスター
0x00B Write モードレジスター
0x00C Write クリアーバイトポインタレジスター
(フリップフロップリセットレジスター)
0x00D Read テンポラリーレジスター
0x00D Write マスタークリアーレジスター
0x00E Write マスクリセットレジスター
0x00F Read/Write オールマスクレジスター


DMAコントローラ1の制御レジスター(マスター)
ポートアドレス Read/Write 説明
0x0D0 Read ステータスレジスター
0x0D0 Write コマンドレジスター
0x0D2 Write リクエストレジスター
0x0D4 Write シングルチャンネルマスクレジスター
0x0D6 Write モードレジスター
0x0D8 Write クリアーバイトポインタレジスター
(フリップフロップリセットレジスター)
0x0DA Read テンポラリーレジスター
0x0DA Write マスタークリアーレジスター
0x0DC Write マスクリセットレジスター
0x0DE Read/Write オールマスクレジスター


これらのレジスター詳細については後ほど見ていきます。ここで、マスターのレジスターとスレーブのレジスターでは

ポートアドレスのアサインが少し違っています。例えばスレーブのステータスレジスターのポートアドレスは0x008

でリクエストレジスターは0x009で1バイト間隔ですが、マスターのステータスレジスターは0x0D0、

リクエストレジスターは0x0D2と2バイト間隔となっています。これはスレーブは8ビットで動作し、マスターは

16ビットで動作するためとなります。(レジスター自体は8ビットのレジスターです)

とりあえずレジスターが少し出てきましたので、C言語で定義をしておきます

制御レジスターの定義

各レジスターのポートアドレスを定義していきます

(プログラム中のDMC0がスレーブでDMC1がマスターとなります)


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    Description :8237 High Performance Programmable DMA Controllerr
                 ( DMAC 0 ) 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==================================================================================

    ISA DMAC Ports ( Generic Registers )

    DMAC 0

==================================================================================
*/
/* DMAC0 Ports( Slave ) */
#define DEF_PORT_DMAC0_STATUS       0x0008  /* status register                  */
#define DEF_PORT_DMAC0_COMMAND      0x0008  /* command register                 */
#define DEF_PORT_DMAC0_REQUEST      0x0009  /* request register                 */
#define DEF_PORT_DMAC0_SINGLE_MASK  0x000A  /* single mask register             */
#define DEF_PORT_DMAC0_MODE         0x000B  /* mode register                    */
#define DEF_PORT_DMAC0_CLEAR_BP     0x000C  /* clear byte pointer register      */
#define DEF_PORT_DMAC0_TEMPORARY    0x000D  /* temporary register               */
#define DEF_PORT_DMAC0_MASTER_CLEAR 0x000D  /* master clear register            */
#define DEF_PORT_DMAC0_MASK_RESET   0x000E  /* mask reset register              */
#define DEF_PORT_DMAC0_ALL_MASK     0x000F  /* all mask register                */

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    Description :8237 High Performance Programmable DMA Controllerr
                 ( DMAC 1 ) 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==================================================================================

    ISA DMAC Ports ( Generic Registers )

    DMAC 1

==================================================================================
*/
/* DMAC1 Ports( Master ) */
#define DEF_PORT_DMAC1_STATUS       0x00D0  /* status register                  */
#define DEF_PORT_DMAC1_COMMAND      0x00D0  /* command register                 */
#define DEF_PORT_DMAC1_REQUEST      0x00D2  /* request register                 */
#define DEF_PORT_DMAC1_SINGLE_MASK  0x00D4  /* single mask register             */
#define DEF_PORT_DMAC1_MODE         0x00D6  /* mode Register                    */
#define DEF_PORT_DMAC1_CLEAR_BP     0x00D8  /* clear byte pointer register      */
#define DEF_PORT_DMAC1_TEMPORARY    0x00DA  /* temporary Register               */
#define DEF_PORT_DMAC1_MASTER_CLEAR 0x00DA  /* master clear Register            */
#define DEF_PORT_DMAC1_MASK_RESET   0x00DC  /* mask reset Register              */
#define DEF_PORT_DMAC1_ALL_MASK     0x00DE  /* all mask Register                */



チャンネル制御レジスター

DMA転送はチャンネル数分可能です。各チャンネルに対して、DMA転送する開始メモリーアドレスと

何バイト転送するかを設定するレジスターがあります。開始メモリーアドレスを設定するレジスターを

ベースアドレスレジスター、何バイト転送するかを設定するレジスターをベースワードカウントレジスター

言います。ベースアドレスレジスターとベースワードカウントレジスターは16ビットのレジスターです

値を設定するときは、1バイト(8ビット)ずつ連続で書き込みます。

では、ベースアドレスレジスターとベースワードカウントレジスターを見て行きましょう

ベースアドレスレジスターとベースワードカウントレジスター

このレジスターもマスターとスレーブの各々4チャンネル分ずつあります

DMC0ベースアドレスレジスターとベースワードカウントレジスター(スレーブ)
ポートアドレス Read/Write 説明
0x000 Write チャンネル0 ベースアドレスレジスター
DRAMリフレッシュ用にシステムが使用しますので
使用できません。
0x001 Write チャンネル0 ベースワードカウントレジスター
DRAMリフレッシュ用にシステムが使用しますので
使用できません。
0x002 Write チャンネル1 ベースアドレスレジスター
0x003 Write チャンネル1 ベースワードカウントレジスター
0x004 Write チャンネル2 ベースアドレスレジスター
0x005 Write チャンネル2 ベースワードカウントレジスター
0x006 Write チャンネル3 ベースアドレスレジスター
0x007 Write チャンネル3 ベースワードカウントレジスター


DMC1ベースアドレスレジスターとベースワードカウントレジスター(マスター)
ポートアドレス Read/Write 説明
0x0C0 Write チャンネル4 ベースアドレスレジスター
マスターとスレーブの連結用で使用できません
0x0C2 Write チャンネル4 ベースワードカウントレジスター
マスターとスレーブの連結用で使用できません
0x0C4 Write チャンネル5 ベースアドレスレジスター
0x0C6 Write チャンネル5 ベースワードカウントレジスター
0x0C8 Write チャンネル6 ベースアドレスレジスター
0x0CA Write チャンネル6 ベースワードカウントレジスター
0x0CC Write チャンネル7 ベースアドレスレジスター
0x0CE Write チャンネル7 ベースワードカウントレジスター


このレジスターのポートアドレスについてもマスターのレジスターは2バイト(16ビット)間隔となっています。

このレジスターは16ビットのレジスターとなりますので、 IN/ OUT命令は2回連続で実行するようにします

ここで注意していただきたいのですが、スレーブは8ビットで動作、マスターは16ビットで動作します

ですので、チャンネル4からチャンネル7は特殊な扱いとなります。通常DMA転送時には1バイト書き込むと

DMAコントローラは転送アドレス(メモリーアドレス)を1増やします。しかし、マスターは16ビットで動作しますので

1バイト書き込むのではなく2バイト(16ビット)書き込みます。そこで、1回の転送(2バイトの書き込み)で

転送アドレスを2増やす必要があります。ここでDMAコントローラは次のような仕組みで動作することになります

マスター(16ビット動作)の転送アドレス操作

CPUはベースアドレスレジスターに転送開始アドレスを1ビット右シフトしてベースアドレスレジスターに書き込みます

DMAコントローラは1回の転送(2バイト転送)を行うごとに転送アドレスを1増やします。転送に使用するアドレスは

左に1ビットシフトしてからアドレスバスに出力します。こうすることで、常に(2進数で)2ずつ転送アドレスが

増えていることになります。(ページングアドレスはそのまま加算されてアドレス バスに出力されます)。

ベースアドレスレジスターとベースワードカウントレジスターの実装

実際にプログラムしてみて動かしてみないと何がなんだかわかりませんので、実装を進めていきます

まずはベースアドレスレジスターとベースワードカウントレジスターを定義していきます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    Description :8237 High Performance Programmable DMA Controllerr
                 ( DMAC 0 ) 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==================================================================================

    ISA DMAC Channel Ports ( Channel Register )

    DMAC 0

==================================================================================
*/
/* DMAC0 Ports( Slave ) */
#define DEF_PORT_DMAC0_CH0_ADDRESS   0x0000  /* Channel 0 base address register */
#define DEF_PORT_DMAC0_CH0_COUNTER   0x0001  /* Channel 0 base count register   */
#define DEF_PORT_DMAC0_CH1_ADDRESS   0x0002  /* Channel 1 base address register */
#define DEF_PORT_DMAC0_CH1_COUNTER   0x0003  /* Channel 1 base count register   */
#define DEF_PORT_DMAC0_CH2_ADDRESS   0x0004  /* Channel 2 base address register */
#define DEF_PORT_DMAC0_CH2_COUNTER   0x0005  /* Channel 2 base count register   */
#define DEF_PORT_DMAC0_CH3_ADDRESS   0x0006  /* Channel 3 base address register */
#define DEF_PORT_DMAC0_CH3_COUNTER   0x0007  /* Channel 3 base count register   */

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    Description :8237 High Performance Programmable DMA Controllerr
                 ( DMAC 1 ) 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==================================================================================

    ISA DMAC Channel Ports ( Channel Register )

    DMAC 1

==================================================================================
*/
/* DMAC1 Ports( Master ) */
#define DEF_PORT_DMAC1_CH4_ADDRESS   0x00C0  /* Channel 4 base address register */
#define DEF_PORT_DMAC1_CH4_COUNTER   0x00C2  /* Channel 4 base count register   */
#define DEF_PORT_DMAC1_CH5_ADDRESS   0x00C4  /* Channel 5 base address register */
#define DEF_PORT_DMAC1_CH5_COUNTER   0x00C6  /* Channel 5 base count register   */
#define DEF_PORT_DMAC1_CH6_ADDRESS   0x00C8  /* Channel 6 base address register */
#define DEF_PORT_DMAC1_CH6_COUNTER   0x00CA  /* Channel 6 base count register   */
#define DEF_PORT_DMAC1_CH7_ADDRESS   0x00CC  /* Channel 7 base address register */
#define DEF_PORT_DMAC1_CH7_COUNTER   0x00CE  /* Channel 7 base count register   */



このレジスターはDMA転送の核となる転送元/先アドレスと転送するバイト数を設定するレジスターです。

設定するアドレスはページングで使用する仮想アドレス ではなく物理アドレスであることに注意してください)。



指定のチャンネルのベースアドレスレジスターに指定のDMA転送アドレスを設定する関数例です。

(ここで使用しているoutPortByte関数は 「割り込みその3」 で作成しました)。


#define DEF_DMA_OK      0
#define DEF_DMA_ERROR   (-1)

/*
==================================================================================

    Description :Channel of ISA DMA

==================================================================================
*/
typedef enum
{
    E_DMA_CHANNEL0 = 0,
    E_DMA_CHANNEL1 = 1,
    E_DMA_CHANNEL2 = 2,
    E_DMA_CHANNEL3 = 3,
    E_DMA_CHANNEL4 = 4,
    E_DMA_CHANNEL5 = 5,
    E_DMA_CHANNEL6 = 6,
    E_DMA_CHANNEL7 = 7
}E_DMA_CHANNEL;

/*
==============================================================================
    Funtion     :dmaSetAddress
    Input       :E_DMA_CHANNEL channel
                 < dma channel >
                 unsigned short address
                 < dma transfer start address >
    Output      :void
    Return      :STATUS

    Description :set dma transfer start address to address register
==============================================================================
*/
PRIVATE STATUS
dmaSetAddress( E_DMA_CHANNEL channel, unsigned short address )
{
    unsigned short port_address;

    if( E_DMA_CH7 < channel )
    {
        return( DEF_DMA_ERROR );
    }

    switch( channel )
    {
    case E_DMA_CHANNEL0:
        port_address = DEF_PORT_DMAC0_CH0_ADDRESS;
        break;
    case E_DMA_CHANNEL1:
        port_address = DEF_PORT_DMAC0_CH1_ADDRESS;
        break;
    case E_DMA_CHANNEL2:
        port_address = DEF_PORT_DMAC0_CH2_ADDRESS;
        break;
    case E_DMA_CHANNEL3:
        port_address = DEF_PORT_DMAC0_CH3_ADDRESS;
        break;
    case E_DMA_CHANNEL4:
        port_address = DEF_PORT_DMAC1_CH4_ADDRESS;
        break;
    case E_DMA_CHANNEL5:
        port_address = DEF_PORT_DMAC1_CH5_ADDRESS;
        break;
    case E_DMA_CHANNEL6:
        port_address = DEF_PORT_DMAC1_CH6_ADDRESS;
        break;
    case E_DMA_CHANNEL7:
        port_address = DEF_PORT_DMAC1_CH7_ADDRESS;
        break;
    default:
        return( DEF_DMA_ERROR );
        break;
    }

    outPortByte( port_address,   ( 0x00FF ) & address        );
    outPortByte( port_address, ( ( 0xFF00 ) & address ) >> 8 );

    return( DEF_DMA_OK );
}



同様にDMA転送するデータのデータ長をベースカウントレジスターに設定する関数例を見てみます。

16ビットレジスターですので、2バイト連続で書き込みます


/*
==============================================================================
    Funtion     :dmaSetCounter
    Input       :E_DMA_CHANNEL channel
                 < dma channel >
                 unsigned short count
                 < dma transfer data length >
    Output      :void
    Return      :STATUS

    Description :set dma transfer data length to base count register
==============================================================================
*/
PRIVATE STATUS
dmaSetCounter( E_DMA_CHANNEL channel, unsigned short count )
{
    unsigned short port_address;

    if( E_DMA_CH7 < channel )
    {
        return( DEF_DMA_ERROR );
    }

    switch( channel )
    {
    case E_DMA_CHANNEL0:
        port_address = DEF_PORT_DMAC0_CH0_COUNTER;
        break;
    case E_DMA_CHANNEL1:
        port_address = DEF_PORT_DMAC0_CH1_COUNTER;
        break;
    case E_DMA_CHANNEL2:
        port_address = DEF_PORT_DMAC0_CH2_COUNTER;
        break;
    case E_DMA_CHANNEL3:
        port_address = DEF_PORT_DMAC0_CH3_COUNTER;
        break;
    case E_DMA_CHANNEL4:
        port_address = DEF_PORT_DMAC1_CH4_COUNTER;
        break;
    case E_DMA_CHANNEL5:
        port_address = DEF_PORT_DMAC1_CH5_COUNTER;
        break;
    case E_DMA_CHANNEL6:
        port_address = DEF_PORT_DMAC1_CH6_COUNTER;
        break;
    case E_DMA_CHANNEL7:
        port_address = DEF_PORT_DMAC1_CH7_COUNTER;
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    outPortByte( port_address,   ( 0x00FF ) & count        );
    outPortByte( port_address, ( ( 0xFF00 ) & count ) >> 8 );

    return( DEF_DMA_OK );
}



例えばフロッピーディスクからデータを転送する場合には、この2つのレジスターに対して、DMA転送アドレスと

転送データ長を設定してから、フロッピーディスクコントローラにREADコマンドを発行します

(手順についてはREAD DATAコマンドに少し記述していました。)

カレントアドレスレジスターとカレントカウントレジスター

ベースアドレスレジスターにはDMA転送の開始アドレスが、ベースカウントレジスターにはDMA転送データの

データ長が格納されていました。一方で、DMA転送中にはどのアドレスまで転送したのか、どのデータまで

転送したのかを憶えておかなければ転送できません。そこでICの内部バッファにどのアドレスまで転送したのか

という情報をカレントアドレスレジスターに、どのデータまでを転送したのかという情報をカレントカウントレジスターに

持っています。カレントアドレスレジスターは1バイト転送するごとに1ずつ増え、カレントカウントレジスターは

1バイト転送する毎に1減っていきます。初期値はベースアドレスレジスターの値とベースカウントレジスターの値

が設定されます。(16ビットのレジスターです)



ベースアドレスレジスター、カレントアドレスレジスターは16ビットのレジスターですので、その最大値は

0xFFFF(10進数で65535)となります。ですので、DMA転送できる最大の物理アドレスは64KBまで

となります。物理アドレスで64KBまでのアドレス転送となります。



しかし、どうしても64KB以上のアドレスでDMA転送したい、といった場合には

64KBまでの制限を少し取り払うことができます。拡張ページアドレスレジスターを使用します

拡張ページアドレスレジスター

拡張ページはアドレスレジスターの64KBを1ページとして数えます。拡張ページアドレスレジスターは8ビットの

レジスターとなりますので、全部で256個のページがあります。ですので、拡張ページレジスターとアドレスレジスターで

アクセスできるメモリーアドレスは64KB(0xFFFF)×256ページ(0xFF)=16320KB(0xFEFF01)となり約16MBまで

アクセスできるようになります。(つまり、アドレスとしては0xFFFFFFまでアクセスできるようになります)

参考までに次の図は拡張ページとアドレスレジスターによる表現可能なメモリーアドレスとなります

拡張ページアドレスレジスターによるメモリーアドレス表現

このように拡張ページと(ベースおよびカレント)アドレスレジスターで24ビットのアドレスまで表現できます。

拡張ページアドレスレジスター詳細

昔のパソコンにはDMAコントローラは1つのみ搭載されていてポートアドレスも異なります。

それ以後のAT/EISA/MCAおよび現在のパソコンではDMAコントローラは2つ搭載されビット数も増えています

昔のパソコンの拡張ページアドレスレジスターは4ビット(メモリーアドレスバスのA16-A19を使用)ですが

AT以降の現在のパソコンでは8ビット(メモリーアドレスバスのA16-A23を使用)です

それでは拡張ページアドレスレジスターのポートアドレスを見て行きましょう

ISA DMA拡張ページアドレスレジスター
ポートアドレス 昔のパソコン AT以降の現在のパソコン
0x080 チャンネル0 拡張/診断ポート
0x081 チャンネル1 チャンネル2
0x082 チャンネル2 チャンネル3
0x083 チャンネル3 チャンネル1
0x084 その他 その他
0x085 その他 その他
0x086 その他 その他
0x087 その他 チャンネル0
0x088 その他 その他
0x089 その他 チャンネル6
0x08A その他 チャンネル7
0x08B その他 チャンネル5
0x08C その他 その他
0x08D その他 その他
0x08E その他 その他
0x08F その他 チャンネル4/DRAMリフレッシュ/スレーブ接続


64KBを超える物理アドレスに転送したいときには拡張ページアドレスレジスターを使用します

64KB以内の物理アドレスの転送では使用しなくてもかまいません。(0x00を書き込んでおきます)

例えばレジスターに1を書き込むとページ1(64KB以降)を転送アドレスとして使用し、レジスターに2を

書き込むとページ2(128KB以降)を転送アドレスとして使用します。以降同様にページ255(64KB×255=16MB)まで

使用できます。

それでは、実装例を見ていきます

拡張ページアドレスレジスターの実装

まずは拡張ページアドレスレジスターのポートアドレスを定義していきます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    Description :8237 High Performance Programmable DMA Controllerr
                 ISA DMAC Extended Page Address Registers

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
#define DEF_PORT_DMAC_PAGE_CH0_ORG  0x0080  /* channel 0 ( for original pc )    */
#define DEF_PORT_DMAC_DIAG          0x0080  /* extra/diagnostic port            */
#define DEF_PORT_DMAC_PAGE_CH1_ORG  0x0081  /* channel 1 ( for original pc )    */
#define DEF_PORT_DMAC_PAGE_CH2      0x0081  /* channel 2 ( for at )             */
#define DEF_PORT_DMAC_PAGE_CH2_ORG  0x0082  /* channel 2 ( for original pc )    */
#define DEF_PORT_DMAC_PAGE_CH3      0x0082  /* channel 3 ( for at )             */
#define DEF_PORT_DMAC_PAGE_CH3_ORG  0x0083  /* channel 3 ( for original pc )    */
#define DEF_PORT_DMAC_PAGE_CH1      0x0083  /* channel 1 ( for at )             */
#define DEF_PORT_DMAC_EXTRA1        0x0084  /* reserved                         */
#define DEF_PORT_DMAC_EXTRA2        0x0085  /* reserved                         */
#define DEF_PORT_DMAC_EXTRA3        0x0086  /* reserved                         */
#define DEF_PORT_DMAC_PAGE_CH0      0x0087  /* channel 0 ( for at )             */
#define DEF_PORT_DMAC_EXTRA4        0x0088  /* reserved                         */
#define DEF_PORT_DMAC_PAGE_CH6      0x0089  /* channel 6 ( for at )             */
#define DEF_PORT_DMAC_PAGE_CH7      0x008A  /* channel 7 ( for at )             */
#define DEF_PORT_DMAC_PAGE_CH5      0x008B  /* channel 8 ( for at )             */
#define DEF_PORT_DMAC_EXTRA5        0x008C  /* reserved                         */
#define DEF_PORT_DMAC_EXTRA6        0x008D  /* reserved                         */
#define DEF_PORT_DMAC_EXTRA7        0x008E  /* reserved                         */
#define DEF_PORT_DMAC_PAGE_CH4      0x008F  /* channel 4 ( for at ) / to slave  */



定義したレジスターにページアドレスを書き込む関数を作成します。


/*
==============================================================================
    Funtion     :dmaSetPageAddress
    Input       :E_DMA_CHANNEL channel
                 < dma channel >
                 unsigned char page
                 < page number >
    Output      :void
    Return      :STATUS

    Description :set page number to extended page address register
==============================================================================
*/
PRIVATE STATUS
dmaSetPageAddress( E_DMA_CHANNEL channel, unsigned char page )
{
    unsigned short port_address;

    if( E_DMA_CH7 < channel )
    {
        return( DEF_DMA_ERROR );
    }

    switch( channel )
    {
    case E_DMA_CHANNEL0:
        port_address = DEF_PORT_DMAC_PAGE_CH0;
        break;
    case E_DMA_CHANNEL1:
        port_address = DEF_PORT_DMAC_PAGE_CH1;
        break;
    case E_DMA_CHANNEL2:
        port_address = DEF_PORT_DMAC_PAGE_CH2;
        break;
    case E_DMA_CHANNEL3:
        port_address = DEF_PORT_DMAC_PAGE_CH3;
        break;
    case E_DMA_CHANNEL4:
        /* exclusive for the connection to the slave */
        return( DEF_DMA_OK );
        break;
    case E_DMA_CHANNEL5:
        port_address = DEF_PORT_DMAC_PAGE_CH5;
        break;
    case E_DMA_CHANNEL6:
        port_address = DEF_PORT_DMAC_PAGE_CH6;
        break;
    case E_DMA_CHANNEL7:
        port_address = DEF_PORT_DMAC_PAGE_CH7;
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    outPortByte( port_address, page );

    return( DEF_DMA_OK );
}



制御レジスター詳細

最初のほうで紹介いたしました制御レジスター一覧 のステータスレジスター、コマンドレジスター、

リクエストレジスター、シングルチャンネルレジスター、モードレジスター、

クリアーバイトポインターレジスター、テンポラリーレジスター、マスタークリアーレジスター、

マスクリセットレジスター、オールマスクレジスターについて詳細を見ていきます

コマンドレジスター

コマンドレジスターのビットアサインを見ていきます

DMCコントローラのコマンドレジスター


DMAコントローラのコマンドレジスター
ビット シンボル 名称 説明
0 NMT メモリー間転送有効 0:メモリーからメモリー間転送を無効にします
1:メモリーからメモリー間転送を有効にします
1 ADHE チャンネル0アドレス保持有効 0:チャンネル0のアドレス保持を無効にします
1:チャンネル0のアドレス保持を有効にします
(ビット0のNMTビットが0の場合はこのビットは機能しません)
2 COND コントローラ無効 0:コントローラを有効にします
1:コントローラを無効にします
3 COMP タイミング 0:通常のサイクルタイミングで転送します
1:圧縮サイクルタイミングで転送します
(圧縮サイクルタイミングを使用すると転送速度が速くなりますが、
パソコンでは互換性を確保するため、通常使用できません)
4 PRIO 優先度変更 0:固定優先度
1:ローテート優先度
(このビットを1にすることで優先度をローテーションすることができますが、
パソコンでは互換性を確保するため、通常使用できません)
5 EXTW 拡張ライト選択 0:遅延ライト選択(通常)
1:拡張ライト選択
(ビット3のCONDビットが1の場合はこのビットは無視されます
また、このビットを1にすることで拡張ライト選択が使用できますが、
パソコンでは互換性確保のため、使用できません)
6 DRQP DREQ入力 0:DREQピンがHighの場合にDMA転送要求を受付ます
1:DREQピンがLowの場合にDMA転送要求を受付ます
7 DACKP DACK出力 0:DACKをLowで出力します
1:DACKをHighで出力します


コマンドレジスターで色々制御できそうですが、x86アーキテクチャではあまり動きません。

DMAコントローラを有効/無効に設定するビット2のCONDビットのみ動作します。

ビット0のMMTビットを設定することでメモリーからメモリー間転送ができる仕様にはなっております

このICは1981年にIBMが仕様化しましたが、当時すでにフレームバッファについての構想が

あったようで、その名残りとなります。とりあえずここでは定義はしておきます。


/*********************************************************************************
 File:dma.h
 Description:definition for DMA Controller

*********************************************************************************/
#ifndef __DMA_CONTROLLER__H
#define __DMA_CONTROLLER__H

/*
==================================================================================

    Command Register structure 

    < bit number >      < discription >
        0               0:Memory to memory disable
                        1:Memory to memory enable
        1               0:Channel 0 address hold disable
                        1:Channel 0 address hold enable
                          ( if bit 0 == 0 : don't care )
        2               0:Controller enable
                        1:Controller disable
        3               0:Normal timing
                        1:Compressed timing
                          ( if bit 0 == 1 : don't care )
        4               0:Fixed priority
                        1:Rotating priority
        5               0:late write selection
                        1:Extended write selection
        6               0:DREQ sense active high
                        1:DREQ sense active low
        7               0:DACK sense active low
                        1:DACK sense active high

        x                only bit 2 is used for AT

==================================================================================
*/
#define DEF_DMAC_COMMAND_REG_MMT    0x01
#define DEF_DMAC_COMMAND_REG_ADHE   0x02
#define DEF_DMAC_COMMAND_REG_COND   0x04
#define DEF_DMAC_COMMAND_REG_COMP   0x08
#define DEF_DMAC_COMMAND_REG_PRIO   0x10
#define DEF_DMAC_COMMAND_REG_EXTW   0x20
#define DEF_DMAC_COMMAND_REG_DRQP   0x40
#define	DEF_DMAC_COMMAND_REG_DACKP  0x80

#endif /*__DMA_CONTROLLER__H */



モードレジスター

モードレジスターはDMAコントローラを制御する上で重要なレジスターとなります

モードレジスターのビットアサインを見ていきます

DMCコントローラのモードレジスター


DMCコントローラのモードレジスター
ビット シンボル 名称 説明
0-1 SEL0-SEL1 チャンネル選択 チャンネルを選択します
モードレジスターのSELビット
選択チャンネル
00b チャンネル0
01b チャンネル1
10b チャンネル2
11b チャンネル3
2-3 TRA0-TRA1 転送タイプ DMA転送時の動作を設定します
モードレジスターのTRAビット
選択チャンネル
00b 転送データ検査
(セルフテストに使用します)
01b 書き込み転送
(メモリーへの書き込み)
10b 読み込み転送
(メモリーからの読み込み)
11b 無効
xxb ビット6-7のMOD0-1ビットの値が11bに設定されているとこのビットは無視されます
4 AUTO 自動初期化 0:DMA転送完了後の自動初期化を無効にします
1:DMA転送完了後の自動初期化を有効にします
1を設定した場合DMA転送完了後にカレントアドレスレジスターとカレントカレントレジスターを
ベースアドレスレジスターとベースカウントレジスターの値に初期化します。フロッピーディスクの
トラックデータ転送時に設定しておくと次のトラックのDMA転送が直ぐにできますので便利です。
(サウンドカードの中には自動初期化に対応していないカードがありますので注意します。
サウンドブラスター2.0以降は対応しています)
5 DOWN 転送アドレスデクリメント 0:1回の転送毎に転送アドレスをインクリメントします
1:1回の転送毎に転送アドレスをデクリメントします
6-7 MOD0-MOD1 DMA転送モード選択 DMA転送モードを選択します
モードレジスターのMODビット
選択チャンネル
00b DMA転送要求時にDMA転送を開始します
01b シングル転送を行います
10b ブロック転送を行います
11b カスケードモードで動作します


DMA転送をする前にモードレジスターでDMA転送方法を設定します。このレジスターでどのチャンネルで、メモリーからの

読み込むのか、書き込むのかを設定しますので、重要なレジスターとなります。

DMA転送モードについて

DMA転送モードには上記表の通り4つのモードがあります。
シングル転送モード
シングル転送モードでは1回の転送(最大64KBのデータ)のみ行います。1回の転送サイズはデバイスが決めます

(DREQをアクティブから非アクティブの状態にします)。また、カレントカウントレジスターの値が0になると

TCピンをアクティブにし、自動初期化が有効な場合は初期化を行います。フロッピーディスクのデータ転送には

このモードを使用します。

ブロック転送モード
ブロック転送モードではカレントカレントレジスターがの値が0になるか、EOPピンのアクティブ入力が入るまで

DMA転送を行います。ブロック転送完了後、自動初期化が有効な場合は初期化を行います。

DMA転送要求時にDMA転送するモード
このモードではDMAコントローラのTCピンがアクティブなる、EOPピンのアクティブ入力が入る、DREQピンが

非アクティブになるまでDMA転送を行います。これにより、デバイスのデータ容量の最大値になるまで一気に

転送することができます。

カスケードモード
このモードはスレーブのチャンネル4に使用します。

それでは、各ビットの定義をしていきます

モードレジスターのビット定義

モードレジスターの各ビットとマスクビットを定義します


/*
===============================================================================

    Mode Register structure 

    < bit number >      < discription >
        0-1             00:Channel 0 select
                        01:Channel 1 select
                        10:Channel 2 select
                        11:Channel 3 select
        2-3             00:Verify transfer
                        01:Write transfer
                        10:Read transfer
                        11:Illegal
                           ( if bits 6-7 = 11 : don't care )
        4               0:Auto initialization disable
                        1:Auto initialization enable
        5               0:Address Increment select
                        1:Address decrement select
        6-7             00:Demand mode select
                        01:Single mode select
                        10:Block mode select
                        11:Cascade mode select

===============================================================================
*/
/* channel select           */
#define DEF_DMAC_MODE_MASK_SEL      0x03
#define DEF_DMAC_MODE_SEL_CH0       0x00
#define DEF_DMAC_MODE_SEL_CH1       0x01
#define DEF_DMAC_MODE_SEL_CH2       0x02
#define DEF_DMAC_MODE_SEL_CH3       0x03

/* transfer type            */
#define DEF_DMAC_MODE_MASK_TRA      0x0C
#define DEF_DMAC_MODE_TRA_VERIFY    0x00
#define DEF_DMAC_MODE_TRA_WRITE     0x04
#define DEF_DMAC_MODE_TRA_READ      0x08
#define DEF_DMAC_MODE_TRA_ILLEGAL   0x0C

/* automatic initialization */
#define DEF_DMAC_MODE_AUTO_DISABLE  0x00
#define DEF_DMAC_MODE_AUTO_ENABLE   0x10

/* address increment        */
#define DEF_DMAC_MODE_DOWN_DEC      0x00
#define DEF_DMAC_MODE_DOWN_INC      0x20

/* transfer mode            */
#define DEF_DMAC_MODE_MASK_MOD      0xC0
#define DEF_DMAC_MODE_MOD_ONDEMAND  0x00
#define DEF_DMAC_MODE_MOD_SINGLE    0x40
#define DEF_DMAC_MODE_MOD_BLOCK     0x80
#define DEF_DMAC_MODE_MOD_CASCADE   0xC0



モードレジスターを制御する関数の実装

モードレジスターを制御する関数例を見ていきます。

まずはモードレジスターに設定したい値を書き込む関数です。


/*
==================================================================================
	Funtion     :dmaSetModeReg
    Input       :E_DMA_CHANNEL channel
                 < dma channel >
                 unsigned char mode
                 < mode value to set to mode register >

    Output      :void
    Return      :STATUS

    Description :set value to mode register
==================================================================================
*/
PRIVATE STATUS
dmaSetModeReg( E_DMA_CHANNEL channel, unsigned char mode )
{
    unsigned short port_address;

    if( E_DMA_CH7 < channel )
    {
        return( DEF_DMA_ERROR );
    }
    
    switch( channel )
    {
    /*--------------------------------------------------------------------------*/
    /* for slave                                                                */
    /*--------------------------------------------------------------------------*/
    case E_DMA_CHANNEL0:
    case E_DMA_CHANNEL1:
    case E_DMA_CHANNEL2:
    case E_DMA_CHANNEL3:
        port_address = DEF_PORT_DMAC0_MODE;
        break;
    /*--------------------------------------------------------------------------*/
    /* for master                                                               */
    /*--------------------------------------------------------------------------*/
    case E_DMA_CHANNEL4:
    case E_DMA_CHANNEL5:
    case E_DMA_CHANNEL6:
    case E_DMA_CHANNEL7:
        port_address = DEF_PORT_DMAC1_MODE;
        /* adjust channel value to mode register bit */
        channel = ( E_DMA_CHANNEL )( channel - E_DMA_CHANNEL4 );
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    /*--------------------------------------------------------------------------*/
    /* write value to mode register                                             */
    /*--------------------------------------------------------------------------*/
    mode = mode | channel;
    outPortByte( port_address, mode );
    
    return( DEF_DMA_OK );
}



指定されたチャンネルによってマスターに書き込むかスレーブに書き込むかを判断します

そして、メモリーからデータを読み込んでデバイスに転送するモードを設定する関数です


/*
==================================================================================
    Funtion     :dmaSetReadTransferMode
    Input       :E_DMA_CHANNEL channel
                 < dma channel >

    Output      :void
    Return      :STATUS

    Description :set dmac to a read transfer mode
==================================================================================
*/
PRIVATE INLINE STATUS
dmaSetReadTransferMode( E_DMA_CHANNEL channel )
{
    unsigned char mode;
    
    mode = DEF_DMAC_MODE_TRA_READ   |
           DEF_DMAC_MODE_MOD_SINGLE |
           DEF_DMAC_MODE_AUTO_ENABLE;

    return( dmaSetModeReg( channel, mode ) );
}



次にデバイスからデータを読み込んでメモリーに書き込み転送するモードを設定する関数を見てみます

フロッピーディスクからデータを読み込む場合に使用します


/*
==================================================================================
	Funtion     :dmaSetWriteTransferMode
    Input       :E_DMA_CHANNEL channel
                 < dma channel >

    Output      :void
    Return      :STATUS

    Description :set dmac to a write transfer mode
==================================================================================
*/
PRIVATE INLINE STATUS
dmaSetReadTransferMode( E_DMA_CHANNEL channel )
{
    unsigned char mode;
    
    mode = DEF_DMAC_MODE_TRA_WRITE  |
           DEF_DMAC_MODE_MOD_SINGLE |
           DEF_DMAC_MODE_AUTO_ENABLE;

    return( dmaSetModeReg( channel, mode ) );
}



ここでAUTO(自動初期化)ビットをセットしていますが、エミュレータまたはシステムによっては

自動初期化ができない場合がありますので、転送開始時には常に転送アドレスと転送バイト数を設定して

おくほうが安全です。

リクエストレジスター

リクエストレジスターは選択したチャンネルのDREQピンを操作することができます。

このレジスターはメモリーからメモリー間転送に使用します。各チャンネルを独立に制御できます

(パソコンではISA DMAはメモリーからメモリーへの転送はできませんので、このレジスターを操作する必要はありません)

DMCコントローラのリクエストレジスター


DMCコントローラのリクエストレジスター
ビット シンボル 名称 説明
0-1 SEL0-SEL1 チャンネル選択 チャンネルを選択します
モードレジスターのSELビット
選択チャンネル
00b チャンネル0
01b チャンネル1
10b チャンネル2
11b チャンネル3
2 REQ リクエスト 0:選択したチャンネルのDREQを無効にします
1:選択したチャンネルのDREQをアクティブにします


シングルチャンネルマスクレジスター

シングルチャンネルマスクレジスターは選択したチャンネル無効(マスク)したり有効(マスク解除)したりすることが

できます。マスクするにはビット2に1を指定します。選択チャンネルをマスクするとEOPピンの出力してデバイスに

DMA転送が完了したことを通知します(自動初期化を設定していない場合)。各チャンネルは独立にマスクしたり

マスク解除してりできます。

DMCコントローラのシングルチャンネルマスクレジスター


DMCコントローラのシングルチャンネルマスクレジスター
ビット シンボル 名称 説明
0-1 SEL0-SEL1 チャンネル選択 チャンネルを選択します
モードレジスターのSELビット
選択チャンネル
00b チャンネル0
01b チャンネル1
10b チャンネル2
11b チャンネル3
2 MASK マスク 0:選択したチャンネルのマスクを解除します
1:選択したチャンネルをマスクします


オールマスクレジスター

各チャンネルのマスクはオールマスクレジスターでも可能です。オールマスクレジスターは各チャンネルを同時に

マスクしたりマスク解除したりすることができます。IBM仕様ではこのレジスターの読み込みは定義されていませんが

実際にはレジスターの値を読み出すことができます。レジスターの値を読み出すことで現在マスクされている

チャンネルがわかります。(マスターのチャンネル4をマスクするとチャンネル5、6、7もマスクされます

DMCコントローラのオールマスクレジスター


DMCコントローラのオールマスクレジスター
ビット シンボル 名称 説明
0 CH0 チャンネル0マスク 0:チャンネル0のマスクを解除します
1:チャンネル0をマスクします
1 CH1 チャンネル1マスク 0:チャンネル1のマスクを解除します
1:チャンネル1をマスクします
2 CH2 チャンネル2マスク 0:チャンネル2のマスクを解除します
1:チャンネル2をマスクします
3 CH3 チャンネル3マスク 0:チャンネル3のマスクを解除します
1:チャンネル3をマスクします


オールマスクレジスターの実装

オールマスクマスクレジスターを制御する実装例について見ていきます

まずは該当チャンネルをマスク(無効)にする関数例です


/*
==================================================================================
	Funtion     :dmaMaskChannel
    Input       :E_DMA_CHANNEL channel
                 < dma channel to mask >

    Output      :void
    Return      :void

    Description :set mask bit of designated channel
==================================================================================
*/
PRIVATE INLIN VOID
dmaMaskChannel( E_DMA_CHANNEL channel )
{
    unsigned char  bit_ch;
    unsigned short port_address;

    /*--------------------------------------------------------------------------*/
    /* read current mask bit                                                    */
    /*--------------------------------------------------------------------------*/
    if( E_DMA_CHANNEL3 < channel )
    {
        bit_ch       = inPortByte( DEF_PORT_DMAC0_ALL_MASK );
        /* adjust mask bit for all mask register */
        channel      = channel - E_DMA_CHANNEL4;
        port_address = DEF_PORT_DMAC0_ALL_MASK
    }
    else
    {
        bit_ch       = inPortByte( DEF_PORT_DMAC1_ALL_MASK );
        port_address = DEF_PORT_DMAC1_ALL_MASK;
    }

    /*--------------------------------------------------------------------------*/
    /* set designated channel bit                                               */
    /*--------------------------------------------------------------------------*/
    bit_ch = bit_ch | ( 1 << channel );

    outPortByte( port_address, bit_ch );
}



この実装例では1回の関数呼び出しで1つのチャンネルのみマスクするようになっています

このレジスターの特徴として複数のチャンネルを同時にマスクできますので、そのような関数を作成されるのも

面白いと思います。



(オールマスクレジスターの値が読み込みできない場合はグローバル変数でマスク状態を持っておくなどの

対処が必要です。)



続いてマスク解除する実装例となります。この例も1回の呼び出しで1つのチャンネルについて

マスク解除する例となります


/*
==================================================================================
	Funtion     :dmaUnmaskChannel
    Input       :E_DMA_CHANNEL channel
                 < dma channel to unmask >

    Output      :void
    Return      :void

    Description :set unmask bit of designated channel
==================================================================================
*/
PRIVATE INLIN VOID
dmaUnmaskChannel( E_DMA_CHANNEL channel )
{
    unsigned char  bit_ch;
    unsigned short port_address;

    /*--------------------------------------------------------------------------*/
    /* read current mask bit                                                    */
    /*--------------------------------------------------------------------------*/
    if( E_DMA_CHANNEL3 < channel )
    {
        bit_ch       = inPortByte( DEF_PORT_DMAC0_ALL_MASK );
        /* adjust mask bit for all mask register */
        channel      = channel - E_DMA_CHANNEL4;
        port_address = DEF_PORT_DMAC0_ALL_MASK
    }
    else
    {
        bit_ch       = inPortByte( DEF_PORT_DMAC1_ALL_MASK );
        port_address = DEF_PORT_DMAC1_ALL_MASK;
    }

    /*--------------------------------------------------------------------------*/
    /* set designated channel bit                                               */
    /*--------------------------------------------------------------------------*/
    bit_ch = bit_ch & ~( 1 << channel );

    outPortByte( port_address, bit_ch );
}



ステータスレジスター

ステータスレジスターは各チャンネルのDMA転送が完了しているかどうかとDMAリクエスト中かどうかの

情報がビットで格納されています。ステータスレジスターはIRQ(DMA転送要求したデバイスが発生させます。

フロッピーディスクの場合は割り込みその3で見てきましたようにIRQ6でした。)

を使用しない場合、つまりポーリングでDMA転送を監視する場合に使用します。IRQを使用する場合は、

DMA転送が完了したときにDMA転送を要求したデバイスがIRQ発生しますので、特に監視する必要はありません

DMCコントローラのステータスレジスター


DMCコントローラのステータスレジスター
ビット シンボル 名称 説明
0 TC0 チャンネル0 DMA転送完了 0:チャンネル0のDMA転送は完了していません
1:チャンネル0のDMA転送は完了しました
1 TC1 チャンネル1 DMA転送完了 0:チャンネル1のDMA転送は完了していません
1:チャンネル1のDMA転送は完了しました
2 TC2 チャンネル2 DMA転送完了 0:チャンネル2のDMA転送は完了していません
1:チャンネル2のDMA転送は完了しました
3 TC0 チャンネル3 DMA転送完了 0:チャンネル3のDMA転送は完了していません
1:チャンネル3のDMA転送は完了しました
4 CH0 チャンネル0 DMAリクエスト保留中 0:チャンネル0はデバイスからのDMA要求を保留していません
1:チャンネル0はデバイスからのDMA要求を保留しています
5 CH1 チャンネル1 DMAリクエスト保留中 0:チャンネル1はデバイスからのDMA要求を保留していません
1:チャンネル1はデバイスからのDMA要求を保留しています
6 CH2 チャンネル2 DMAリクエスト保留中 0:チャンネル2はデバイスからのDMA要求を保留していません
1:チャンネル2はデバイスからのDMA要求を保留しています
7 CH3 チャンネル3 DMAリクエスト保留中 0:チャンネル3はデバイスからのDMA要求を保留していません
1:チャンネル3はデバイスからのDMA要求を保留しています


ISA DMAコマンドレジスター

レジスター一覧に載っているレジスターでまだ詳細を見ていないレジスターがあります。

クリアーバイトポインタレジスター、テンポラリーレジスター、マスタークリアーレジスター、マスクリセットレジスターです

これらのレジスターは特殊なレジスターでとなります



各レジスターでICの”状態”をリセットすることができます

それでは詳細を見ていきます

クリアーバイトポインタレジスター(フリップフロップリセットレジスター)

クリアーバイトポインタレジスターはフリップフロップの状態をリセット(Low)にします

マスターDMAコントローラにデータを書き込む時に制御する重要なレジスターとなります

マスターDMAコントローラは8ビットで動作するようにハードウェア上設計されておりますが、

IC自体は16ビットのICとなります。CPUから通常8ビットのOUT命令でDMAコントローラにデータを書き込み

ますが、ICは16ビットなので、OUT命令を2回発行して8ビットデータを2回連続書き込みます

DMAコントローラは最初の8ビットを受け取るとADSTBピンをHighにしから、次の8ビットデータを受け取ります

このようにして、16ビットのデータを8ビット×2回で受け取るような動作となります

しかし、マスターDMAコントローラは旧PCとの互換性から8ビットで動作するように設計されております

マスターDMAコントローラ自体は16ビットですので、1回目の8ビットを受け取るとADSTBピンをHighに

してしまいます。このままでは、次の別データを書き込むためにOUT命令を発行しても、ADSTBピンが

Highですので、2回目の8ビットデータだとDMAコントローラは解釈してしまします。

そこで、マスターDMAコントローラに1回目の8ビットデータを書き込みした後は、

マスターDMAコントローラのクリアーバイトポインタレジスターを制御して、ADSTBピンを強制的に

Lowにさせることで1回目の8ビットデータ受信待ち状態に設定してから、別の8ビットデータを書き込むようにします

このような制御はスレーブDMAコントローラは16ビットで動作するので、必要ありません



クリアーバイトポインタレジスターを制御するにはレジスターになんでもよいので値を書き込むと

フリップフロップをクリアー(ADSTBピンをLow)します。

クリアーバイトポインタレジスター(フリップフロップリセットレジスター)の実装

この実装例ではクリアーバイトポインタレジスターに0xFFを書き込んでフリップフロップをリセットします


typedef enum
{
    E_DMA_CLASS_MASTER = 0x00,
    E_DMA_CLASS_SLAVE  = 0x01
}E_DMA_CLASS;

#define DEF_DMAC_CLR_DATA 0xFF

/*
==================================================================================
    Funtion     :dmaResetFlipFlop
    Input       :E_DMA_CLASS class
                 < select DMAC 0 or 1 >
    Output      :void
    Return      :STATUS

    Description :Reset flip-flop
==================================================================================
*/
STATUS dmaResetFlipFlop( E_DMA_CLASS class )
{
    unsigned char port_address;

    if( E_DMA_CLASS_SLAVE < class )
    {
        return( DEF_DMA_ERROR );
    }

    switch( class )
    {
    case E_DMA_CLASS_MASTER:
        port_address = DEF_PORT_DMAC1_CLEAR_BP;
        break;
    case E_DMA_CLASS_SLAVE:
        port_address = DEF_PORT_DMAC0_CLEAR_BP;
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    outPortByte( port_address, DEF_DMAC_CLR_DATA );

    return( DEF_DMAC_OK );
}



マスクリセットレジスター

マスクリセットレジスターになんでもよいので値を書き込むことでチャンネルのマスク状態をリセットします。

(マスク状態をリセットすると全チャンネルはマスクされた状態となります)


/*
==============================================================================
    Funtion     :dmaResetAllMask
    Input       :E_DMA_CLASS class
                 < select DMAC 0 or 1 >
    Output      :void
    Return      :STATUS

    Description :Reset all masks
==============================================================================
*/
STATUS dmaResetAllMask( E_DMA_CLASS class )
{
    BYTE port_address;

    if( E_DMA_CLASS_SLAVE < class )
    {
        return( DEF_DMA_ERROR );
    }

    switch( class )
    {
    case E_DMA_CLASS_MASTER:
        port_address = DEF_PORT_DMAC1_MASK_RESET;
        break;
    case E_DMA_CLASS_SLAVE:
        port_address = DEF_PORT_DMAC0_MASK_RESET;
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    outPortByte( port_address, DEF_DMAC_CLR_DATA );

    return( DEF_DMAC_OK );
}



マスタークリアーレジスター

マスタークリアーレジスターの場合も適当な値を書き込むことでDMAコントローラICを

クリアーすることができます。ここでのクリアーとは、フリップフロップのクリア(ADSTBをLowにする)、

ステータスをクリア、マスクをリセットすることを言います


/*
==============================================================================
    Funtion     :dmaClearMaster
    Input       :E_DMA_CLASS class
                 < select DMAC 0 or 1 >
    Output      :void
    Return      :STATUS

    Description :clear master
==============================================================================
*/
STATUS dmaClearMaster( E_DMA_CLASS class )
{
    BYTE port_address;

    if( E_DMA_CLASS_SLAVE < class )
    {
        return( DEF_DMA_ERROR );
    }

    switch( class )
    {
    case E_DMA_CLASS_MASTER:
        port_address = DEF_PORT_DMAC1_MASTER_CLEAR;
        break;
    case E_DMA_CLASS_SLAVE:
        port_address = DEF_PORT_DMAC0_MASTER_CLEAR;
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    outPortByte( port_address, DEF_DMAC_CLR_DATA );

    return( DEF_DMAC_OK );
}



DMAドライバの実装例

DMAドライバの実装例を少し見ていきます。ここでは、 フロッピーディスクのREAD DATAコマンドによる

セクター読み込み時のDMA転送を想定しております。フロッピーディスクから

LBAを使用したセクター読み込みで で DMA転送について少し触れておりますので、

もう一度その部分のみ見てみましょう

フロッピーディスクのセクター読み込み関数例の復習

フロッピーディスクのREAD DATAコマンドの実装例では最初にinitDMAforReadFDD関数で

DMAの初期化を行っていました。


PUBLIC STATUS
fddReadSector( unsigned char drive,
               unsigned int  lba,
               unsigned char *trasfered_address,
               unsigned int  data_length )
{
    unsigned char head;
    unsigned char track;
    unsigned char sector;
    STATUS        status;
    
    if( DEF_FDC_DOR_DRIVE3 < drive )
    {
        return( DEF_FDD_ERROR );
    }

    /*--------------------------------------------------------------------------*/
    /* set up DMA controller for fdd                                            */
    /*--------------------------------------------------------------------------*/
    status = initDMAforReadFDD( trasfered_address,
                                data_length - 1 );

    /*--------------------------------------------------------------------------*/
    /* start the motor of specified dirve                                       */
    /*--------------------------------------------------------------------------*/
    startFdc0Motor( drive );

    /*--------------------------------------------------------------------------*/
    /* convert lba to chs                                                       */
    /*--------------------------------------------------------------------------*/
    head   = convertLBA2Head( lba   );
    track  = convertLBA2Track( lba  );
    sector = convertLBA2Sector( lba ); 

    /*--------------------------------------------------------------------------*/
    /* seek specified track                                                     */
    /*--------------------------------------------------------------------------*/
    status = fddSeek( drive, head, track );
    
    if( status != DEF_FDD_OK )
    {
        return( status );
    }   

    /*--------------------------------------------------------------------------*/
    /* issue read data command to read a sector                                 */
    /*--------------------------------------------------------------------------*/
    status = writeFdc0CmdReadData( drive, head, track, sector );

    /*--------------------------------------------------------------------------*/
    /* wait until irq of dma is fired                                           */
    /*--------------------------------------------------------------------------*/
    dmaWaitIrq( E_DMA_CHANNEL2 );

    /*--------------------------------------------------------------------------*/
    /* stop the motor of specified dirve                                        */
    /*--------------------------------------------------------------------------*/
    stopFdc0Motor( drive );
    
    return( status );
}



このinitDMAforReadFDD関数の中身について見ていきます。

ここまで見てきまししたように、フロッピーディスクはDMAのチャンネル2を使用しますので、

E_DMA_CHANNEL2を関数の中で指定します。また、DMA転送先の物理メモリーアドレスと

転送サイズを引数で指定しています。転送アドレスはベースアドレスレジスターに、

転送サイズはベースカウントレジスターに設定します

initDMAforReadFDD関数

この関数例ではレジスターに設定する転送サイズ(unsigned shortのサイズ)のエラー処理と

64KBを超える物理アドレスを使用するときに設定する 拡張ページアドレスレジスターの処理を

省略しています



/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
    Funtion     :initDMAforReadFDD
    Input       :unsigned char *phy_adr
                 < physical address of dma transfer >
                 unsigned int length
                 < length of dma transfer >
    Output      :void
    Return      :void
    Description :initialize dma for reading Floppy Disc
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
PUBLIC INLINE STATUS
initDMAforReadFDD( unsigned char *phy_adr, unsigned int length )
{
    return( dmaInitDMA( E_DMA_TRANSFER_TYPE_WRITE,
                        E_DMA_CHANNEL2,
                        ( VOID* )phy_adr,
                        length ) );
}



ここで関数dmaInitDMA本処理を呼び出してDMAコントローラをWRITEモードにします

(DMAコントローラから見てメモリーにWRITEすることになります)

続いて関数dmaInitDMAを見てみます


/*
==================================================================================
    Funtion     :dmaInitDMAforFDD
    Input       :E_DMA_TRANSFER_TYPE type
                 < type of transfer >
                 E_DMA_CHANNEL channel
                 < dma channel >
                 unsigned char *phy_adr
                 < physical address of dma transfer >
                 unsigned int length
                 < length of dma transfer >
    Output      :void
    Return      :void
    Description :initialize dma for device
==================================================================================
*/
PRIVATE STATUS
dmaInitDMA( E_DMA_TRANSFER_TYPE type,
            E_DMA_CHANNEL       channel,
            void                *phy_adr,
            unsigned int        length )
{
    STATUS         status;
    unsigned short trans_adr;
    
    if( USHRT_MAX <  length )
    {
        return( DEF_DMA_ERROR );
    }

    /*--------------------------------------------------------------------------*/
    /* mask the channel                                                         */
    /*--------------------------------------------------------------------------*/
    status = dmaMaskChannel( channel );
    
    if( DEF_DMA_OK != status ) return DEF_DMA_ERROR;

    /*--------------------------------------------------------------------------*/
    /* clear the flip flop of dmc1                                              */
    /*--------------------------------------------------------------------------*/
    status = dmaResetFlipFlop( E_DMA_CLASS_MASTER );
    
    if( DEF_DMA_OK != status ) return DEF_DMA_ERROR;

    /*--------------------------------------------------------------------------*/
    /* set transfered address                                                   */
    /*--------------------------------------------------------------------------*/
    trans_adr = ( unsigned short )( ( unsigned int )phy_adr & DEF_DMAC_ADDRESS );
    status    = dmaSetAddress( channel, trans_adr  );
    
    if( DEF_DMA_OK != status ) return DEF_DMA_ERROR;

    /*--------------------------------------------------------------------------*/
    /* clear the flip flop of dmc1 again                                        */
    /*--------------------------------------------------------------------------*/
    status = dmaResetFlipFlop( E_DMA_CLASS_MASTER );
    
    if( DEF_DMA_OK != status ) return DEF_DMA_ERROR;

    /*--------------------------------------------------------------------------*/
    /* set the length of transfered data                                        */
    /*--------------------------------------------------------------------------*/
    status = dmaSetCounter( channel, ( unsigned short )length );
    
    if( DEF_DMA_OK != status ) return DEF_DMA_ERROR;
  
    /*--------------------------------------------------------------------------*/
    /* set transfer type                                                        */
    /*--------------------------------------------------------------------------*/
    switch( type )
    {
    case E_DMA_TRANSFER_TYPE_WRITE:
        status |= dmaSetWirteTransferMode( channel );
        break;
    case E_DMA_TRANSFER_TYPE_READ:
        status |= dmaSetReadTransferMode( channel );
        break;
    default:
        return( DEF_DMA_ERROR );
    }

    /*--------------------------------------------------------------------------*/
    /* unmask the channel                                                       */
    /*--------------------------------------------------------------------------*/
    status = dmaUnmaskChannel( channel );

    return( status );
}



この関数内では、 を行っています。

DMAコントローラのマスターの8ビットレジスターに値を書き込む場合はフリップフロップをクリアーしておきます

また、ここではモードレジスターにシングル転送モードを設定しておりますが、ICや仮想環境のDMAコントローラは

自動初期化や要求時転送機能に対応していないものもあるため、シングル転送モードにしています

自動初期化機能、要求時転送機能に対応しているICを検出した場合はこれらの機能をONにすることを

おすすめします



ここまでで、OSとして最低限のI/Oができるようになったかと思います。

本回でファイルが読み込みできるようになりました。

次回はもう少しソフトウェア側について見ていきたいと思います。

ファイルシステムと仮想ファイルシステム(VFS:Virtual File System)と見て行きたいのですが

その前に、VFSで使用するオブジェクトの割り当て/解放を管理するスラブアロケーターについて

見て行きたいと思います。。。

inserted by FC2 system