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を実行して表示される画像です。
「30日でできる!OS自作入門」の例を描画しています。
説明
- QEMU 2.12 の Raspberry Pi モデルは、フレームバッファの設定がソースコードに記述されています。
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だと、そのままのアドレスでないと動きませんでした。
#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)に記述されています。