ラズベリーパイ、ベアメタルのメールボックス
##はじめに
これは自作OS Advent Calendar 2017の12/15の記事としてかかれました。
自作OS Advent Calendar 2017については主催者のuchan_nos氏による12/1の記事をご覧下さい。
ここでいうメールボックスはラズベリーパイでCPUとVideoCore(VC、いわゆるGPU)との間で情報をやりとりするための機能で、e-mailは関係ありません。
##ラズベリーパイ上の自作OSとメールボックス
ラズベリーパイはいわずとしれた教育用低価格コンピュータです。通常のプログラミング教育や、ホビー向けの工作、はては実用的な組み込みまでいろいろな分野に広がっていますが、ベアメタル(OS抜きのプログラミング)や自作OS作成などの目的でにも使われています。豊富なGPIOがJTAGやUART(シリアル)として機能するのでデバッグにも便利ですし、価格も安くて壊してもダメージが少ないので、自作OS学習に向いたプラットフォームだと思います1。
そのラズベリーパイでは、VCはブートローダーを実行するなどコントロール面においてもCPU並に重要な機能を担っています。当然CPUとVCとで情報を渡しあったり、動作を命令したりすることがあるわけですが、メールボックスはそういった目的のために使われます。ベアメタルプロラミングや自作OSには重要な機能です。特に、HDMIを通して画像をディスプレイに表示しようとすると必須機能2と言っても良いと思います。 私も1年ほど前に「30日でできる!OS自作入門」のHariobte OSをラズベリーパイに移植してみたのですが、実際にメールボックス機能を多用しました。
そのように重要なメールボックス機能ですが、ざっと探してみたところ日本語でメールボックス機能について書いた記事が多くないようなのでこの機会にまとめてみることにしました。なお、以下の記述はRaspberry Pi B+およびRaspberry Pi Zero3を前提としています。
メールボックスの機能
メールボックスが使われる機能は多岐にわたります。詳しいことはRaspberry Pi Org のgithubにて公開されていますが、主要な物だけでも以下のようなものがあります。
- グラフィックの各種設定(画像サイズ、色数、パレット、フレームバッファーのアドレス)
- ファームウェアのバージョンの確認
- ボードのモデル、リビジョン、MACアドレス、シリアルナンバーの確認
- ARMとVCのメモリサイズ・アドレス、クロックスピードの設定・確認
- ペリフェラル(UARTなど)のクロックや電源状態の設定及・確認
このようにメールボックスを使うことでベアメタルからラズベリーパイの重要な機能にアクセスすることができます。
メールボックの通信方法
ARMがVCとメールボックスを使って通信するにあたっては次のような手順をとります。
- リクエストバッファーをメモリー上に確保し必要なパラメータを書き込む
- リクエストバッファーは16byte単位でアラインしている必要がある
- バッファーの内容については次の項を参照
- メールボックスがリクエスト可能になるのを待つ
- メールボックスのステータスはメモリ上の0x2000B898からの32ビットにマップされています
- ステータスの最上位ビット(Bit31)がゼロになるとリクエスト可能です
- メールボックスにライトリクエストを出す
- ライトリクエストは0x2000B8A0にマップされています
- この位置に、リクエストバッファーのアドレスの上位28bitとチャンネル(LSB4bit)を書き込みます
- チャンネルは機能ごとに分かれているが、グラフィック用フレームバッファの設定は1チャンネル、それ以外のARMからVCへの要求は通常8チャンネル。
- VCが処理を終えてステータスが読み取り可能になるのを待つ
- ステータスのBit30がゼロになれば読み取り可能です
- メールボックスからリード情報を読み出す
- リード情報のアドレスは0x2000B8880です。
- リード情報のLSB4bitはこのリード情報のチャンネル
- リード情報のMSB28bitはレスポンスバッファーのアドレスの上位28bitです。実際にはレスポンスバッファーのアドレスは、リクエストバッファーのアドレスと同じになるはずです。
- リード情報のチャンネルが通信に使ったチャンネルと同じであれば次へ。そうでなければ4に戻る
- レスポンスコードを確認。エラーがなければ処理を続行、エラーがあればエラー処理へ
- レスポンスバッファーの最初の32ビットはバッファサイズ、次の32ビットはVCからのレスポンスコードを示す
- レスポンスが0x80000000であれば成功、0x80000001は失敗
リクエスト・レスポンスバッファーのフォーマット
リクエスト・レスポンスバッファーの内容は次のようになっています。フォーマットはリクエストとレスポンスで共通です
バッファのフォーマットは先頭位置から以下のようになっています。
サイズ | 内容 |
---|---|
4 byte | バッファのサイズ byte単位、レスポンスのみに必要なバイト数も数える |
4 byte | リクエストコードまたはレスポンスコード リクエスト:0x00000000 レスポンス成功:0x80000000 レスポンス-失敗: 0x80000001 |
4*n byte | 一つ以上の連結したタグ。 4*nは使用したタグのサイズの合計 |
4 byte | 終了tag (0x00000000) |
x byte | 16 byteアライン終了までパディング xは必要なbyte数 |
バッファは16 byteアラインです
タグのフォーマットは先頭位置から
サイズ | 内容 |
---|---|
4 byte | タグID 使用する機能毎に決まっている 例: クロック問い合わせは0x00010007 |
4 byte | タグのサイズ(byte単位) |
4 byte | リクエストコード (MSB=0、残りはリザーブ) またはレスポンスコード(MSB=1、残りはタグの内容のサイズ) |
m byte | タグの内容 mはタグ毎に決まったサイズ |
x byte | 以下、4 byteアライン終了までパディング xは必要なバイト数 |
タグは4 byteアラインです
タグの内容とバッファの例
タグの内容については、これまた先のRaspberry Pi Orgのgithubに詳細があります。
https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
例えば、256色表示モードで、パレットを設定するには次のようなタグが必要だとあります。
Set palette
- Tag: 0x0004800b
- Request:
- Length: 24..1032
- Value:
- u32: offset: first palette index to set (0-255)
- u32: length: number of palette entries to set (1-256)
- u32...: RGBA palette values (offset to offset+length-1)
- Response:
- Length: 4
- Value:
- u32: 0=valid, 1=invalid
これはつまり、例えば256色表示モードで16番目から216番目までのパレットを設定するならば、次のようなバッファが必要だということをしめしています。
相対アドレス | 値 | 説明 |
---|---|---|
0 - 3 | 0x00000380 | バッファ長=896 |
4 - 7 | 0x00000000 | リクエストコード |
8 - 11 | 0x0004800b | パレット設定のタグ ここがタグのスタート |
12 - 15 | 0x00000368 | タグ長さ =(先頭位置情報+色の数情報+216色のパレット情報)*4 =872 =0x368 |
16 - 19 | 0x00000000 | タグのリクエストコード。MSB=0 |
20 - 23 | 0x00000010 | 変更するパレットの最初の番号=16 |
24 - 27 | 0x000000d8 | 変更するパレットの数=216 |
28 - 31 | 0x00BBGGRR | 最初のパレットのRGB値 BB:青、GG:緑、RR:赤 4 |
32 - 35 | 0x00BBGGRR | 2番めのパレットのRGB値 BB:青、GG:緑、RR:赤 |
...... | ...... | ...... |
888 - 891 | 0x00BBGGRR | 216番目のパレットのRGB値 BB:青、GG:緑、RR:赤 |
892 - 895 | 0x00000000 | ストップタグ |
実際の使用
ベアメタル上で、上記のパレットの設定をする実際のコードの例としては、このようなものになります。
// startとendは変更するパレット色の最初と最後の番号
// rgbはパレット値を8ビットでr, g, b の順に並べたもの
void set_palette(int start, int end, unsigned char *rgb)
{
int i;
uint32_t addr;
uint32_t mail;
for(i=0; i<2048; i++) {
pt[i]=0;
}
pt[ 0] = 12; // placeholder for the total size
pt[ 1] = 0; // Request code: process request
pt_index=2;
pt[pt_index++] = 0x0004800b; // tag identifeir = 0x0004800b: Set Palette)
pt[pt_index++] = (1+1+(end-start+1))*4; // value buffer size in bytes ((1+1+16)*4)
pt[pt_index++] = 0; // 1bit request/response (0/1) at MSB
pt[pt_index++] = start; // first palette index to set
pt[pt_index++] = (end - start+1); // number of palette index to set
for(i=0; i<=end-start; i++) {
pt[pt_index++] = (rgb[i*3+2] << 16) + (rgb[i*3+1] << 8) + (rgb[i*3+0]);
}
pt[pt_index++] = 0; // stop tag (unnecessary if initialized with zero. Just in case)
pt[0] = pt_index*4;
addr = ((unsigned int) pt) + 0x40000000;
mailbox_write(addr, 8);
mailbox_read(8, &mail);
if (*((volatile unsigned int *) (mail+4)) == 0x80000000) {
printf("Palette initialization successful\n");
printf("response: 0x%08x\n", *((volatile unsigned int *) (mail+4)));
} else {
printf("Palette initialization fail\n");
printf("response: 0x%08x\n", *((volatile unsigned int *) (mail+4)));
}
for(i=0; i<6; i++) {
printf("Returned Value: 0x%08x\n", *((unsigned int *) (mail + i*4)));
}
return;
}
int mailbox_write(uint32_t value, uint32_t channel) {
volatile uint32_t sta;
volatile uint32_t cmd;
// valueとchannelの範囲チェック
if ((value & 0x0000000F) != 0) {
return -1;
}
if ((channel & 0xFFFFFFF0) != 0) {
return -1;
}
cmd = 0;
do {
sta = *( (volatile uint32_t *) MB_STATUS);
} while ((sta & 0x80000000) != 0);
cmd = value | channel;
*((volatile uint32_t *) MB_WRITE) = cmd;
return 0;
}
int mailbox_read(uint32_t channel, uint32_t *value) {
uint32_t mail, sta;
// channelの範囲チェック
if ((channel & 0xFFFFFFF0) != 0) {
return -1;
}
do {
do {
sta = *( (volatile uint32_t *) MB_STATUS);
} while (sta & 0x40000000);
mail = *( (volatile uint32_t *) MB_READ);
} while ((mail & 0x0000000F) != channel);
*value = mail & 0xFFFFFFF0;
return 0;
}
まとめ
Raspberry Piで自作OSを試そうとするとメールボックス機能はとても重要になります。
ここでは基本的な内容と使い方をまとめてみました。
Raspberry PiのMailbox機能に関わる記事へのリンク集
- Raspberry Pi Org Firmware Github Wiki
- Cambridge - Baking Pi(アセンブラによるメールボックスを使ったグラフィックモードの設定がある)
- Valvers Raspberry Pi Bare Metal Programming in C Part 5 - Graphics (Basic)
-
このあたりの情報は電子書籍「BareMetalで遊ぶ Raspberry Pi」が詳しいです。私自身とても参考になりました。 ↩
-
そうは言ってもVCのファームウェアまで自分で書いてしまうようなレベルの人にはあてはまりませんが ↩
-
おそらくWも含むが確認していない ↩
-
MSB8bitはアルファブレンディング値に使うこともできる ↩