BIOSを使わないで画面モードを変更するぞ!(主にQEMU用)

  • (by K, 2008.08.29)
  • 発展課題のページ(全部読む前にここを読むのは混乱するのでおすすめじゃないです)

まえがき

  • 「はりぼてOS」では起動時に32bitモードになる前にBIOSを使って画面モードを設定していますが、これは本来は一般的な方法ではありません(個人が作るOSでは定番の方法ですが)。Windowsなどのビデオドライバは、IN命令やOUT命令を使って画面モードを切り替えています。
  • そういうのって具体的にどうやっているの?と思うことがあるでしょう。それを説明しようと思います。ただこれはビデオチップ・ビデオカードごとにやり方が違うので、みんなが共通に実験できる例として、QEMUがエミュレーションしているビデオチップを題材にしようと思います。
    • これは結局、BIOSが中でどんなことをやっているかを知ることにもなる。
  • この方法で画面モードを切り替えるのなら、32bitモードのままでできるので、起動後でも何度でも好きなときに画面モードを変えられます(変えたいと思うかどうかは分からないけど)。

QEMUでの画面モード切替方法0(-stdvga編)

  • 実はQEMUではエミュレーションするビデオカードを起動時に選ぶことができます。起動時に-stdvgaというオプションをつけると、QEMUより古いBochsというエミュレータと互換性のある(実在しない)ビデオカードをエミュレーションします。
    • 歴史的背景を説明すると、初期のQEMUとBochsではこのビデオカードしかサポートされていなかった。しかし、これだと既存のOSをエミュレーションするときに、Bochsのビデオカード用のデバイスドライバを用意しなければいけないため、エミュレーション可能なOSを一気に増やせなかった。そこで、QEMUの開発者(もしかしたらBochsの開発者かもしれない)はそこそこメジャーで実在する複雑なビデオカード(Cirrus Logic GD5446)もエミュレーションできるようして、既存のOSのエミュレーションをしたいときは、既存のデバイスドライバを選ぶだけでいいようにした(未確認だけど、最新のBochsにもCirrus Logic GD5446のエミュレーションモードがあるかも)。
    • ちなみにBIOSから利用する限りにおいては、ビデオカードの仕様の複雑さはほとんど関係ない。画面モード設定の方法がややこしくても、それはBIOSが苦労するだけなので。
    • 新しいQEMUでは-std-vgaと書くようです。
  • このカードは非常に単純で理解しやすいので、まずはこれを例に説明しようと思います。
  • ただし一度でもこの方法で画面モードを設定してしまうと、BIOS内部が覚えている状態との食い違いが生じてしまうため、BIOSを通じての画面モード切替や文字表示などがうまくいかなくなることがあります(エミュレータを再起動すれば直ります)。

  • 使用するI/Oポートは0x01ceと0x01cfだけです。0x01ceにレジスタ番号を入れて、0x01cfにデータを入れると、QEMUのビデオカードのレジスタにデータをセットできます。どちらのポートも16bitアクセスしなければいけないので、io_out16()関数を使います。
    • レジスタ0x0001 : xの解像度(320, 640, 800, 1024 のどれか)
    • レジスタ0x0002 : yの解像度(200, 240, 400, 480, 600, 768 のどれか)
      • レジスタ0x0001との組み合わせで適切なものを設定すること
      • (でも変な組み合わせでも長細い画面でちゃんと設定どおりに動いちゃうんだけどね)
    • レジスタ0x0003 : 色のビット数(4, 8, 15, 16, 24, 32 のどれか)
      • レジスタ0x0001〜0x0002との組み合わせで適切なものを設定すること
    • レジスタ0x0004 : 詳細設定
      • bit0 : 設定は有効
      • bit6 : リニアアクセスモード選択
      • bit7 : 画面モード切替時にVRAMをクリアしない(クリアしないメリット:切り替えが早く終わる)
      • その他のビットの意味は不明。必ず0にしておくこと。
    • レジスタ0x0005 : 用途がよく分からないが、0x0000を設定しておくようだ。・・・用途判明!バンクレジスタだった(後述)
    • レジスタ0x0006 : 以下では使っていないが、表示域よりも広い仮想画面をVRAM内に設定するときのためのxのピクセル数
    • レジスタ0x0007 : 上記のyのピクセル数
    • レジスタ0x0008 : 仮想画面を使っているときに、表示位置のx座標
    • レジスタ0x0009 : 仮想画面を使っているときに、表示位置のy座標

  • 例としてはできるだけ単純なほうがいいと思うので、4日目の最後から改造することにしましょう。つまりベースとなるのはharib02iです。まず、asmhead.nasの画面モード設定をコメントアウトしてしまいましょう。こんなものにはもう頼りませんよ。
    ;; 画面モードを設定
    ;
    ;       MOV     AL,0x13         ; VGAグラフィックス、320x200x8bitカラー
    ;       MOV     AH,0x00
    ;       INT     0x10
    ;       MOV     BYTE [VMODE],8  ; 画面モードをメモする(C言語が参照する)
    ;       MOV     WORD [SCRNX],320
    ;       MOV     WORD [SCRNY],200
    ;       MOV     DWORD [VRAM],0x000a0000
    
  • そしてbootpack.cに以下の記述を書き加えます。init_qemuvga0()での手順が、画面モード切り替えの設定方法です。
    void io_out16(int port, int data);
    
    void set_qemuvga0reg(int reg, int dat)
    {
        io_out16(0x01ce, reg);
        io_out16(0x01cf, dat);
        return;
    }
    
    void init_qemuvga0(struct BOOTINFO *binfo, int x, int y, int c, int flag)
    {
        set_qemuvga0reg(0x0004, 0x0000);
        set_qemuvga0reg(0x0001, x);
        set_qemuvga0reg(0x0002, y);
        set_qemuvga0reg(0x0003, c); /* 4, 8, 15, 16, 24, 32 */
        set_qemuvga0reg(0x0005, 0x0000);
        set_qemuvga0reg(0x0004, flag); /* リニアアクセスモードでVRAMの初期化をするなら0x41 */
            /* bit7 : VRAM初期化抑制, bit6 : リニアアクセスモード, bit0 : 設定有効 */
        binfo->scrnx = x;
        binfo->scrny = y;
        binfo->vmode = c;
        if ((flag & 0x40) == 0) {
            binfo->vram = (char *) 0x000a0000;
        } else {
            binfo->vram = (char *) 0xe0000000;
        }
        return;
    }
    
  • そんでもって、HariMain()のinit_screen8()の前に、1行書き足します。ここでは800x600の8bitカラーにしてみます。どうせこの直後に全画面描き換えるのでVRAMのクリアはしないことにしました。
    void HariMain(void)
    {
        (中略)
        init_gdtidt();
        init_palette();
        init_qemuvga0(binfo, 800, 600, 8, 0xc1); /* ココ! */
        init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
        (中略)
    }
    
  • これでできあがりです。さて実験・・・よしうまくいったぁ〜。
  • 本当にこれだけでうまくいくの?という人は是非自分でもやってみましょう。

  • とりあえず上記だけでたいていのことはできると思いますが、32bitモードに切り替えるのが面倒な場合(もしくは切り替えられない特別な事情がある場合)は、なんと1MBまでの番地内だけでも高解像度が使えます。この方法を使うことは多分ないと思いますが、筆者がせっかく調べたので一応まとめておきます。
  • まず、画面モード切替だけならOUT命令だけでできるので、16bitモードのままで問題無しです。しかし上記のサンプルと同じ手順では少し問題があるので、以下の点を変えてください。画面モード設定の際に、「レジスタ0x0004 : 詳細設定」の「bit6 : リニアアクセスモード選択」をゼロにします。
  • こうすることで、0xe0000000からの64KBの内容がそのまま0xa0000からの64KBで読み書きできるようになります。で、これだともちろん不十分です。だって256色の800x600とかに設定した場合はVRAMは全体で468KBを超えるわけで、そのうちの先頭の64KBしか使えないというのはいかにも困るわけです。
  • ということで、他の部分を読み書きしたいときは、上記の「レジスタ0x0005 : バンクレジスタ」を使います。ここに1を書き込むと、0xa0000からの64KBの内容は、0xe0010000からの内容に瞬時に入れ替わります。2を書き込めば0xe0020000になります。こんな感じで、結局0xa0000〜0xaffffへ読み書きするだけで、VRAM全体を読み書きできるというわけです。

QEMUでもQEMU以外でも使える切替方法1(古いモード編)

  • 本の中でよく使っている320x240x8bitのモードは、BIOSを使わずに設定することが可能です。VBEを使わなくても設定できる画面モードのうちメジャーなものは、実はほとんどのカードで設定方法が同じになっています。したがってこのセクションの方法は、QEMU以外のエミュレータでもうまくいくのはもちろんのこと、実機でもうまくいきます。QEMUでいえば、-stdvgaであってもなくてもうまくいきます。ただVBE以前のモードばかりなので高解像度や多色に対応できず、ややイマイチな感じではあります。
    • ただし一度でもこの方法で画面モードを設定してしまうと、BIOS内部が覚えている状態との食い違いが生じてしまうため、BIOSを通じての画面モード切替や文字表示などがうまくいかなくなることがあります(もちろん再起動すれば直ります)。
  • -stdvga以外のビデオカードの設定がどれほど面倒そうなのかの感じをつかんでほしくてこのセクションを作りました。
  • すごく長くなりそうなので、別ページに作ります。
  • (つづく)

QEMUでの画面モード切替方法2(Cirrus Logic GD5446編)

  • (つづく:調査中)

こめんと欄

  • もしかすると、この方法だとXen上で動くかもですね -- あっきぃ 2008-09-12 (金) 02:16:21
  • ほんとう?XenってQEMUの-std-vgaの互換モードってあるのかな?あったらビンゴだね! -- K 2008-12-17 (水) 22:14:58

コメントお名前NameLink

リロード   新規 編集 差分 添付   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS
Last-modified: 2008-12-17 (水) 22:14:59 (4008d)