console.c static void cgaputc(int c)
トップページ
jupiteroak.hatenablog.com
console.c
https://github.com/mit-pdos/xv6-public/blob/master/console.c#L127
#define BACKSPACE 0x100 #define CRTPORT 0x3d4 static ushort *crt = (ushort*)P2V(0xb8000); // CGA memory static void cgaputc(int c) { int pos; // Cursor position: col + 80*row. outb(CRTPORT, 14); pos = inb(CRTPORT+1) << 8; outb(CRTPORT, 15); pos |= inb(CRTPORT+1); if(c == '\n') pos += 80 - pos%80; else if(c == BACKSPACE){ if(pos > 0) --pos; } else crt[pos++] = (c&0xff) | 0x0700; // black on white if(pos < 0 || pos > 25*80) panic("pos under/overflow"); if((pos/80) >= 24){ // Scroll up. memmove(crt, crt+80, sizeof(crt[0])*23*80); pos -= 80; memset(crt+pos, 0, sizeof(crt[0])*(24*80 - pos)); } outb(CRTPORT, 14); outb(CRTPORT+1, pos>>8); outb(CRTPORT, 15); outb(CRTPORT+1, pos); crt[pos] = ' ' | 0x0700; }
cgaputc関数は、引数cで指定された文字データを使ってVGA(Video Graphics Array)による文字描写を行います。
引数 int c
描写処理で使用される文字データです。
処理の内容
文字を描写するカーソルの位置を取得する
outb(CRTPORT, 14); pos = inb(CRTPORT+1) << 8; outb(CRTPORT, 15); pos |= inb(CRTPORT+1);
outb(CRTPORT, 14)
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)を指定することにより、VGA内のCDTコントローラが持つインデックスレジスタへの書き込みを行なっています。インデックスレジスタは、データレジスタに設定されるデータの種類を指定するために使用されます。
インデックスレジスタに14(0x0e)を書き込むことで、カーソル位置を示すオフセット値の上位8bitをデータレジスタから取得できるようになります。
pos = inb(CRTPORT+1) << 8;
in命令(ポート入力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)+1を指定することにより、VGA内のCDTコントローラが持つデータレジスタの読み取りを行なっています。前の命令によって、カーソル位置を示すオフセット値の上位8bitをデータレジスタから取得できるようになっています。
データレジスタから取得した値を8ビット左シフト演算して変数posに代入することで、変数posのbit15-8にカーソル位置を示すオフセット値の上位8bitをセットしています。
outb(CRTPORT, 15);
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)を指定することにより、VGA内のCDTコントローラが持つインデックスレジスタへの書き込みを行なっています。インデックスレジスタは、データレジスタに設定されるデータの種類を指定するために使用されます。
インデックスレジスタに15(0x0f)を書き込むことで、カーソル位置を示すオフセット値の下位8bitをデータレジスタから取得できるようになります。
pos |= inb(CRTPORT+1);
in命令(ポート入力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)+1を指定することにより、VGA内のCDTコントローラが持つデータレジスタの読み取りを行なっています。前の命令によって、カーソル位置を示すオフセット値の下位8bitをデータレジスタから取得できるようになっています。
データレジスタから取得した値を変数posにビット和代入することにより、変数posのbit7-0にカーソル位置を示すオフセット値の下位8bitをセットしています。
以上の操作をもって、カーソル位置を示すオフセット値(posのbit15-0の値)の取得が完了しました。
文字を画面に描写する
if(c == '\n') pos += 80 - pos%80; else if(c == BACKSPACE){ if(pos > 0) --pos; } else crt[pos++] = (c&0xff) | 0x0700;
videoバッファと画面描写について
videoバッファは、アドレス範囲0x000a 0000~0x000b ffffのメモリ領域で、VGA内のvideoメモリにメモリマップドされています。
このvideoバッファに文字データを書き込むことで、画面に文字を描写することができます。
テキストモードにおける画面描写について
VGAはデフォルトではテキストモードで動作し、このテキストモードにおける描写範囲は80列×25行になっています。
カーソル位置を示すオフセット値は、x(列の位置を示す値、x軸方向の位置を示す値)+y(行の位置を示す値、y軸方向の位置)×80として表現され、xの値は(x+y*80)%80として、yの値は(x+y*80)/80として求めることができます。
カラーテキストモードについて
カラーテキストモード(テキストモードの一種)を利用する場合は、アドレス範囲0x000b 8000~0x000b ffffのvideoバッファに文字データを書き込みます。
カラーテキストモード(テキストモードの一種)では、1文字を描写するために2バイトのデータを使用しており、上位1バイトのattributeバッファには文字色や背景色を指定する値を設定し、下位1バイトのcharacter mapバッファにはASCIIコードの値を設定します。
文字データcが改行の特殊文字だった場合
c == '\n' が真となる場合→文字データcが改行の特殊文字だった場合は、カーソル位置を次の行へ移動させ、さらにその行における先頭列へ移動させます。
そのために、現在のカーソル位置を示すオフセット値posに、80を加算し(次の行へ移動)、pos%80を減算(先頭列へ移動)します。
文字データcがBACKSPACEだった場合
c == BACKSPACE が真となる場合→文字データcがBACKSPACEだった場合は、cの値が0より大きいことを確認してから前置デクリメントします。
文字データがその他の値だった場合
crt[pos++]によって指定されるvideoバッファ上の位置に文字データcを書き込み、文字を描写します。
文字データcを0xffでマスク処理することで、character mapバッファに設定する値(ASCIIコードの値)を取り出しています。
さらに、0x0700を論理和演算することで、attributeバッファに0x07(文字色や背景色を指定する値)を設定できるようにしています。
attributeバッファに0x07を設定すると、背景が黒・文字色が灰色で文字が描写されます。
文字を描写する位置は、ushort型へのポインタcrtとカーソル位置を示すオフセット値posを用いると、crt[pos]として表現できます。
変数posに後置インクリメント使っているので、変数posの値が評価された後にインクリメントします(crt[pos]の位置に文字を描写した後に、カーソルを一文字文だけ進めます)。
ushort型へのポインタcrtには、カラーテキストモードで利用されるvideoバッファの先頭アドレス0x000b 8000に対応する仮想アドレスが格納されています。
static ushort *crt = (ushort*)P2V(0xb8000); // CGA memory
カーソルの位置が描写範囲外にないかを確認する
if(pos < 0 || pos > 25*80) panic("pos under/overflow");
カーソル位置を示すオフセット値posが、テキストモードにおける描写範囲外にある場合は、panic関数を呼び出します。
スクロールアップの処理を行う
if((pos/80) >= 24){ memmove(crt, crt+80, sizeof(crt[0])*23*80); pos -= 80; memset(crt+pos, 0, sizeof(crt[0])*(24*80 - pos)); }
(pos/80) >= 24 が真となる場合→文字が描写されている行が24行目の場合は、スクロールアップの処理を行います。
具体的には、2行目から24行目までの範囲にある23行分の描写内容を、1行目から23行目の範囲へ移動させます。
そのために、memmove関数を呼び出して、先頭アドレスがcrt+80・サイズがsizeof(crt[0])*23*80となるメモリ領域から、先頭アドレスがcrt・サイズがsizeof(crt[0])*23*80となるメモリ領域へ、データを移動させます。
次に、カーソルの位置も1行分上へ移動させるために、カーソル位置を示すオフセット値posを80減算します。
最後に、カーソル位置から24行目までの描写範囲を初期化します。
そのために、memset関数を呼び出して、先頭アドレスがcrt+pos・サイズがsizeof(crt[0])*(24*80 - pos)となるメモリ領域を0で初期化します(黒色にします)。
文字を描写するカーソルの位置を更新する
outb(CRTPORT, 14); outb(CRTPORT+1, pos>>8); outb(CRTPORT, 15); outb(CRTPORT+1, pos); crt[pos] = ' ' | 0x0700;
outb(CRTPORT, 14);
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)を指定することにより、VGA内のCDTコントローラが持つインデックスレジスタへの書き込みを行なっています。インデックスレジスタは、データレジスタに設定されるデータの種類を指定するために使用されます。
インデックスレジスタに14(0x0e)を書き込むことで、データレジスタを使ってカーソル位置を示すオフセット値の上位8bitを更新できるようになります。
outb(CRTPORT+1, pos>>8);
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)+1を指定することにより、VGA内のCDTコントローラが持つデータレジスタへの書き込みを行なっています。前の命令によって、データレジスタを使ってカーソル位置を示すオフセット値の上位8bitを更新できるようになっています。
変数posを8ビット右シフト演算した値をデータレジスタにセットすることで、変数posのbit15-8の値をカーソル位置を示すオフセット値の上位8bitとして更新できます。
outb(CRTPORT, 15);
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)を指定することにより、VGA内のCDTコントローラが持つインデックスレジスタへの書き込みを行なっています。インデックスレジスタは、データレジスタに設定されるデータの種類を指定するために使用されます。
インデックスレジスタに15(0x0f)を書き込むことで、データレジスタを使ってカーソル位置を示すオフセット値の下位8bitを更新できるようになります。
outb(CRTPORT+1, pos);
out命令(ポート出力命令)でポートアドレスCRTPORT(#define CRTPORT 0x3d4)+1を指定することにより、VGA内のCDTコントローラが持つデータレジスタへの書き込みを行なっています。前の命令によって、データレジスタを使ってカーソル位置を示すオフセット値の下位8bitを更新できるようになっています。
変数posのbit7-0の値をカーソル位置を示すオフセット値の下位8bitとして更新しています。
crt[pos] = ' ' | 0x0700;
最後に、crt[pos]によって指定されるvideoバッファ上の位置に' 'を書き込み、空文字を描写します。
attributeバッファには0x07が設定されるので、背景が黒になります。