シリアルドライバを作るデバッグ用にテキストの表示などが行えると便利です。ここではシリアル通信*1のドライバを作成し、シリアル通信により文字の送受信を行うことで、QEMUのシリアル通信用の画面で文字の表示と入力された文字の取得を行ってみます*2。送信(Tx)と受信(Rx)をそれぞれ1本の信号線でやり取りする通信方式です。Windowsでは「COMポート」と呼ばれていたり、Linuxでは「シリアルポート(ttySx)」等と呼ばれているやつです。昔のPCには「D-sub9ピン」と呼ばれるコネクタが搭載されていて、「RS-232C」規格のケーブルを使ってシリアル通信を行うことができました。ここはあくまでもシリアル通信による文字の送受信なので、キーボードドライバ自体は後の章で作成します。文字を送信して画面表示それでは早速、シリアル通信による文字の送信を試してみましょう。この節のサンプルディレクトリは「031_ser_tx」です。シリアル通信を行うにはシリアル通信には専用のコントロールIC(コントローラ)があります。コントローラにもCPU同様にレジスタがあり、そのレジスタへ値を書き込んだり、逆に値を読み出したりすることで、シリアル通信でデータを送信したり受信したりできます。外部のICが持つレジスタは、基本的に「IOアドレス空間」というアドレス空間からアクセスします。これは、ポインタ等でアクセスするような通常のメモリのアドレス空間とは別で、「in」命令と「out」命令という専用の命令を使用して、そのアドレス空間へアクセスします。Register)」というレジスタで行います。IOアドレスなどについては表3.1の通りです*3。マシンには複数のシリアルポートが搭載されていたりするのですが、これは1つ目のシリアルポートのアドレスです。複数のシリアルポートを使う予定も無いので本書では常に1つ目のシリアルポートを使います。THRについてIOアドレス0x03f8レジスタサイズ1バイトレジスタサイズが1バイトなので、1文字ずつ書き込み、1文字ずつ送信します。IOアドレス空間のレジスタへデータを書き込む「out」(命令18)それでは、out命令を使って、文字をシリアル通信で送ってみます。1バイトのデータ送信を行う際のout命令の構文は以下の通りです。%dx機械語ee使用できるレジスタは固定です。今回の場合、「AL」に送信したい文字のASCIIコードを格納し、「DX」へTHRのIOアドレス(0x03f8)を格納した状態でout命令を実施することで、データ送信を行えます。8ビットレジスタと16ビットレジスタへそれぞれのビット幅の即値を格納する構文は2章で紹介しました。再掲すると以下の通りです。値(16bit)ALレジスタのreg_ofsは「0」で、DXレジスタのreg_ofsは「2」なので、ALレジスタとDXレジスタへの値格納までをsample.txtへ記述するとリスト3.1の通りです。$0x03f8,%dxここでは文字A(0x41)を送信してみることにしました。out命令を使い、ALに格納した文字をDXが指すTHRへ書き込んでみます(リスト3.2)。(追加)out命令の後にhltを使った無限ループを設置しました。動作確認kernel.binを生成し、run_qemuスクリプトで実行してみてください。シリアル送信した文字は、QEMUをGUIで起動した場合「Ctrl-Alt-3」キーでシリアル通信用の画面へ切り替えることで確認できます。CUIで起動した場合、標準でシリアル通信画面になっているのでそのまま表示されます。文字「A」が表示されれば成功です。コントローラの状態を確認して送信する前節では、コントローラの状態確認無しでTHRへ書き込みを行っていました。THRはコントローラが持つ送信バッファです。コントローラの側で送信バッファにデータを受け入れる準備ができていない時にこのバッファへ書き込みを行ってしまうと、最悪の場合、未送信のデータを上書きして消してしまう恐れがあります。シリアル通信のコントローラには「送信バッファが空になった」事を示すフラグがあるため、ここではこのフラグを確認してからTHRへ書き込むように変更してみます。この節のサンプルディレクトリは「032_ser_tx_chk_flg」です。Register(LSR)」というレジスタで確認できます。LSRについてIOアドレス0x03fdレジスタサイズ1バイトLSRは各ビットがコントローラのステータスを示しており、各ビットの意味は以下の通りです。受信バッファに1つ以上のデータがある使うのはビット5です。このビットがセットされていれば送信バッファへデータ書き込みを行って問題ありません。IOアドレス空間のレジスタ値取得「in」(命令19)それでは、LSRの値を読み出す処理を追加してみます。IOアドレス空間上のレジスタの値を取得する命令は「in」命令で、構文は表3.7の通りです。%al機械語ec試しに、単体のソースコード「read_lsr.txt」を作り、LSRの値を読み出してみます(リスト3.3)。-1リスト3.3を実行し、QEMUモニターでレジスタ値を確認すると以下の通りでした。R11=00000000e7dc4657RAXレジスタの最下位1バイト(ALレジスタ)が0x60になっています。ビット6とビット5がセットされているので、このステータス値なら、送信して問題無さそうです。フラグの待ち方「and」/「je」(命令20)新たに2つの命令を使ってビットがセットされるまで待つ処理を実装します。流れとしては以下の2ステップです。1.の演算結果が0の場合、in命令まで戻る(再度LSR取得)新たに登場した「je」命令は、直前の演算結果に応じてジャンプするか否かが決まる「条件付きジャンプ命令」の1つです。CPUでは、数値の演算を行うと、その結果に応じてCPU内の「フラグレジスタ」と呼ばれるレジスタの各ビットが変化します。条件付きジャンプ命令は、このフラグレジスタの状態に応じてジャンプするか否かが決まる命令です。Equal」の略です。関係するのはフラグレジスタの中の「ゼロフラグ」のビットで、je命令の実行時、このビットがセットされていればオペランドで示した相対アドレスへジャンプし、ビットがセットされていなければ、ジャンプはせずに次の命令へ進みます。上記の2ステップの場合、ステップ1のANDによって、LSRのビット5がセットされていた場合のみステップ2で次の命令へ進むようになり、LSRのビット5がセットされていなければLSR値を取得するin命令まで戻ることになります。je命令の構文はjmp命令と同じで以下の通りです。fe+rel_addrフラグレジスタの動きを見てみるここで、フラグレジスタの動きを見てみます。alに0を格納し、0x20とandを取るだけのコードを作ってみます(リスト3.4)。-1実行し、QEMUモニターでレジスタ値を表示させてみます。HLT=1「RFL=」に書かれているのがフラグレジスタの値です。右側の"[]"の中にどんなフラグが設定されているかが現れていて、"Z"が「ゼロフラグ」の設定を示しています。ゼロフラグは演算結果がゼロだった場合に設定されるフラグです。今回の場合、0x00と0x20のANDは0x00になるため、このフラグが設定されました。この状態で「je」命令を実行するとオペランドで指定した相対アドレスへジャンプします。フラグレジスタの算術演算に関わるビット(下位8ビット)の一覧を紹介しておきます(表3.9)。