ラズベリーパイ上のベアメタルプログラミングにおけるメールボックス 機能 

# ラズベリーパイ、ベアメタルのメールボックス
##はじめに
これは[自作OS Advent Calendar](https://adventar.org/calendars/2282#list-2017-12-03) 2017の12/15の記事としてかかれました。
自作OS Advent Calendar 2017については主催者の[uchan_nos氏による12/1の記事](http://uchan.hateblo.jp/entry/2017/11/30/211206)をご覧下さい。

ここでいうメールボックスはラズベリーパイでCPUとVideoCore（VC、いわゆるGPU）との間で情報をやりとりするための機能で、e-mailは関係ありません。

##ラズベリーパイ上の自作OSとメールボックス
ラズベリーパイはいわずとしれた教育用低価格コンピュータです。通常のプログラミング教育や、ホビー向けの工作、はては実用的な組み込みまでいろいろな分野に広がっていますが、ベアメタル（OS抜きのプログラミング）や自作OS作成などの目的でにも使われています。豊富なGPIOがJTAGやUART(シリアル）として機能するのでデバッグにも便利ですし、価格も安くて壊してもダメージが少ないので、自作OS学習に向いたプラットフォームだと思います[^1]。

[^1]: このあたりの情報は電子書籍[「BareMetalで遊ぶ　Raspberry Pi」](https://tatsu-zine.com/books/raspi-bm)が詳しいです。私自身とても参考になりました。

そのラズベリーパイでは、VCはブートローダーを実行するなどコントロール面においてもCPU並に重要な機能を担っています。当然CPUとVCとで情報を渡しあったり、動作を命令したりすることがあるわけですが、メールボックスはそういった目的のために使われます。ベアメタルプロラミングや自作OSには重要な機能です。特に、HDMIを通して画像をディスプレイに表示しようとすると必須機能[^2]と言っても良いと思います。 私も1年ほど前に「[30日でできる！OS自作入門](https://book.mynavi.jp/supportsite/detail/4839919844.html)」の[Hariobte OSをラズベリーパイに移植してみた](http://uzusayuu.hatenadiary.jp/entry/2017/02/12/064131)のですが、実際にメールボックス機能を多用しました。

[^2]: そうは言ってもVCのファームウェアまで自分で書いてしまうようなレベルの人にはあてはまりませんが

そのように重要なメールボックス機能ですが、ざっと探してみたところ日本語でメールボックス機能について書いた記事が多くないようなのでこの機会にまとめてみることにしました。なお、以下の記述はRaspberry Pi B+およびRaspberry Pi Zero[^4]を前提としています。
[^4]: おそらくWも含むが確認していない

## メールボックスの機能
メールボックスが使われる機能は多岐にわたります。詳しいことは[Raspberry Pi Org のgithubにて公開](https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface)されていますが、主要な物だけでも以下のようなものがあります。

* グラフィックの各種設定（画像サイズ、色数、パレット、フレームバッファーのアドレス）
* ファームウェアのバージョンの確認
* ボードのモデル、リビジョン、MACアドレス、シリアルナンバーの確認
* ARMとVCのメモリサイズ・アドレス、クロックスピードの設定・確認
* ペリフェラル（UARTなど）のクロックや電源状態の設定及・確認

このようにメールボックスを使うことでベアメタルからラズベリーパイの重要な機能にアクセスすることができます。

## メールボックの通信方法
ARMがVCとメールボックスを使って通信するにあたっては次のような手順をとります。

1. リクエストバッファーをメモリー上に確保し必要なパラメータを書き込む
    * リクエストバッファーは16byte単位でアラインしている必要がある
    * バッファーの内容については次の項を参照
2. メールボックスがリクエスト可能になるのを待つ
    * メールボックスのステータスはメモリ上の0x2000B898からの32ビットにマップされています
    * ステータスの最上位ビット(Bit31)がゼロになるとリクエスト可能です
3. メールボックスにライトリクエストを出す
    * ライトリクエストは0x2000B8A0にマップされています
    * この位置に、リクエストバッファーのアドレスの上位28bitとチャンネル（LSB4bit）を書き込みます
    * チャンネルは機能ごとに分かれているが、グラフィック用フレームバッファの設定は1チャンネル、それ以外のARMからVCへの要求は通常8チャンネル。
4. VCが処理を終えてステータスが読み取り可能になるのを待つ
    * ステータスのBit30がゼロになれば読み取り可能です
5. メールボックスからリード情報を読み出す
    * リード情報のアドレスは0x2000B8880です。
    * リード情報のLSB4bitはこのリード情報のチャンネル
    * リード情報のMSB28bitはレスポンスバッファーのアドレスの上位28bitです。実際にはレスポンスバッファーのアドレスは、リクエストバッファーのアドレスと同じになるはずです。
6. リード情報のチャンネルが通信に使ったチャンネルと同じであれば次へ。そうでなければ4に戻る
7. レスポンスコードを確認。エラーがなければ処理を続行、エラーがあればエラー処理へ
    * レスポンスバッファーの最初の32ビットはバッファサイズ、次の32ビットはVCからのレスポンスコードを示す
    * レスポンスが0x80000000であれば成功、0x80000001は失敗

## リクエスト・レスポンスバッファーのフォーマット
リクエスト・レスポンスバッファーの内容は次のようになっています。フォーマットはリクエストとレスポンスで共通です

バッファのフォーマットは先頭位置から以下のようになっています。

|  サイズ  |   内容             |
|:----------------:|:------------------|
|     4 byte           | バッファのサイズ <br> byte単位、レスポンスのみに必要なバイト数も数える |
|     4 byte           | リクエストコードまたはレスポンスコード <br> リクエスト:0x00000000 <br> レスポンス成功:0x80000000 <br> レスポンス-失敗: 0x80000001|
|      4*n byte         | 一つ以上の連結したタグ。<br> 4*nは使用したタグのサイズの合計 |
|     4 byte           | 終了tag (0x00000000) |
|   x byte           | 16 byteアライン終了までパディング <br> xは必要なbyte数 |

バッファは16 byteアラインです

タグのフォーマットは先頭位置から

| サイズ      |  内容  |
|:----------:|:-----|
| 4 byte     | タグID <br> 使用する機能毎に決まっている <br> 例: クロック問い合わせは0x00010007 |
| 4 byte     | タグのサイズ(byte単位) |
| 4 byte     | リクエストコード (MSB=0、残りはリザーブ） <br> またはレスポンスコード(MSB=1、残りはタグの内容のサイズ) |
| m byte     |  タグの内容 <br> mはタグ毎に決まったサイズ |
| x byte     |  以下、4 byteアライン終了までパディング <br> 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 |パレット設定のタグ <br> ここがタグのスタート|
|  12 -  15 | 0x00000368 |タグ長さ <br> =(先頭位置情報＋色の数情報＋216色のパレット情報)*4 <br> =872 <br> =0x368|
|  16 -  19 | 0x00000000 |タグのリクエストコード。MSB=0|
|  20 -  23 | 0x00000010 |変更するパレットの最初の番号=16|
|  24 -  27 | 0x000000d8 |変更するパレットの数=216|
|  28 -  31 | 0x00BBGGRR |最初のパレットのRGB値 <br> BB:青、GG:緑、RR:赤 [^5]|
|  32 -  35 | 0x00BBGGRR |2番めのパレットのRGB値 <br> BB:青、GG:緑、RR:赤 |
| ...... | ...... | ...... | 
| 888 - 891 | 0x00BBGGRR |216番目のパレットのRGB値 <br> BB:青、GG:緑、RR:赤 |
| 892 - 895 | 0x00000000 |ストップタグ|

[^5]: MSB8bitはアルファブレンディング値に使うこともできる

## 実際の使用
ベアメタル上で、上記のパレットの設定をする実際のコードの例としては、このようなものになります。

```c
// 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](https://github.com/raspberrypi/firmware/wiki)
* [Cambridge - Baking Pi](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/screen01.html)（アセンブラによるメールボックスを使ったグラフィックモードの設定がある）
* [Valvers Raspberry Pi Bare Metal Programming in C Part 5 - Graphics (Basic)](http://www.valvers.com/open-software/raspberry-pi/step05-bare-metal-programming-in-c-pt5/)