search
LoginSignup
6

More than 5 years have passed since last update.

posted at

updated at

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

ラズベリーパイ、ベアメタルのメールボックス

はじめに

これは自作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とメールボックスを使って通信するにあたっては次のような手順をとります。

  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 バッファのサイズ
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機能に関わる記事へのリンク集


  1. このあたりの情報は電子書籍「BareMetalで遊ぶ Raspberry Pi」が詳しいです。私自身とても参考になりました。 

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

  3. おそらくWも含むが確認していない 

  4. MSB8bitはアルファブレンディング値に使うこともできる 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6