Trusty API 参考文档

Trusty 提供了用于开发两类应用和服务的 API

  • 在 TEE 处理器上运行的可信应用和服务
  • 在主处理器上运行并使用可信应用提供的服务的普通和不可信应用

Trusty API 通常描述 Trusty 进程间通信 (IPC) 系统,包括与非安全世界的通信。在主处理器上运行的软件可以使用 Trusty API 连接到可信应用和服务,并像通过 IP 的网络服务一样与它们交换任意消息。消息的数据格式和语义由应用级协议中的应用决定。消息的可靠传递由底层 Trusty 基础架构(以在主处理器上运行的驱动程序的形式)保证,并且通信是完全异步的。

端口和通道

端口由 Trusty 应用使用,以服务端点的形式公开命名路径,客户端可以连接到该路径。这为客户端提供了基于字符串的简单服务 ID 以供使用。命名约定是反向 DNS 样式命名,例如 com.google.servicename

当客户端连接到端口时,客户端会收到一个用于与服务交互的通道。服务必须接受传入连接,接受后,服务也会收到一个通道。本质上,端口用于查找服务,然后通过一对连接的通道(即端口上的连接实例)进行通信。当客户端连接到端口时,会建立对称的双向连接。使用此全双工路径,客户端和服务器可以交换任意消息,直到任一方决定断开连接。

只有安全侧的可信应用或 Trusty 内核模块才能创建端口。在非安全侧(在正常世界中)运行的应用只能连接到安全侧发布的服务。

根据需求,可信应用可以同时充当客户端和服务器。发布服务(作为服务器)的可信应用可能需要连接到其他服务(作为客户端)。

句柄 API

句柄是表示端口和通道等资源的无符号整数,类似于 UNIX 中的文件描述符。创建句柄后,它们会放入特定于应用的句柄表,以便稍后引用。

调用方可以使用 set_cookie() 方法将私有数据与句柄关联。

句柄 API 中的方法

句柄仅在应用的上下文中有效。除非明确指定,否则应用不应将句柄的值传递给其他应用。句柄值只能通过将其与 INVALID_IPC_HANDLE #define 进行比较来解释,应用可以使用它来指示句柄无效或未设置。

将调用方提供的私有数据与指定的句柄关联。

long set_cookie(uint32_t handle, void *cookie)

[输入] handle:API 调用之一返回的任何句柄

[输入] cookie:指向 Trusty 应用中任意用户空间数据的指针

[返回值]:成功时为 NO_ERROR,否则为 < 0 错误代码

当事件在创建句柄后的稍后时间发生时,此调用对于处理事件很有用。事件处理机制会将句柄及其 Cookie 返回给事件处理程序。

可以使用 wait() 调用等待句柄上的事件。

wait()

等待给定句柄上的事件在指定时间段内发生。

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

[输入] handle_id:API 调用之一返回的任何句柄

[输出] event:指向表示在此句柄上发生的事件的结构的指针

[输入] timeout_msecs:超时值(以毫秒为单位);值 -1 表示无限超时

[返回值]:如果在指定的超时时间间隔内发生有效事件,则返回 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);

[输入] handle_id:要销毁的句柄

[返回值]:成功时为 0;否则为负错误

服务器 API

服务器首先创建一个或多个命名端口,这些端口表示其服务端点。每个端口都由一个句柄表示。

服务器 API 中的方法

port_create()

创建命名的服务端口。

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

[输入] path:端口的字符串名称(如上所述)。此名称在整个系统中应是唯一的;尝试创建重复项将失败。

[输入] num_recv_bufs:此端口上的通道可以预先分配的最大缓冲区数,以方便与客户端交换数据。缓冲区的计数对于两个方向的数据是分开的,因此在此处指定 1 将表示预先分配 1 个发送缓冲区和 1 个接收缓冲区。通常,所需的缓冲区数取决于客户端和服务器之间的高级协议约定。在非常同步的协议(发送消息,接收回复,然后再发送另一条消息)的情况下,该数字可以少至 1。但是,如果客户端希望在回复出现之前发送多条消息(例如,一条消息作为序言,另一条消息作为实际命令),则该数字可能会更大。分配的缓冲区集是按通道划分的,因此两个单独的连接(通道)将具有单独的缓冲区集。

[输入] recv_buf_size:上述缓冲区集中每个单独缓冲区的最大大小。此值取决于协议,并有效地限制了您可以与对等方交换的最大消息大小

[输入] flags:指定其他端口行为的标志组合

此值应为以下值的组合

IPC_PORT_ALLOW_TA_CONNECT - 允许来自其他安全应用的连接

IPC_PORT_ALLOW_NS_CONNECT - 允许来自非安全世界的连接

[返回值]:如果为非负数,则为创建的端口的句柄;如果为负数,则为特定错误

然后,服务器使用 wait() 调用轮询端口句柄列表以查找传入连接。在收到连接请求(由 uevent_t 结构的 event 字段中设置的 IPC_HANDLE_POLL_READY 位指示)后,服务器应调用 accept() 以完成建立连接并创建通道(由另一个句柄表示),然后可以轮询该通道以查找传入消息。

accept()

接受传入连接并获取通道的句柄。

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[输入] handle_id:表示客户端已连接到的端口的句柄

[输出] peer_uuid:指向要填充连接客户端应用的 UUID 的 uuid_t 结构的指针。如果连接源自非安全世界,则设置为全零

[返回值]:如果为非负数,则为服务器可以在其上与客户端交换消息的通道的句柄(否则为错误代码)

客户端 API

本节包含客户端 API 中的方法。

客户端 API 中的方法

connect()

启动与按名称指定的端口的连接。

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

[输入] path:Trusty 应用发布的端口的名称

[输入] flags:指定其他可选行为

[返回值]:可以通过其与服务器交换消息的通道的句柄;如果为负数,则为错误

如果未指定 flagsflags 参数设置为 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 位指示),然后再开始正常操作。

消息传递 API

消息传递 API 调用支持通过先前建立的连接(通道)发送和读取消息。消息传递 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 数组描述的缓冲区的内容完全是任意的。

消息传递 API 中的方法

send_msg()

通过指定的通道发送消息。

long send_msg(uint32_t handle, ipc_msg_t *msg);

[输入] handle:要通过其发送消息的通道的句柄

[输入] msg:指向描述消息的 ipc_msg_t structure 的指针

[返回值]:成功时发送的总字节数;否则为负错误

如果客户端(或服务器)尝试通过通道发送消息,并且目标对等方消息队列中没有空间,则通道可能会进入发送阻止状态(对于简单的同步请求/回复协议,这永远不应发生,但在更复杂的情况下可能会发生),这通过返回 ERR_NOT_ENOUGH_BUFFER 错误代码来指示。在这种情况下,调用方必须等待,直到对等方通过检索处理和停用消息来释放其接收队列中的一些空间,这由 uevent_t 结构的 event 字段中设置的 IPC_HANDLE_POLL_SEND_UNBLOCKED 位(由 wait() 调用返回)指示。

get_msg()

获取有关传入消息队列中下一条消息的元信息

指定通道的元信息。

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[输入] handle:必须在其上检索新消息的通道的句柄

[输出] msg_info:消息信息结构,描述如下

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

每条消息都在一组未完成的消息中分配了一个唯一 ID,并填充了每条消息的总长度。如果协议配置并允许,则对于特定通道,可以同时存在多条未完成(已打开)的消息。

[返回值]:成功时为 NO_ERROR;否则为负错误

read_msg()

从指定的偏移量开始读取具有指定 ID 的消息的内容。

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

[输入] handle:要从中读取消息的通道的句柄

[输入] msg_id:要读取的消息的 ID

[输入] offset:从其开始读取的消息偏移量

[输出] msg:指向描述一组缓冲区的 ipc_msg_t 结构的指针,这些缓冲区用于存储传入的消息数据

[返回值]:成功时存储在 msg 缓冲区中的总字节数;否则为负错误

可以根据需要多次调用 read_msg 方法,从不同的(不一定是顺序的)偏移量开始。

put_msg()

停用具有指定 ID 的消息。

long put_msg(uint32_t handle, uint32_t msg_id);

[输入] handle:消息已到达的通道的句柄

[输入] msg_id:要停用的消息的 ID

[返回值]:成功时为 NO_ERROR;否则为负错误

在消息停用且其占用的缓冲区已释放后,将无法访问消息内容。

文件描述符 API

文件描述符 API 包括 read()write()ioctl() 调用。所有这些调用都可以在传统上由小数字表示的预定义(静态)文件描述符集上运行。在当前实现中,文件描述符空间与 IPC 句柄空间是分开的。Trusty 中的文件描述符 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);

[输入] fd:要从中读取的文件描述符

[输出] buf:指向要将数据存储到其中的缓冲区的指针

[输入] count:要读取的最大字节数

[返回值]:返回的读取字节数;否则为负错误

write()

将最多 count 个字节的数据写入指定的文件描述符。

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

[输入] fd:要写入的文件描述符

[输出] buf:指向要写入的数据的指针

[输入] count:要写入的最大字节数

[返回值]:返回的写入字节数;否则为负错误

ioctl()

为给定的文件描述符调用指定的 ioctl 命令。

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

[输入] fd:要在其上调用 ioctl() 的文件描述符

[输入] cmdioctl 命令

[输入/输出] args:指向 ioctl() 参数的指针

其他 API

其他 API 中的方法

gettime()

返回当前系统时间(以纳秒为单位)。

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

[输入] clock_id:平台相关;对于默认值,传递零

[输入] flags:保留,应为零

[输出] time:指向 int64_t 值的指针,当前时间将存储到该值中

[返回值]:成功时为 NO_ERROR;否则为负错误

nanosleep()

暂停调用应用的执行指定时间段,并在该时间段后恢复执行。

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

[输入] clock_id:保留,应为零

[输入] flags:保留,应为零

[输入] sleep_time:睡眠时间(以纳秒为单位)

[返回值]:成功时为 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 和应用

一组 Trusty 服务(从安全侧发布并标有 IPC_PORT_ALLOW_NS_CONNECT 属性)可供在非安全侧运行的内核和用户空间程序访问。

非安全侧(内核和用户空间)的执行环境与安全侧的执行环境截然不同。因此,不是针对两个环境使用单个库,而是使用两组不同的 API。在内核中,客户端 API 由 trusty-ipc 内核驱动程序提供,并注册一个字符设备节点,用户空间进程可以使用该节点与在安全侧运行的服务通信。

用户空间 Trusty IPC 客户端 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 事件传递到应用事件循环。根据特定的、特定于服务的协议,Trusty 服务可能会发送一条或多条回复消息,这些消息被传递回非安全端,并放置在相应的通道文件描述符消息队列中,以供用户空间应用 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 客户端 API

内核 Trusty IPC 客户端 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() 方法对消息进行排队,以便传递到(安全端的)Trusty 服务,通道已连接到该服务

成功排队后,调用者应忘记消息缓冲区,因为消息缓冲区最终会在远程端处理后返回到空闲缓冲区池(以便稍后重用,用于其他消息)。用户只需在无法对缓冲区进行排队或不再需要缓冲区时调用 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() 调用释放。

内核 Trusty IPC 客户端 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)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 的指针,如果消息在本地处理且不再需要(或者它可以是通过 tipc_chan_get_rxbuf() 调用获得的新缓冲区),则该指针可以与作为 mb 参数接收的指针相同

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]:无