Trusty API 參考資料

Trusty 提供 API,可用於開發兩類應用程式和服務:

  • 在 TEE 處理器上執行的可信任應用程式和服務
  • 在主處理器上執行的一般和不受信任的應用程式,並使用受信任的應用程式提供的服務

Trusty API 通常會說明 Trusty 處理序間通訊 (IPC) 系統,包括與非安全環境的通訊。在主處理器上執行的軟體可以使用 Trusty API 連線至信任的應用程式和服務,並與這些應用程式和服務交換任意訊息,就像透過 IP 的網路服務一樣。應用程式可使用應用程式層級通訊協定,判斷這些訊息的資料格式和語意用語。底層 Trusty 基礎架構 (以在主處理器上執行的驅動程式形式) 可確保可靠地傳送訊息,且通訊完全非同步。

連接埠和管道

Trusty 應用程式會使用通訊埠,以命名路徑的形式公開服務端點,供用戶端連線。這會提供簡單的字串型服務 ID,供用戶端使用。命名慣例是反向 DNS 樣式的命名,例如 com.google.servicename

當用戶端連線到通訊埠時,會收到用於與服務互動的管道。服務必須接受傳入的連線,並在收到通道時接收通道。基本上,通訊埠用於查詢服務,然後透過一組已連結的管道 (即連線執行個體)。當用戶端連線至通訊埠時,系統會建立對稱的雙向連線。使用這個全雙工路徑,用戶端和伺服器可以交換任意訊息,直到其中一方決定中斷連線為止。

只有安全端信任的應用程式或 Trusty 核心模組可以建立端口。在非安全端 (一般環境) 執行的應用程式只能連線至由安全端發布的服務。

視需求而定,受信任的應用程式可以同時是用戶端和伺服器。發布服務 (做為伺服器) 的受信任應用程式可能需要連線至其他服務 (做為用戶端)。

處理 API

句柄是未簽署的整數,代表端口和管道等資源,類似於 UNIX 中的檔案描述元。建立句柄後,系統會將其放入應用程式專屬的句柄表格,以便日後參照。

呼叫端可以使用 set_cookie() 方法,將私人資料與句柄建立關聯。

Handle API 中的各項方法

句柄僅在應用程式用途中有效。除非明確指定,否則應用程式不應將句柄的值傳遞至其他應用程式。只有在與 INVALID_IPC_HANDLE #define, 進行比較時,才應解讀句柄值。應用程式可使用 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() 的說明)

事件處理常式應準備好處理指定事件的組合,因為可能會同時設定多個位元。舉例來說,管道可能會有待處理訊息,且同時由同級機器關閉連線。

大部分事件都會保留。只要基礎條件持續存在 (例如收到所有待處理訊息,並處理待處理的連線要求),這些項目就會持續存在。例外狀況是 IPC_HANDLE_POLL_SEND_UNBLOCKED 事件,這類事件會在讀取時清除,且應用程式只有一次機會處理這類事件。

您可以呼叫 close() 方法來銷毀句柄。

close()

摧毀與指定句柄相關聯的資源,並從句柄表格中移除。

long close(uint32_t handle_id);

[in] handle_id:要銷毀的句柄

[retval]:成功則為 0,否則為負數錯誤

伺服器 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。但如果客戶端希望在回覆出現前傳送多則訊息 (例如一則訊息做為序言,另一則做為實際指令),則數量可能會更多。系統會為每個管道分配緩衝區集,因此兩個獨立的連線 (管道) 會有不同的緩衝區集。

[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_t 結構的指標,該結構會填入連線用戶端應用程式的 UUID。如果連線源自非安全世界,則會設為全零

[retval]:管道句柄 (如果非負值),伺服器可透過該管道與用戶端交換訊息 (否則為錯誤代碼)

用戶端 API

本節包含 Client API 中的各項方法。

Client API 中的方法

connect()

透過名稱指定的通訊埠啟動連線。

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

[in] path:由 Trusty 應用程式發布的通訊埠名稱

[in] flags:指定其他選用行為

[retval]:可用於與伺服器交換訊息的管道句柄;如果為負值,則表示錯誤

如果未指定 flags (flags 參數設為 0),呼叫 connect() 會啟動與指定通訊埠的同步連線,如果通訊埠不存在,就會立即傳回錯誤,並建立一個區塊,直到伺服器接受其他連線為止。

您可以指定兩個值的組合,藉此變更這項行為,如下所述:

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

IPC_CONNECT_WAIT_FOR_PORT:如果指定的連接埠在執行時不立即存在,則強制 connect() 呼叫等待,而非立即失敗。

IPC_CONNECT_ASYNC:如果已設為啟用,就會啟動非同步連線。應用程式必須先呼叫 wait() 以取得連線完成事件,然後再對該事件進行輪詢,以便在開始正常運作之前,取得 uevent_t 結構體事件欄位中設定的 IPC_HANDLE_POLL_READY 位元所指示的事件。

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 結構體陣列表示。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:在郵件中偏移,從哪裡開始讀取

[out] msg:指向 ipc_msg_t 結構的指標,該結構描述一組緩衝區,用於儲存傳入的訊息資料

[retval]:成功時,儲存在 msg 緩衝區中的位元組總數;否則為負值錯誤

read_msg 方法可視需要從不同的偏移值 (不一定是連續) 開始多次呼叫。

put_msg()

退回含有指定 ID 的郵件。

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle:訊息已抵達的管道帳號代碼

[in] msg_id:要停用的訊息 ID

[retval]:成功時為 NO_ERROR,否則為負值錯誤

訊息退役後,系統會釋放其所佔用的緩衝區,因此無法存取訊息內容。

檔案描述元 API

檔案描述元 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 (實作特定平台的擴充功能),但擴充檔案描述元時必須謹慎操作。擴充檔案描述元件容易造成衝突,因此一般不建議這麼做。

檔案描述元 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] cmdioctl 指令

[in/out] args:指向 ioctl() 引數

其他 API

Miscellaneous API 中的各項方法

gettime()

傳回目前的系統時間 (以奈秒為單位)。

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

[in] clock_id:平台相關;傳遞零為預設值

[in] flags:保留欄位,應為零

[out] 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 app 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 服務,可供在非安全端執行的核心和使用者空間程式存取。

非安全端 (核心和使用者空間) 的執行環境與安全端的執行環境截然不同。因此,這兩種環境都會使用兩組不同的 API,而非單一程式庫。在核心中,Client API 是由 trusty-ipc 核心驅動程式提供,並註冊字元裝置節點,供使用者空間程序用於與安全端執行的服務通訊。

使用者空間 Trusty IPC Client API

使用者空間 Trusty IPC 用戶端 API 程式庫是裝置節點 fd 之上的薄層。

使用者空間程式會呼叫 tipc_connect() 來啟動通訊工作階段,並初始化與指定 Trusty 服務的連線。在內部,tipc_connect() 呼叫會開啟指定的裝置節點,以取得檔案描述符,並呼叫 TIPC_IOC_CONNECT ioctl() 呼叫,其中 argp 參數會指向包含要連線服務名稱的字串。

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

產生的檔案描述元只能用於與建立檔案的服務通訊。當不再需要連線時,請呼叫 tipc_close() 來關閉檔案描述元。

tipc_connect() 呼叫取得的檔案描述元會以一般字元裝置節點的形式運作;檔案描述元:

  • 可視需要切換至非封鎖模式
  • 可使用標準 write() 呼叫寫入,以便將訊息傳送至另一端
  • 可透過輪詢 (使用 poll() 呼叫或 select() 呼叫) 確認是否有可用來做為一般檔案描述元的內送訊息。
  • 可讀取,以便擷取收到的訊息

呼叫端會針對指定的 fd 執行寫入呼叫,藉此將訊息傳送至 Trusty 服務。所有傳遞至上述 write() 呼叫的資料都會由 trusty-ipc 驅動程式轉換為訊息。訊息會傳送至安全端,在該端,Trusty 核心中的 IPC 子系統會處理資料,並將資料路由至適當的目的端,然後以特定管道句柄的 IPC_HANDLE_POLL_MSG 事件形式傳送至應用程式事件迴圈。視特定服務專屬的特定通訊協定而定,信任服務可能會傳送一或多個回覆訊息,這些訊息會傳送回非安全端,並放置在適當的管道檔案描述元訊息佇列中,供使用者空間應用程式 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() 呼叫開啟的檔案描述元

Kernel Trusty IPC Client API

核心 Trusty IPC Client API 可供核心驅動程式使用。使用者空間 Trusty IPC API 是在這個 API 之上實作。

一般來說,這個 API 的典型用法包括呼叫端使用 tipc_create_channel() 函式建立 struct tipc_chan 物件,然後使用 tipc_chan_connect() 呼叫,以便與在安全端執行的 Trusty IPC 服務建立連線。您可以呼叫 tipc_chan_shutdown() 並接著呼叫 tipc_chan_destroy() 來清理資源,藉此終止與遠端端點的連線。

在收到通知 (透過 handle_event() 回呼) 表示已成功建立連線後,呼叫端會執行以下操作:

  • 使用 tipc_chan_get_txbuf_timeout() 呼叫取得訊息緩衝區
  • 撰寫郵件,然後
  • 使用 tipc_chan_queue_msg() 方法將訊息排入佇列,以便傳送至信任服務 (在安全端),該服務與管道相連

排隊成功後,呼叫端應忘記訊息緩衝區,因為訊息緩衝區在遠端處理後,最終會傳回至空閒緩衝區 (以便日後重複使用,用於其他訊息)。只有在排入此緩衝區失敗或不再需要時,使用者才需要呼叫 tipc_chan_put_txbuf()

API 使用者會透過處理 handle_msg() 通知回呼 (在 trusty-ipc rx 工作佇列的情況下呼叫) 接收遠端傳送的訊息,該回呼會提供指向 rx 緩衝區的指標,其中包含要處理的傳入訊息。

handle_msg() 回呼實作項目應會傳回有效 struct tipc_msg_buf 的指標。如果在本機處理並不再需要,則可與傳入訊息緩衝區相同。或者,如果傳入的緩衝區已排入佇列以供進一步處理,則可以是 tipc_chan_get_rxbuf() 呼叫取得的新緩衝區。您必須追蹤已分離的 rx 緩衝區,並在不再需要時使用 tipc_chan_put_rxbuf() 呼叫來釋出。

Kernel 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)

一般來說,呼叫端必須提供兩個回呼,在發生對應活動時非同步叫用。

系統會叫用 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() 呼叫啟動的 Trusty 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]:無