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が設定されるので、背景が黒になります。