QEMUのRaspberry Pi2モデルでフレームバッファ描画

QEMUのRaspberry Pi2モデルを使って、フレームバッファに描画してみました。

QEMUのRaspberry Pi2モデルは、VRAMのアドレスさえ分かれば、ベアメタルでも簡単に描画できます。

本当は、Raspberry Pi3モデルを使いたいのだけど、なぜか動かないんです。(もう一回試したところ、ブー部分のコードをAARCH64に書き換えたら、Raspberry Pi3モデルでもFrabeBufferは動いた。でもメールボックスは動かない。)

# 動作例

QEMUは2.12を使っています。

```
$ emu-system-arm -no-reboot -m 128 -M raspi3 -kernel kernel.elf 
```

QEMUを実行して表示される画像です。
<img width="752" alt="スクリーンショット 2018-05-17 17.35.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/42120/859fab84-526e-c4c0-5c4a-60f2d4a090c6.png">
「30日でできる！OS自作入門」の例を描画しています。

# 説明
* QEMU 2.12 の Raspberry Pi モデルは、フレームバッファの設定がソースコードに記述されています。

```c:hw/display/bcm2835_fb.c
static Property bcm2835_fb_props[] = {
    DEFINE_PROP_UINT32("vcram-base", BCM2835FBState, vcram_base, 0),/*required*/
    DEFINE_PROP_UINT32("vcram-size", BCM2835FBState, vcram_size,
                       DEFAULT_VCRAM_SIZE),
    DEFINE_PROP_UINT32("xres", BCM2835FBState, xres, 640),
    DEFINE_PROP_UINT32("yres", BCM2835FBState, yres, 480),
    DEFINE_PROP_UINT32("bpp", BCM2835FBState, bpp, 16),
    DEFINE_PROP_UINT32("pixo", BCM2835FBState, pixo, 1), /* 1=RGB, 0=BGR */
    DEFINE_PROP_UINT32("alpha", BCM2835FBState, alpha, 2), /* alpha ignored */
    DEFINE_PROP_END_OF_LIST()
};
```
* つまり、640x480の16bitモードがデフォルト設定です。（なので、QEMUではフレームバッファの設定をしなくても、この設定で動きます。）
* この時のVRAMのアドレスは0x04100000固定です。(実際にはソースを見てもわからないので、mailbox経由でアドレスを取得しました。)
* VRAMに16ビットのカラーコードを書き込めば、描画できます。
* VRAMのアドレスを使って、画面の左上の1ピクセルを白にする場合の例です。

```
uint16_t *vram = (uint16_t *) 0x4100000;
vram = 0xffff;
```

動作例を描画したコードです。

```
void boxfill8(uint16_t *vram, int xsize, uint16_t c, int x0, int y0, int x1, int y1)
{
    int x, y;
    for (y = y0; y <= y1; y++) {
        for (x = x0; x <= x1; x++)
            vram[y * xsize + x] = c;
    }
    return;
}

#define COL8_000000 (((0x00>>3)<<11) + ((0x00>>3)<<6) + (0x00>>3))
#define COL8_008484 (((0x00>>3)<<11) + ((0x84>>3)<<6) + (0x84>>3))
#define COL8_848484 (((0x84>>3)<<11) + ((0x84>>3)<<6) + (0x84>>3))
#define COL8_C6C6C6 (((0xC6>>3)<<11) + ((0xC6>>3)<<6) + (0xC6>>3))
#define COL8_FFFFFF (((0xFF>>3)<<11) + ((0xFF>>3)<<6) + (0xFF>>3))

void kernel_main(void)
{
    uint16_t *vram = (uint16_t *) 0x4100000;
    int x = 640 ,y = 480;

    boxfill8(vram, x, COL8_008484,  0,     0,      x -  1, y - 29);
    boxfill8(vram, x, COL8_C6C6C6,  0,     y - 28, x -  1, y - 28);
    boxfill8(vram, x, COL8_FFFFFF,  0,     y - 27, x -  1, y - 27);
    boxfill8(vram, x, COL8_C6C6C6,  0,     y - 26, x -  1, y -  1);

    boxfill8(vram, x, COL8_FFFFFF,  3,     y - 24, 59,     y - 24);
    boxfill8(vram, x, COL8_FFFFFF,  2,     y - 24,  2,     y -  4);
    boxfill8(vram, x, COL8_848484,  3,     y -  4, 59,     y -  4);
    boxfill8(vram, x, COL8_848484, 59,     y - 23, 59,     y -  5);
    boxfill8(vram, x, COL8_000000,  2,     y -  3, 59,     y -  3);
    boxfill8(vram, x, COL8_000000, 60,     y - 24, 60,     y -  3);

    boxfill8(vram, x, COL8_848484, x - 47, y - 24, x -  4, y - 24);
    boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y -  4);
    boxfill8(vram, x, COL8_FFFFFF, x - 47, y -  3, x -  4, y -  3);
    boxfill8(vram, x, COL8_FFFFFF, x -  3, y - 24, x -  3, y -  3);

    while (1)
        ;
}
```



# VRAMアドレス取得
QEMU上でもVRAMのアドレスをメールボックス経由で取得できます。
0x4100000というアドレスは下記のコードをQEMU上で動かして取得しました。

メールボックスの使い方は、参考にしたページを見てください。

ただ実機とQEMUの動作が同じかどうかはよくわからないです。
実機(Raspberry Pi)では、メールボックスへのライトで渡すアドレスに、0x40000000を足しているのですが、QEMUだと、そのままのアドレスでないと動きませんでした。

```c
#define MB_READ   0x3F00B880
#define MB_WRITE  0x3F00B8A0
#define MB_STATUS 0x2000B898

void mb_write(uint32_t cmd_addr, uint32_t channel)
{
    volatile uint32_t sta, cmd = 0;

    do {
        sta = *( (volatile uint32_t *) MB_STATUS);
    } while (sta & 0x80000000);

    cmd = cmd_addr | channel;
    *((volatile uint32_t *) MB_WRITE) = cmd;
    return;
}

void mb_read(uint32_t channel, uint32_t *resp_addr)
{
    volatile uint32_t mail, sta;

    do {
        do {
            sta = *( (volatile uint32_t *) MB_STATUS);
        } while (sta & 0x40000000);
        mail = *( (volatile uint32_t *) MB_READ);
    } while ((mail & 0x0000000F) != channel);

    *resp_addr =  mail & 0xFFFFFFF0;
    uart_puts("mb_read resp addr ");
    uart_hex_puts(*resp_addr);
    return;
}

static uint32_t pt[8192] __attribute__((aligned(16)));

uint16_t *fb_get_address(void)
{
    uint32_t i;
    uint32_t mail;
    uint32_t *mailp;

    pt[0] = 32;      // buffer size in bytes
    pt[1] = 0;       // request code: process request
    pt[2] = 0x40001; // tag: allocate frame buffer
    pt[3] = 4;       // length
    pt[4] = 0;
    pt[5] = 16;      // alignments in bytes
    pt[7] = 0;       // end tag

    uart_puts("mailbox cmd addr: ");
    uart_hex_puts( (uint32_t) pt);
    for(i=0; i<*((uint32_t *) pt)/4; i++) {
        uart_hex_puts( *((uint32_t *) (pt+i)));
    }

    mb_write((uint32_t) pt, 8);
    mb_read(8, &mail);

    uart_puts("mailbox resp addr: ");
    uart_hex_puts( (unsigned int) mail);
    mailp = (uint32_t *) mail;
    for(i=0; i<32/4; i++) {
        uart_hex_puts( * (mailp+i));
    }

    return (uint16_t *) *(mailp + 5);
}

void kernel_main(void)
{
    uint16_t *vram;
    uart_init();

    vram = fb_get_address();
...
}

```

上記を動作させた時のUART出力ログ

```
mailbox cmd addr: 0xB000
0x20
0x0
0x40001
0x4
0x0
0x10
0x0
0x0

mb_read resp addr 0xB000

mailbox resp addr: 0xB000
0x20
0x80000000
0x40001
0x4
0x80000008
0x4100000
0x96000
0x0

```
このログの、0x4100000がVRAMのアドレスです。

QEMUが対応しているメールボックスのコマンドは、QEMUの[bcm2835_property.c] (https://github.com/qemu/qemu/blob/master/hw/misc/bcm2835_property.c)に記述されています。



# 参考にしたページ
* [VC framebuffer on QEMU -M raspi2](https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=198982)
* [boot-arm-raspi.s](https://github.com/mmuszkow/NoOsBootstrap/blob/master/arm/boot-arm-raspi.s)
* [ラズベリーパイ上のベアメタルプログラミングにおけるメールボックス 機能](https://qiita.com/moizumi99/items/a14d43884ba78808a992)
* [RPiHaribote](https://github.com/moizumi99/RPiHaribote/)の[mailbox.c](https://github.com/moizumi99/RPiHaribote/blob/master/haribote/mailbox.c)
* Officital の github wikiにある [Mailbox property interface](https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface)