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 イベントの場合は例外で、読み取り時にクリアされます。アプリケーションがこれを処理する機会は一度しかありません。

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

close()

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

long close(uint32_t handle_id);

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

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

Server API

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

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]: なし