Trusty API リファレンス

Trusty では、次の 2 つのクラスのアプリケーションやサービスを開発するための API を提供しています。

  • TEE プロセッサで実行される信頼できるアプリケーションまたはサービス
  • メイン プロセッサ上で実行され、信頼できるアプリケーションによって提供されるサービスを使用する、通常のアプリケーションまたは信頼できないアプリケーション

Trusty API は一般に、非セキュア環境との通信を含む Trusty プロセス間通信(IPC)システムを記述します。メイン プロセッサ上で実行されるソフトウェアは、Trusty API を使用して信頼できるアプリケーションやサービスに接続し、IP 経由のネットワーク サービスと同様に任意のメッセージを交換できます。アプリケーション レベルのプロトコルを使用して、これらのメッセージのデータ形式とセマンティクスを判断するのは、アプリケーションの責任です。メッセージの信頼性の高い配信は、基盤となる Trusty インフラストラクチャによって(メイン プロセッサで実行されるドライバの形式で)保証されており、通信は完全に非同期です。

ポートとチャネル

ポートは、クライアントが接続する名前付きパスの形式でサービス エンドポイントを公開するために、Trusty アプリケーションで使用されます。これにより、クライアントが使用するシンプルな文字列ベースのサービス ID が提供されます。命名規則は com.google.servicename のように逆 DNS スタイルです。

クライアントがポートに接続すると、クライアントはサービスと通信するためのチャネルを受信します。サービスは受信方向の接続を許可する必要があり、許可した場合はサービスもチャネルを受信します。本質的に、ポートはサービスの検索に使用され、通信は一対の接続されたチャネル(ポート上の接続インスタンス)を介して行われます。クライアントがポートに接続すると、対称双方向接続が確立されます。クライアントとサーバーは、この全二重パスを使用して、いずれかの側が接続を切断するまで任意のメッセージを交換できます。

セキュア側の信頼できるアプリケーションまたは Trusty カーネル モジュールのみがポートを作成できます。非セキュア側(通常の環境)で実行されるアプリケーションは、セキュア側が公開するサービスにのみ接続できます。

要件に応じて、信頼できるアプリケーションは同時にクライアントとサーバーになります。サーバーとしてサービスを公開する信頼できるアプリケーションは、クライアントとして他のサービスへの接続が必要な場合があります。

Handle API

ハンドルは、UNIX のファイル記述子に似た、ポートやチャネルなどのリソースを表す符号なし整数です。ハンドルは作成後にアプリケーション固有のハンドル テーブルに格納され、後で参照できます。

呼び出し元は set_cookie() メソッドを使用して、個人データをハンドルに関連付けることができます。

Handle API のメソッド

ハンドルは、アプリケーションのコンテキストでのみ有効です。明示的に指定しない限り、アプリケーションはハンドル値を他のアプリケーションに渡すことはできません。ハンドル値は、ハンドルが無効であるか未設定であるかを示すためにアプリケーションが使用できる INVALID_IPC_HANDLE #define, との比較によってのみ、解釈される必要があります。

呼び出し元が提供する個人データを、指定されたハンドルに関連付けます。

    long set_cookie(uint32_t handle, void *cookie)
    

[in] handle: いずれかの API 呼び出しによって返されたハンドル

[in] cookie: Trusty アプリケーション内の任意のユーザー空間データへのポインタ

[retval]:成功の場合は NO_ERROR、それ以外の場合は < 0 のエラーコード

この呼び出しは、ハンドルが作成された後に発生したイベントを処理する場合に便利です。イベント処理メカニズムは、ハンドルとその Cookie をイベント ハンドラに返します。

wait() 呼び出しを使用して、ハンドルでイベントが発生するのを待つことができます。

wait()

指定された期間、特定のハンドルでイベントが発生するのを待機します。

    long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
    

[in] handle_id: いずれかの API 呼び出しによって返されたハンドル

[out] event: このハンドルで発生したイベントを表す構造体へのポインタ

[in] timeout_msecs: ミリ秒単位のタイムアウト値。-1 の値は無限のタイムアウトを示します。

[retval]: 指定されたタイムアウト間隔内に有効なイベントが発生した場合は NO_ERROR。指定されたタイムアウトが経過してもイベントが発生しなかった場合は ERR_TIMED_OUT。その他のエラーの場合は < 0

成功時(retval == NO_ERROR)、wait() 呼び出しは発生したイベントに関する情報を、指定された uevent_t 構造体に入力します。

    typedef struct uevent {
        uint32_t handle; /* handle this event is related to */
        uint32_t event;  /* combination of IPC_HANDLE_POLL_XXX flags */
        void    *cookie; /* cookie associated with this handle */
    } uevent_t;
    

event フィールドには、次の値の組み合わせが含まれます。

    enum {
      IPC_HANDLE_POLL_NONE    = 0x0,
      IPC_HANDLE_POLL_READY   = 0x1,
      IPC_HANDLE_POLL_ERROR   = 0x2,
      IPC_HANDLE_POLL_HUP     = 0x4,
      IPC_HANDLE_POLL_MSG     = 0x8,
      IPC_HANDLE_POLL_SEND_UNBLOCKED = 0x10,
      … more values[TBD]
    };
    

IPC_HANDLE_POLL_NONE - 実際に保留中のイベントはなく、呼び出し元は待機を再開する必要があります。

IPC_HANDLE_POLL_ERROR - 指定されていない内部エラーが発生しました。

IPC_HANDLE_POLL_READY - 次のようにハンドルタイプによって異なります。

  • ポートの場合、この値は保留中の接続があることを示します。
  • チャネルの場合、この値は非同期接続(connect() を参照)が確立されたことを示します。

次のイベントはチャネルにのみ関連します。

  • IPC_HANDLE_POLL_HUP - ピアがチャネルを閉じたことを示します。
  • IPC_HANDLE_POLL_MSG - このチャネルに保留中のメッセージがあることを示します。
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - 以前にブロックされた発信者がメッセージの再送信を試せることを示します(詳しくは send_msg() を参照)。

複数のビットが同時に設定される可能性があるため、イベント ハンドラは指定されたイベントの組み合わせを処理できるようにしておく必要があります。たとえば、1 つのチャネルでメッセージを保留すると同時に、ピアが接続を終了する可能性があります。

ほとんどのイベントは持続的です。基本的な条件が持続する限り(たとえば、すべての保留メッセージが受信され、保留中の接続要求が処理される限り)、イベントも持続します。ただし IPC_HANDLE_POLL_SEND_UNBLOCKED の場合は例外で、読み取り時にクリアされ、アプリケーションは 1 回しか処理できません。

ハンドルは、close() メソッドを呼び出すことで破棄できます。

close()

指定されたハンドルに関連付けられたリソースを破棄し、ハンドル テーブルから削除します。

    long close(uint32_t handle_id);
    

[in] handle_id: 破棄するハンドル

[retval]: 成功した場合は 0、それ以外の場合は負のエラー

Server API

サーバーではまず、サービス エンドポイントを表す名前付きポートを作成します。各ポートはハンドルで表されます。

Server API のメソッド

port_create()

名前付きサービスポートを作成します。

    long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size,
    uint32_t flags)
    

[in] path: ポートの文字列名(前述のとおり)。この名前はシステム全体で一意である必要があります。重複する名前を作成しようとすると失敗します。

[in] num_recv_bufs: クライアントとのデータ交換を容易にするために、このポートのチャネルが事前に割り当てることができるバッファの最大数。バッファはデータの方向ごとに別々にカウントされるため、ここで 1 を指定すると、送信 1 回と受信 1 回のバッファが事前に割り当てられます。一般に、必要なバッファの数は、クライアントとサーバー間の上位レベルのプロトコルの取り決めによって決まります。非常に同期的なプロトコルの場合は 1 を指定できます(メッセージを送信し、別のメッセージを送信する前に返信を受信する場合)。クライアントが返信を受信する前に複数のメッセージを送信する場合は、それより多い数を指定できます(1 つはプロローグとして、もう 1 つは実際のコマンドとして送信する場合など)。割り当てられたバッファセットはチャネルごとであるため、2 つの別個の接続(チャネル)が別々のバッファセットを持つ場合があります。

[in] recv_buf_size: 上記のバッファセット内の個々のバッファの最大サイズ。この値はプロトコルに依存し、ピアと交換できる最大メッセージ サイズを実質的に制限します。

[in] flags: ポートの追加の動作を指定するフラグの組み合わせ

この値は、次の値の組み合わせである必要があります。

IPC_PORT_ALLOW_TA_CONNECT - 他のセキュアなアプリからの接続を許可します。

IPC_PORT_ALLOW_NS_CONNECT - 非セキュアな環境からの接続を許可します。

[retval]:負でない場合は作成されたポートへのハンドル、負の場合は特定のエラー

次に、サーバーは wait() 呼び出しを使用して、受信接続のポートハンドルのリストをポーリングします。uevent_t 構造体の event フィールドで IPC_HANDLE_POLL_READY ビットがセットされている接続要求を受信すると、サーバーは accept() を呼び出して接続の確立を終了し、受信メッセージ用にポーリングできるチャネル(別のハンドルで表現)を作成する必要があります。

accept()

受信方向の接続を受け入れ、チャンネルへのハンドルを取得します。

    long accept(uint32_t handle_id, uuid_t *peer_uuid);
    

[in] handle_id: クライアントが接続しているポートを表すハンドル

[out] peer_uuid: 接続するクライアント アプリケーションの UUID が入力される uuud_t 構造体へのポインタ接続が非セキュアな環境から開始された場合は、すべてゼロに設定されます。

[retval]: 負でない場合は、サーバーがクライアントとメッセージを交換できるチャネルへのハンドル(それ以外の場合はエラーコード)

Client API

このセクションでは、Client API のメソッドについて説明します。

Client API のメソッド

connect()

名前で指定されたポートへの接続を開始します。

    long connect(const char *path, uint flags);
    

[in] path: Trusty アプリケーションによって公開されるポートの名前

[in] flags: 追加のオプションの動作を指定します。

[retval]:サーバーとのメッセージ交換が可能なチャネルへのハンドル。負の場合はエラー。

flags が指定されない場合(flags パラメータが 0 に設定されている場合)、connect() を呼び出すことで指定されたポートへの同期接続を開始します。ポートが存在しない場合はすぐにエラーを返し、サーバーが接続を受け入れるまでブロックを作成します。

この動作は、次の 2 つの値の組み合わせを指定することで変更できます。

    enum {
    IPC_CONNECT_WAIT_FOR_PORT = 0x1,
    IPC_CONNECT_ASYNC = 0x2,
    };
    

IPC_CONNECT_WAIT_FOR_PORT - connect() 呼び出しの実行時に指定されたポートが存在しない場合に、すぐに失敗するのではなく待機するようにします。

IPC_CONNECT_ASYNC - 設定すると、非同期接続が開始されます。アプリケーションは、返されたハンドルについて(通常の動作を開始する前に、uevent_t 構造体のイベント フィールドで IPC_HANDLE_POLL_READY ビットが設定されている接続完了イベントの wait() を呼び出すことによって)ポーリングする必要があります。

Messaging API

Messaging API 呼び出しにより、以前に確立した接続(チャネル)を介したメッセージの送受信が可能になります。Messaging API 呼び出しは、サーバーとクライアントで同じです。

クライアントは connect() 呼び出しを発行してチャネルへのハンドルを受け取り、サーバーは上述の accept() 呼び出しからチャネル ハンドルを取得します。

Trusty メッセージの構造体

次に示すように、Trusty API によって交換されるメッセージは最小限の構造体を持ち、実際のコンテンツのセマンティクスに同意するかどうかはサーバーとクライアントに委ねられます。

    /*
     *  IPC message
     */
    typedef struct iovec {
            void   *base;
            size_t  len;
    } iovec_t;

    typedef struct ipc_msg {
            uint     num_iov; /* number of iovs in this message */
            iovec_t  *iov;    /* pointer to iov array */

            uint     num_handles; /* reserved, currently not supported */
            handle_t *handles;    /* reserved, currently not supported */
    } ipc_msg_t;
    

メッセージは、iovec_t 構造体の配列で表される、1 つ以上の連続しないバッファで構成できます。Trusty では、iov 配列を使用して、これらのブロックに対するスキャッター ギャザーの読み取りと書き込みを行います。iov 配列で記述できるバッファの内容は、完全に任意です。

Messaging API のメソッド

send_msg()

指定されたチャネルでメッセージを送信します。

    long send_msg(uint32_t handle, ipc_msg_t *msg);
    

[in] handle: メッセージを送信するチャネルへのハンドル

[in] msg: メッセージについて説明する ipc_msg_t structure へのポインタ

[retval]: 成功時には送信されたバイトの合計数、それ以外の場合は負のエラー

クライアント(またはサーバー)がチャネルを介してメッセージを送信しようとしたときに、宛先ピアのメッセージ キューにスペースがない場合、チャネルは送信ブロックの状態になる場合があります(これは単純な同期要求または応答プロトコルでは発生しませんが、より複雑なケースでは発生する可能性があります)。その場合は ERR_NOT_ENOUGH_BUFFER というエラーコードが返されます。 そのような場合、呼び出し元は、ピアが処理メッセージと廃棄メッセージを取得して受信キュー内のスペースを確保するまで待つ必要があります。その場合、wait() 呼び出しが返した uevent_t 構造体の event フィールドで設定された IPC_HANDLE_POLL_SEND_UNBLOCKED ビットによって示されます。

get_msg()

指定されたチャネルの受信メッセージ キューの次のメッセージに関する

メタ情報を取得します。

    long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
    

[in] handle: 新しいメッセージをそこで取得する必要があるチャネルのハンドル

[out] msg_info: 次のように記述されたメッセージ情報の構造体

    typedef struct ipc_msg_info {
            size_t    len;  /* total message length */
            uint32_t  id;   /* message id */
    } ipc_msg_info_t;
    

各メッセージには未処理のメッセージのセット全体で一意の ID が割り当てられ、各メッセージの長さの合計が入力されます。プロトコルによってそのように構成および許可されていれば、特定のチャネルに未処理(オープン中)のメッセージが複数同時に存在できます。

[retval]: 成功した場合は NO_ERROR、それ以外の場合は負のエラー

read_msg()

指定されたオフセットから開始する、指定された ID のメッセージの内容を読み取ります。

    long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t
    *msg);
    

[in] handle: メッセージの読み取り元となるチャネルのハンドル

[in] msg_id: 読み取るメッセージの ID

[in] offset: 読み取りを開始するメッセージへのオフセット

[in] msg: 受信メッセージのデータを格納する一連のバッファを記述する ipc_msg_t 構造体へのポインタ

[retval]: 成功した場合は dst バッファに格納されたバイトの合計数、それ以外の場合は負のエラー

read_msg メソッドは、必要に応じて別の(必ずしも順次ではない)オフセットから複数回呼び出すことができます。

put_msg()

指定された ID のメッセージを廃棄します。

    long put_msg(uint32_t handle, uint32_t msg_id);
    

[in] handle: メッセージが到着したチャネルのハンドル

[in] msg_id: 廃棄されるメッセージの ID

[retval]: 成功した場合は NO_ERROR、それ以外の場合は負のエラー

メッセージが廃棄され、占有されていたバッファが解放された後は、メッセージのコンテンツにはアクセスできません。

File Descriptor API

File Descriptor API には、read()write()ioctl() の各呼び出しが含まれます。これらの呼び出しはすべて、以前から小さい数で表されてきた、事前定義された(静的な)一連のファイル記述子で動作します。現在の実装では、ファイル記述子のスペースは IPC のハンドル スペースから分離されています。Trusty の File Descriptor API は、従来のファイル記述子ベースの API に似ています。

デフォルトでは、3 つの定義済み(標準的でよく知られた)ファイル記述子があります。

  • 0 - 標準入力。標準入力 fd のデフォルト実装は(信頼できるアプリケーションが対話型コンソールを持つことは想定されていないため)何もしません。そのため、fd が 0 の状態で ioctl() の読み取り、書き込み、呼び出しを行うと、ERR_NOT_SUPPORTED エラーが返ります。
  • 1 - 標準出力。標準出力に書き込まれるデータは、プラットフォームや構成に応じて、LK デバッグレベルによって非セキュア側で使用可能な UART やメモリログに転送できます。重要でないデバッグログとメッセージは標準出力に出力されます。read() メソッドと ioctl() メソッドは何もせず、ERR_NOT_SUPPORTED エラーを返します。
  • 2 - 標準エラー。標準エラーに書き込まれたデータは、プラットフォームや構成に応じて、非セキュア側で使用可能な UART やメモリログに転送されます。このストリームには制限がかからない可能性が非常に高いため、標準エラーには重要なメッセージのみを書き込むことをおすすめします。read() メソッドと ioctl() メソッドは何もせず、ERR_NOT_SUPPORTED エラーを返します。

このファイル記述子のセットを拡張して(プラットフォーム固有の拡張を実装するために)さらに多くの fds を実装することもできますが、ファイル記述子の拡張は慎重に行なう必要があります。ファイル記述子を拡張すると、競合が発生する可能性が高くなるため、通常はおすすめしていません。

File Descriptor API のメソッド

read()

指定されたファイル記述子から最大 count バイトのデータの読み取りを試みます。

    long read(uint32_t fd, void *buf, uint32_t count);
    

[in] fd: 読み取り元のファイル記述子

[out] buf: データを格納するバッファへのポインタ

[in] count: 読み取りの最大バイト数

[retval]: 返された読み取りバイト数。それ以外の場合は負のエラー

write()

指定されたファイル記述子に最大 count バイトのデータを書き込みます。

    long write(uint32_t fd, void *buf, uint32_t count);
    

[in] fd: 書き込み先のファイル記述子

[out] buf: 書き込むデータへのポインタ

[in] count: 書き込みの最大バイト数

[retval]: 返された書き込みバイト数。それ以外の場合は負のエラー

ioctl()

特定のファイル記述子の指定された ioctl コマンドを呼び出します。

    long ioctl(uint32_t fd, uint32_t cmd, void *args);
    

[in] fd: ioctl() を呼び出すファイル記述子

[in] cmd: ioctl コマンド

[in/out] args: ioctl() 引数へのポインタ

Miscellaneous API

Miscellaneous API のメソッド

gettime()

現在のシステム時刻をナノ秒単位で返します。

    long gettime(uint32_t clock_id, uint32_t flags, uint64_t *time);
    

[in] clock_id: プラットフォームに依存。デフォルトでゼロを渡します。

[in] flags: 予約済み。ゼロにする必要があります。

[in] time: 現在の時刻を格納する int64_t 値へのポインタ

[retval]: 成功した場合は NO_ERROR、それ以外の場合は負のエラー

nanosleep()

指定した期間、呼び出し元のアプリケーションの実行を停止し、その期間の終了後に再開します。

    long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
    

[in] clock_id: 予約済み。ゼロにする必要があります。

[in] flags: 予約済み。ゼロにする必要があります。

[in] sleep_time: スリープ時間(ナノ秒単位)

[retval]: 成功した場合は NO_ERROR、それ以外の場合は負のエラー

信頼できるアプリケーション サーバーの例

次のサンプル アプリケーションは、上記の API の使用法を示しています。サンプルでは、複数の受信接続を処理し、クライアントから受信した、セキュア側または非セキュア側から開始されたすべてのメッセージを呼び出し元に返す「echo」サービスを作成します。

    #include <uapi/err.h>
    #include <stdbool.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <trusty_ipc.h>
    #define LOG_TAG "echo_srv"
    #define TLOGE(fmt, ...) \
        fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ##__VA_ARGS__)

    # define MAX_ECHO_MSG_SIZE 64

    static
    const char * srv_name = "com.android.echo.srv.echo";

    static uint8_t msg_buf[MAX_ECHO_MSG_SIZE];

    /*
     *  Message handler
     */
    static int handle_msg(handle_t chan) {
      int rc;
      struct iovec iov;
      ipc_msg_t msg;
      ipc_msg_info_t msg_inf;

      iov.iov_base = msg_buf;
      iov.iov_len = sizeof(msg_buf);

      msg.num_iov = 1;
      msg.iov = &iov;
      msg.num_handles = 0;
      msg.handles = NULL;

      /* get message info */
      rc = get_msg(chan, &msg_inf);
      if (rc == ERR_NO_MSG)
        return NO_ERROR; /* no new messages */

      if (rc != NO_ERROR) {
        TLOGE("failed (%d) to get_msg for chan (%d)\n",
          rc, chan);
        return rc;
      }

      /* read msg content */
      rc = read_msg(chan, msg_inf.id, 0, &msg);
      if (rc < 0) {
        TLOGE("failed (%d) to read_msg for chan (%d)\n",
          rc, chan);
        return rc;
      }

      /* update number of bytes received */
      iov.iov_len = (size_t) rc;

      /* send message back to the caller */
      rc = send_msg(chan, &msg);
      if (rc < 0) {
        TLOGE("failed (%d) to send_msg for chan (%d)\n",
          rc, chan);
        return rc;
      }

      /* retire message */
      rc = put_msg(chan, msg_inf.id);
      if (rc != NO_ERROR) {
        TLOGE("failed (%d) to put_msg for chan (%d)\n",
          rc, chan);
        return rc;
      }

      return NO_ERROR;
    }

    /*
     *  Channel event handler
     */
    static void handle_channel_event(const uevent_t * ev) {
      int rc;

      if (ev->event & IPC_HANDLE_POLL_MSG) {
        rc = handle_msg(ev->handle);
        if (rc != NO_ERROR) {
          /* report an error and close channel */
          TLOGE("failed (%d) to handle event on channel %d\n",
            rc, ev->handle);
          close(ev->handle);
        }
        return;
      }
      if (ev->event & IPC_HANDLE_POLL_HUP) {
        /* closed by peer. */
        close(ev->handle);
        return;
      }
    }

    /*
     *  Port event handler
     */
    static void handle_port_event(const uevent_t * ev) {
      uuid_t peer_uuid;

      if ((ev->event & IPC_HANDLE_POLL_ERROR) ||
        (ev->event & IPC_HANDLE_POLL_HUP) ||
        (ev->event & IPC_HANDLE_POLL_MSG) ||
        (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) {
        /* should never happen with port handles */
        TLOGE("error event (0x%x) for port (%d)\n",
          ev->event, ev->handle);
        abort();
      }
      if (ev->event & IPC_HANDLE_POLL_READY) {
        /* incoming connection: accept it */
        int rc = accept(ev->handle, &peer_uuid);
        if (rc < 0) {
          TLOGE("failed (%d) to accept on port %d\n",
            rc, ev->handle);
          return;
        }
        handle_t chan = rc;
        while (true){
          struct uevent cev;

          rc = wait(chan, &cev, INFINITE_TIME);
          if (rc < 0) {
            TLOGE("wait returned (%d)\n", rc);
            abort();
          }
          handle_channel_event(&cev);
          if (cev.event & IPC_HANDLE_POLL_HUP) {
            return;
          }
        }
      }
    }

    /*
     *  Main application entry point
     */
    int main(void) {
      int rc;
      handle_t port;

      /* Initialize service */
      rc = port_create(srv_name, 1, MAX_ECHO_MSG_SIZE,
        IPC_PORT_ALLOW_NS_CONNECT |
        IPC_PORT_ALLOW_TA_CONNECT);
      if (rc < 0) {
        TLOGE("Failed (%d) to create port %s\n",
          rc, srv_name);
        abort();
      }
      port = (handle_t) rc;

      /* enter main event loop */
      while (true) {
        uevent_t ev;

        ev.handle = INVALID_IPC_HANDLE;
        ev.event = 0;
        ev.cookie = NULL;

        /* wait forever */
        rc = wait(port, &ev, INFINITE_TIME);
        if (rc == NO_ERROR) {
          /* got an event */
          handle_port_event(&ev);
        } else {
          TLOGE("wait returned (%d)\n", rc);
          abort();
        }
      }
      return 0;
    }
    

run_end_to_end_msg_test() メソッドは、10,000 件のメッセージを「echo」サービスに非同期で送信し、返信を処理します。

    static int run_echo_test(void)
    {
      int rc;
      handle_t chan;
      uevent_t uevt;
      uint8_t tx_buf[64];
      uint8_t rx_buf[64];
      ipc_msg_info_t inf;
      ipc_msg_t   tx_msg;
      iovec_t     tx_iov;
      ipc_msg_t   rx_msg;
      iovec_t     rx_iov;

      /* prepare tx message buffer */
      tx_iov.base = tx_buf;
      tx_iov.len  = sizeof(tx_buf);
      tx_msg.num_iov = 1;
      tx_msg.iov     = &tx_iov;
      tx_msg.num_handles = 0;
      tx_msg.handles = NULL;

      memset (tx_buf, 0x55, sizeof(tx_buf));

      /* prepare rx message buffer */
      rx_iov.base = rx_buf;
      rx_iov.len  = sizeof(rx_buf);
      rx_msg.num_iov = 1;
      rx_msg.iov     = &rx_iov;
      rx_msg.num_handles = 0;
      rx_msg.handles = NULL;

      /* open connection to echo service */
      rc = sync_connect(srv_name, 1000);
      if(rc < 0)
        return rc;

      /* got channel */
      chan = (handle_t)rc;

      /* send/receive 10000 messages asynchronously. */
      uint tx_cnt = 10000;
      uint rx_cnt = 10000;

      while (tx_cnt || rx_cnt) {
        /* send messages until all buffers are full */
    while (tx_cnt) {
        rc = send_msg(chan, &tx_msg);
          if (rc == ERR_NOT_ENOUGH_BUFFER)
          break;  /* no more space */
        if (rc != 64) {
          if (rc > 0) {
            /* incomplete send */
            rc = ERR_NOT_VALID;
    }
          goto abort_test;
    }
        tx_cnt--;
      }

      /* wait for reply msg or room */
      rc = wait(chan, &uevt, 1000);
      if (rc != NO_ERROR)
        goto abort_test;

      /* drain all messages */
      while (rx_cnt) {
        /* get a reply */
          rc = get_msg(chan, &inf);
        if (rc == ERR_NO_MSG)
            break;  /* no more messages  */
      if (rc != NO_ERROR)
    goto abort_test;

      /* read reply data */
        rc = read_msg(chan, inf.id, 0, &rx_msg);
      if (rc != 64) {
        /* unexpected reply length */
        rc = ERR_NOT_VALID;
        goto abort_test;
    }

      /* discard reply */
      rc = put_msg(chan, inf.id);
      if (rc != NO_ERROR)
        goto abort_test;
      rx_cnt--;
      }
    }

    abort_test:
      close(chan);
      return rc;
    }
    

非セキュアな環境の API とアプリケーション

セキュア側から公開され、IPC_PORT_ALLOW_NS_CONNECT 属性でマークされた一連の Trusty サービスは、非セキュア側で実行されているカーネルとユーザー空間プログラムからアクセスできます。

非セキュア側の実行環境(カーネルとユーザー空間)は、セキュア側の実行環境と大きく異なります。したがって、両方の環境には 1 つのライブラリではなく、2 つの異なる API セットがあります。カーネルでは、Client API は trusty-ipc カーネル ドライバによって提供され、セキュア側で実行されるサービスと通信するためにユーザー空間プロセスで使用できるキャラクター デバイスノードを登録します。

ユーザー空間の Trusty IPC Client API

ユーザー空間の Trusty IPC Client API ライブラリは、デバイスノード fd の最上部にあるシンレイヤです。

ユーザー空間プログラムは、tipc_connect() を呼び出して指定された Trusty サービスへの接続を初期化することで通信を開始します。内部的には、tipc_connect() 呼び出しは指定されたデバイスノードを開いてファイル記述子を取得し、接続先のサービス名を含む文字列を指す argp パラメータで TIPC_IOC_CONNECT ioctl() を呼び出します。

    #define TIPC_IOC_MAGIC  'r'
    #define TIPC_IOC_CONNECT  _IOW(TIPC_IOC_MAGIC, 0x80, char *)
    

作成されたファイル記述子は、目的のサービスとの通信にのみ使用できます。接続が不要になった場合は、tipc_close() を呼び出してファイル記述子をクローズする必要があります。

tipc_connect() 呼び出しで取得されたファイル記述子は、通常のキャラクター デバイスノードとして、次のように動作します。

  • 必要に応じて非ブロッキング モードに切り替えることができます。
  • 標準の write() 呼び出しを使用して書き込み、相手側にメッセージを送信できます。
  • 受信メッセージを通常のファイル記述子として使用できるかどうかについて、poll() 呼び出しまたは select() 呼び出しを使用してポーリングできます。
  • 読み取って受信メッセージを取得できます。

呼び出し元は、指定された fd について write 呼び出しを実行し、Trusty サービスにメッセージを送信します。上記の write() 呼び出しに渡されたすべてのデータは、trusty-ipc ドライバによってメッセージに変換されます。メッセージはセキュア側に配信されます。そこでは Trusty カーネルの IPC サブシステムによってデータが処理され、適切な宛先にルーティングされ、特定のチャネル ハンドルの IPC_HANDLE_POLL_MSG イベントとしてアプリ イベントループに配信されます。特定のサービス固有のプロトコルに応じて、Trusty サービスは 1 つ以上の応答メッセージを送信できます。そのメッセージは非セキュア側に返され、適切なチャネルのファイル記述子のメッセージキューに配置され、ユーザー空間アプリケーション read() 呼び出しによって取得されます。

tipc_connect()

指定された tipc デバイスノードを開き、指定された Trusty サービスへの接続を開始します。

    int tipc_connect(const char *dev_name, const char *srv_name);
    

[in] dev_name: 開く Trusty IPC デバイスノードへのパス

[in] srv_name: 接続先の公開されている Trusty サービスの名前

[retval]:成功した場合は有効なファイル記述子、それ以外の場合は -1。

tipc_close()

ファイル記述子で指定された Trusty サービスへの接続をクローズします。

    int tipc_close(int fd);
    

[in] fd: 以前に tipc_connect() 呼び出しが開いたファイル記述子

カーネルの Trusty IPC Client API

Trusty IPC Client API はカーネル ドライバで使用できます。ユーザー空間の Trusty IPC API は、この API の上に実装されています。

この API の一般的な使用法では、呼び出し元が tipc_create_channel() 関数と tipc_chan_connect() 呼び出しを使用して struct tipc_chan オブジェクトを作成し、セキュア側で実行されている Trusty IPC サービスへの接続を開始します。リモート側への接続は tipc_chan_shutdown() を呼び出して終了し、その後 tipc_chan_destroy() でリソースをクリーンアップします。

接続が正常に確立されたという通知を(handle_event() コールバックで)受け取ると、呼び出し元は次の処理を行います。

  • tipc_chan_get_txbuf_timeout() 呼び出しを使用してメッセージ バッファを取得します。
  • メッセージを作成します。
  • チャネルが接続されている Trusty サービス(セキュア側)に配信するために、tipc_chan_queue_msg() メソッドを使用してメッセージをキューに登録します。

キューイングが成功すると、メッセージ バッファはリモート側で処理され、最終的にフリーのバッファプールに戻されるため(後で他のメッセージに再利用するため)、呼び出し元がメッセージ バッファを使用することはなくなります。ユーザーはこのようなバッファのキューイングに失敗した場合や、バッファが不要になった場合にのみ、tipc_chan_put_txbuf() を呼び出す必要があります。

API ユーザーは、処理する受信メッセージを格納している rx バッファへのポインタを提供する handle_msg() 通知コールバック(これは trusty-ipc の rx ワークキューのコンテキストで呼び出されます)を処理して、リモート側からメッセージを受信します。

handle_msg() コールバックの実装は、有効な struct tipc_msg_buf へのポインタを返すことが期待されます。 ローカルで処理されて不要になった場合は、受信メッセージのバッファと同じになります。または、受信バッファが追加の処理のためにキューに登録された場合は、tipc_chan_get_rxbuf() 呼び出しで取得された新しいバッファになります。分離された rx バッファは追跡して、最終的に不要になった場合は tipc_chan_put_rxbuf() 呼び出しを使用して解放する必要があります。

カーネルの Trusty IPC Client API のメソッド

tipc_create_channel()

特定の trusty-ipc デバイス用に Trusty IPC チャネルのインスタンスを作成して構成します。

    struct tipc_chan *tipc_create_channel(struct device *dev,
                              const struct tipc_chan_ops *ops,
                                  void *cb_arg);
    

[in] dev: デバイス チャネルが作成された trusty-ipc へのポインタ

[in] ops: 呼び出し元固有のコールバックが入力された、struct tipc_chan_ops へのポインタ

[in] cb_arg: tipc_chan_ops コールバックに渡すデータへのポインタ

[retval]: 成功した場合は struct tipc_chan の新しく作成されたインスタンスへのポインタ、それ以外の場合は ERR_PTR(err)

通常、呼び出し元は対応するアクティビティが発生している場合、非同期に呼び出される 2 つのコールバックを提供する必要があります。

void (*handle_event)(void *cb_arg, int event) イベントが呼び出され、呼び出し元にチャネル状態の変化が通知されます。

[in] cb_arg: tipc_create_channel() 呼び出しに渡されたデータへのポインタ

[in] event: 次のいずれかの値になるイベント

  • TIPC_CHANNEL_CONNECTED - リモート側との接続が成功したことを示します。
  • TIPC_CHANNEL_DISCONNECTED - リモート側が新しい接続要求を拒否したか、以前に接続したチャネルの接続の切断を要求したことを示します。
  • TIPC_CHANNEL_SHUTDOWN - リモート側がシャットダウンし、すべての接続を永続的に終了することを示します。

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) コールバックが呼び出され、指定されたチャネルで新しいメッセージが受信されたことを通知します。

  • [in] cb_arg: tipc_create_channel() 呼び出しに渡されたデータへのポインタ
  • [in] mb: 受信メッセージについて記述している struct tipc_msg_buf へのポインタ
  • [retval]: コールバック実装は、struct tipc_msg_buf へのポインタを返すことが期待されます。このポインタは、メッセージがローカルで処理されて不要になった場合、mb パラメータとして受信されたポインタと同じになります(または、tipc_chan_get_rxbuf() 呼び出しで取得された新しいバッファになる場合もあります)。

tipc_chan_connect()

指定された Trusty IPC サービスへの接続を開始します。

    int tipc_chan_connect(struct tipc_chan *chan, const char *port);
    

[in] chan: tipc_create_chan() 呼び出しにより返されるチャネルへのポインタ

[in] port: 接続先のサービス名を含む文字列へのポインタ

[retval]: 成功した場合は 0、それ以外の場合は負のエラー

接続が確立されると、呼び出し元は handle_event コールバックによる通知を受信します。

tipc_chan_shutdown()

tipc_chan_connect() 呼び出しによって以前に開始された Trusted IPC サービスへの接続を終了します。

    int tipc_chan_shutdown(struct tipc_chan *chan);
    

[in] chan: tipc_create_chan() 呼び出しにより返されるチャネルへのポインタ

tipc_chan_destroy()

指定された Trusty IPC チャネルを破棄します。

    void tipc_chan_destroy(struct tipc_chan *chan);
    

[in] chan: tipc_create_chan() 呼び出しにより返されるチャネルへのポインタ

tipc_chan_get_txbuf_timeout()

指定されたチャネルでデータを送信するために使用できるメッセージ バッファを取得します。バッファがすぐに使用できない場合、呼び出し元は指定されたタイムアウトに応じてミリ秒単位でブロックされます。

    struct tipc_msg_buf *
    tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
    

[in] chan: メッセージをキューに登録するチャネルへのポインタ

[in] chan: tx バッファが使用可能になるまで待機する最大タイムアウト

[retval]: 成功した場合は有効なメッセージ バッファ、エラーの場合は ERR_PTR(err)

tipc_chan_queue_msg()

指定された Trusty IPC チャネルを介して送信されるメッセージをキューに登録します。

    int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
    

[in] chan: メッセージをキューに登録するチャネルへのポインタ

[in] mb: キューに登録するメッセージへのポインタ(tipc_chan_get_txbuf_timeout() 呼び出しにより取得)

[retval]: 成功した場合は 0、それ以外の場合は負のエラー

tipc_chan_put_txbuf()

以前に tipc_chan_get_txbuf_timeout() 呼び出しにより取得された、指定された Tx メッセージ バッファを解放します。

    void tipc_chan_put_txbuf(struct tipc_chan *chan,
                             struct tipc_msg_buf *mb);
    

[in] chan: このメッセージ バッファが属するチャネルへのポインタ

[in] mb: 解放するメッセージ バッファへのポインタ

[retval]: なし

tipc_chan_get_rxbuf()

指定されたチャネルでメッセージを受信するために使用できる新しいメッセージ バッファを取得します。

    struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
    

[in] chan: このメッセージ バッファが属するチャネルへのポインタ

[retval]: 成功した場合は有効なメッセージ バッファ、エラーの場合は ERR_PTR(err)

tipc_chan_put_rxbuf()

以前に tipc_chan_get_rxbuf() 呼び出しにより取得された、指定されたメッセージ バッファを解放します。

    void tipc_chan_put_rxbuf(struct tipc_chan *chan,
                             struct tipc_msg_buf *mb);
    

[in] chan: このメッセージ バッファが属するチャネルへのポインタ

[in] mb: 解放するメッセージ バッファへのポインタ

[retval]: なし