Referencia de la API de confianza

Trusty proporciona API para desarrollar dos clases de aplicaciones/servicios:

  • Aplicaciones o servicios de confianza que se ejecutan en el procesador TEE
  • Aplicaciones normales/no confiables que se ejecutan en el procesador principal y usan los servicios provistos por aplicaciones confiables

La API de Trusty generalmente describe el sistema de comunicación entre procesos (IPC) de Trusty, incluidas las comunicaciones con el mundo no seguro. El software que se ejecuta en el procesador principal puede usar las API de Trusty para conectarse a aplicaciones/servicios confiables e intercambiar mensajes arbitrarios con ellos como un servicio de red a través de IP. Depende de la aplicación determinar el formato de datos y la semántica de estos mensajes utilizando un protocolo de nivel de aplicación. La entrega confiable de mensajes está garantizada por la infraestructura Trusty subyacente (en forma de controladores que se ejecutan en el procesador principal), y la comunicación es completamente asíncrona.

puertos y canales

Las aplicaciones Trusty utilizan los puertos para exponer los puntos finales del servicio en forma de una ruta con nombre a la que se conectan los clientes. Esto proporciona una identificación de servicio simple basada en cadenas para que la usen los clientes. La convención de nomenclatura es de estilo DNS inverso, por ejemplo, com.google.servicename .

Cuando un cliente se conecta a un puerto, el cliente recibe un canal para interactuar con un servicio. El servicio debe aceptar una conexión entrante y, cuando lo hace, también recibe un canal. En esencia, los puertos se utilizan para buscar servicios y luego la comunicación se produce a través de un par de canales conectados (es decir, instancias de conexión en un puerto). Cuando un cliente se conecta a un puerto, se establece una conexión bidireccional simétrica. Usando esta ruta de dúplex completo, los clientes y los servidores pueden intercambiar mensajes arbitrarios hasta que cualquiera de los lados decida interrumpir la conexión.

Solo las aplicaciones confiables del lado seguro o los módulos del kernel Trusty pueden crear puertos. Las aplicaciones que se ejecutan en el lado no seguro (en el mundo normal) solo pueden conectarse a los servicios publicados por el lado seguro.

Según los requisitos, una aplicación de confianza puede ser cliente y servidor al mismo tiempo. Una aplicación de confianza que publica un servicio (como servidor) puede necesitar conectarse a otros servicios (como cliente).

Manejar API

Los identificadores son enteros sin signo que representan recursos como puertos y canales, similares a los descriptores de archivos en UNIX. Una vez que se crean los identificadores, se colocan en una tabla de identificadores específica de la aplicación y se puede hacer referencia a ellos más adelante.

Una persona que llama puede asociar datos privados con un identificador utilizando el método set_cookie() .

Métodos en la API Handle

Los identificadores solo son válidos en el contexto de una aplicación. Una aplicación no debe pasar el valor de un identificador a otras aplicaciones a menos que se especifique explícitamente. Un valor de identificador solo debe interpretarse comparándolo con el INVALID_IPC_HANDLE #define, que una aplicación puede usar como una indicación de que un identificador no es válido o no está configurado.

Asocia los datos privados proporcionados por la persona que llama con un identificador específico.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : cualquier identificador devuelto por una de las llamadas a la API

[in] cookie : Puntero a datos de espacio de usuario arbitrarios en la aplicación Trusty

[retval]: NO_ERROR en caso de éxito, < 0 código de error de lo contrario

Esta llamada es útil para manejar eventos cuando ocurren en un momento posterior después de que se haya creado el identificador. El mecanismo de gestión de eventos proporciona el identificador y su cookie al controlador de eventos.

Se puede esperar a los identificadores de eventos mediante el uso de la llamada wait() .

Espere()

Espera a que ocurra un evento en un identificador determinado durante un período de tiempo específico.

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

[in] handle_id : cualquier identificador devuelto por una de las llamadas a la API

[out] event : un puntero a la estructura que representa un evento que ocurrió en este identificador

[in] timeout_msecs : un valor de tiempo de espera en milisegundos; un valor de -1 es un tiempo de espera infinito

[retval]: NO_ERROR si ocurrió un evento válido dentro de un intervalo de tiempo de espera especificado; ERR_TIMED_OUT si transcurrió un tiempo de espera especificado pero no ocurrió ningún evento; < 0 para otros errores

Tras el éxito ( retval == NO_ERROR ), la llamada wait() llena una estructura uevent_t específica con información sobre el evento que ocurrió.

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;

El campo de event contiene una combinación de los siguientes valores:

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 : en realidad no hay eventos pendientes, la persona que llama debe reiniciar la espera

IPC_HANDLE_POLL_ERROR : se ha producido un error interno no especificado

IPC_HANDLE_POLL_READY : depende del tipo de identificador, de la siguiente manera:

  • Para puertos, este valor indica que hay una conexión pendiente
  • Para los canales, este valor indica que se estableció una conexión asíncrona (ver connect() )

Los siguientes eventos solo son relevantes para los canales:

  • IPC_HANDLE_POLL_HUP : indica que un canal ha sido cerrado por un par
  • IPC_HANDLE_POLL_MSG : indica que hay un mensaje pendiente para este canal
  • IPC_HANDLE_POLL_SEND_UNBLOCKED : indica que una persona que anteriormente bloqueó el envío de llamadas puede intentar enviar un mensaje nuevamente (consulte la descripción de send_msg() para obtener más información).

Un controlador de eventos debe estar preparado para manejar una combinación de eventos específicos, ya que se pueden configurar varios bits al mismo tiempo. Por ejemplo, para un canal, es posible tener mensajes pendientes y una conexión cerrada por un par al mismo tiempo.

La mayoría de los eventos son pegajosos. Persisten mientras persista la condición subyacente (por ejemplo, se reciben todos los mensajes pendientes y se gestionan las solicitudes de conexión pendientes). La excepción es el caso del evento IPC_HANDLE_POLL_SEND_UNBLOCKED , que se borra con una lectura y la aplicación solo tiene una oportunidad para manejarlo.

Los identificadores se pueden destruir llamando al método close() .

cerca()

Destruye el recurso asociado con el identificador especificado y lo elimina de la tabla de identificadores.

long close(uint32_t handle_id);

[in] handle_id : Identificador para destruir

[retval]: 0 si tiene éxito; un error negativo de lo contrario

API del servidor

Un servidor comienza creando uno o más puertos con nombre que representan sus puntos finales de servicio. Cada puerto está representado por un identificador.

Métodos en la API del servidor

crear_puerto()

Crea un puerto de servicio con nombre.

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

[in] path : el nombre de cadena del puerto (como se describe anteriormente). Este nombre debe ser único en todo el sistema; los intentos de crear un duplicado fallarán.

[in] num_recv_bufs : el número máximo de búferes que un canal en este puerto puede preasignar para facilitar el intercambio de datos con el cliente. Los búferes se cuentan por separado para los datos que van en ambas direcciones, por lo que especificar 1 aquí significaría que 1 búfer de envío y 1 de recepción están preasignados. En general, la cantidad de búfer necesarios depende del acuerdo de protocolo de nivel superior entre el cliente y el servidor. El número puede ser tan pequeño como 1 en caso de un protocolo muy síncrono (enviar mensaje, recibir respuesta antes de enviar otro). Pero el número puede ser mayor si el cliente espera enviar más de un mensaje antes de que aparezca una respuesta (por ejemplo, un mensaje como prólogo y otro como el comando real). Los conjuntos de búfer asignados son por canal, por lo que dos conexiones separadas (canales) tendrían conjuntos de búfer separados.

[in] recv_buf_size : tamaño máximo de cada búfer individual en el conjunto de búfer anterior. Este valor depende del protocolo y limita efectivamente el tamaño máximo de mensaje que puede intercambiar con pares

[in] flags : una combinación de banderas que especifica el comportamiento del puerto adicional

Este valor debe ser una combinación de los siguientes valores:

IPC_PORT_ALLOW_TA_CONNECT : permite una conexión desde otras aplicaciones seguras

IPC_PORT_ALLOW_NS_CONNECT - permite una conexión desde el mundo no seguro

[retval]: identificador del puerto creado si no es negativo o un error específico si es negativo

Luego, el servidor sondea la lista de identificadores de puertos para las conexiones entrantes mediante la llamada wait() . Al recibir una solicitud de conexión indicada por el conjunto de bits IPC_HANDLE_POLL_READY en el campo de event de la estructura uevent_t , el servidor debe llamar a accept() para terminar de establecer una conexión y crear un canal (representado por otro identificador) que luego se puede sondear para los mensajes entrantes. .

aceptar()

Acepta una conexión entrante y obtiene un identificador de un canal.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : Identificador que representa el puerto al que se ha conectado un cliente

[out] peer_uuid : Puntero a una estructura uuud_t que se completará con el UUID de la aplicación cliente que se conecta. Se establecerá en ceros si la conexión se originó en el mundo no seguro.

[retval]: identificador de un canal (si no es negativo) en el que el servidor puede intercambiar mensajes con el cliente (o un código de error en caso contrario)

API de cliente

Esta sección contiene los métodos en la API del cliente.

Métodos en la API del cliente

conectar()

Inicia una conexión a un puerto especificado por nombre.

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

[in] path : Nombre de un puerto publicado por una aplicación Trusty

[in] flags : especifica un comportamiento opcional adicional

[retval]: identificador de un canal a través del cual se pueden intercambiar mensajes con el servidor; error si es negativo

Si no se especifican flags (el parámetro de flags se establece en 0), llamar a connect() inicia una conexión síncrona a un puerto específico que devuelve inmediatamente un error si el puerto no existe y crea un bloque hasta que el servidor acepte una conexión. .

Este comportamiento se puede modificar especificando una combinación de dos valores, que se describen a continuación:

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

IPC_CONNECT_WAIT_FOR_PORT : obliga a una llamada connect() a esperar si el puerto especificado no existe inmediatamente en la ejecución, en lugar de fallar inmediatamente.

IPC_CONNECT_ASYNC : si se establece, inicia una conexión asíncrona. Una aplicación debe sondear el identificador devuelto (llamando a wait() para un evento de finalización de conexión indicado por el bit IPC_HANDLE_POLL_READY establecido en el campo de evento de la estructura uevent_t antes de comenzar la operación normal).

API de mensajería

Las llamadas de Messaging API permiten el envío y lectura de mensajes a través de una conexión (canal) previamente establecida. Las llamadas a la API de mensajería son las mismas para servidores y clientes.

Un cliente recibe un identificador de un canal emitiendo una llamada connect() , y un servidor obtiene un identificador de canal de una llamada accept() , descrita anteriormente.

Estructura de un mensaje Trusty

Como se muestra a continuación, los mensajes intercambiados por Trusty API tienen una estructura mínima, dejando que el servidor y el cliente acuerden la semántica del contenido real:

/*
 *  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;

Un mensaje puede estar compuesto por uno o más búferes no contiguos representados por una matriz de estructuras iovec_t . Trusty realiza lecturas y escrituras de dispersión y recopilación en estos bloques utilizando la matriz iov . El contenido de los búferes que puede describir la matriz iov es completamente arbitrario.

Métodos en la API de mensajería

enviar_mensaje()

Envía un mensaje a través de un canal específico.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Identificador del canal a través del cual enviar el mensaje

[in] msg : Puntero a la ipc_msg_t structure describe el mensaje

[retval]: número total de bytes enviados en caso de éxito; un error negativo de lo contrario

Si el cliente (o el servidor) está intentando enviar un mensaje a través del canal y no hay espacio en la cola de mensajes del par de destino, el canal podría entrar en un estado de bloqueo de envío (esto nunca debería suceder para un protocolo simple de solicitud/respuesta síncrona). pero podría ocurrir en casos más complicados) que se indica al devolver un código de error ERR_NOT_ENOUGH_BUFFER . En tal caso, la persona que llama debe esperar hasta que el par libere algo de espacio en su cola de recepción al recuperar los mensajes de manejo y retiro, indicado por el bit IPC_HANDLE_POLL_SEND_UNBLOCKED establecido en el campo de event de la estructura uevent_t devuelto por la llamada wait() .

get_msg()

Obtiene metainformación sobre el siguiente mensaje en una cola de mensajes entrantes

de un canal especificado.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Identificador del canal en el que se debe recuperar un nuevo mensaje

[out] msg_info : estructura de información del mensaje descrita de la siguiente manera:

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

A cada mensaje se le asigna una ID única en el conjunto de mensajes pendientes y se completa la longitud total de cada mensaje. Si el protocolo lo configura y lo permite, puede haber varios mensajes pendientes (abiertos) a la vez para un canal en particular.

[retval]: NO_ERROR en caso de éxito; un error negativo de lo contrario

leer_mensaje()

Lee el contenido del mensaje con el ID especificado a partir del desplazamiento especificado.

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

[in] handle : Identificador del canal desde el que leer el mensaje

[in] msg_id : ID del mensaje a leer

[in] offset : Desplazamiento en el mensaje desde el cual comenzar a leer

[in] msg : Puntero a la estructura ipc_msg_t que describe un conjunto de búferes en los que almacenar datos de mensajes entrantes

[retval]: número total de bytes almacenados en los búferes dst en caso de éxito; un error negativo de lo contrario

El método read_msg se puede llamar varias veces a partir de un desplazamiento diferente (no necesariamente secuencial) según sea necesario.

poner_mensaje()

Retira un mensaje con un ID especificado.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Identificador del canal por el que ha llegado el mensaje

[in] msg_id : ID del mensaje que se retira

[retval]: NO_ERROR en caso de éxito; un error negativo de lo contrario

No se puede acceder al contenido del mensaje después de que se haya retirado un mensaje y se haya liberado el búfer que ocupaba.

API de descriptor de archivo

La API File Descriptor incluye llamadas read() , write() e ioctl() . Todas estas llamadas pueden operar en un conjunto predefinido (estático) de descriptores de archivos tradicionalmente representados por números pequeños. En la implementación actual, el espacio del descriptor de archivo está separado del espacio de identificador de IPC. La API de descriptor de archivos en Trusty es similar a una API tradicional basada en descriptores de archivos.

De forma predeterminada, hay 3 descriptores de archivo predefinidos (estándar y conocidos):

  • 0 - entrada estándar. La implementación predeterminada de la entrada estándar fd no es operativa (ya que no se espera que las aplicaciones confiables tengan una consola interactiva), por lo que leer, escribir o invocar ioctl() en fd 0 debería devolver un error ERR_NOT_SUPPORTED .
  • 1 - salida estándar. Los datos escritos en la salida estándar se pueden enrutar (según el nivel de depuración de LK) a UART o a un registro de memoria disponible en el lado no seguro, según la plataforma y la configuración. Los registros y mensajes de depuración no críticos deben ir en la salida estándar. Los métodos read() e ioctl() no son operativos y deberían devolver un error ERR_NOT_SUPPORTED .
  • 2 - error estándar. Los datos escritos con error estándar deben enrutarse al UART o al registro de memoria disponible en el lado no seguro, según la plataforma y la configuración. Se recomienda escribir solo mensajes críticos para el error estándar, ya que es muy probable que esta transmisión no se limite. Los métodos read() e ioctl() no son operativos y deberían devolver un error ERR_NOT_SUPPORTED .

Aunque este conjunto de descriptores de archivos se puede ampliar para implementar más fds (para implementar extensiones específicas de la plataforma), la ampliación de los descriptores de archivos debe realizarse con precaución. La ampliación de los descriptores de archivos es propensa a crear conflictos y, por lo general, no se recomienda.

Métodos en la API de descriptor de archivos

leer()

Intenta leer hasta count bytes de datos de un descriptor de archivo especificado.

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

[in] fd : descriptor de archivo desde el que leer

[out] buf : Puntero a un búfer en el que almacenar datos

[en] count : número máximo de bytes para leer

[retval]: Número devuelto de bytes leídos; un error negativo de lo contrario

escribe()

Escribe hasta count bytes de datos en el descriptor de archivo especificado.

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

[in] fd : descriptor de archivo en el que escribir

[out] buf : Puntero a datos para escribir

[in] count : número máximo de bytes para escribir

[retval]: Número devuelto de bytes escritos; un error negativo de lo contrario

ioctl()

Invoca un comando ioctl especificado para un descriptor de archivo dado.

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

[in] fd : descriptor de archivo en el que invocar ioctl()

[en] cmd : el comando ioctl

[in/out] args : Puntero a argumentos ioctl()

API miscelánea

Métodos en la API miscelánea

consigue tiempo()

Devuelve la hora actual del sistema (en nanosegundos).

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

[in] clock_id : depende de la plataforma; pasar cero por defecto

[in] flags : Reservado, debe ser cero

[in] time : Puntero a un valor int64_t en el que almacenar la hora actual

[retval]: NO_ERROR en caso de éxito; un error negativo de lo contrario

nanosueño()

Suspende la ejecución de la aplicación de llamada durante un período de tiempo específico y la reanuda después de ese período.

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

[in] clock_id : Reservado, debe ser cero

[in] flags : Reservado, debe ser cero

[in] sleep_time : tiempo de sueño en nanosegundos

[retval]: NO_ERROR en caso de éxito; un error negativo de lo contrario

Ejemplo de un servidor de aplicaciones de confianza

La siguiente aplicación de ejemplo muestra el uso de las API anteriores. La muestra crea un servicio de "eco" que maneja múltiples conexiones entrantes y refleja a la persona que llama todos los mensajes que recibe de los clientes originados desde el lado seguro o no seguro.

#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;
}

El método run_end_to_end_msg_test() envía 10 000 mensajes de forma asíncrona al servicio "echo" y maneja las respuestas.

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 y aplicaciones del mundo no seguro

Un conjunto de servicios Trusty, publicados desde el lado seguro y marcados con el atributo IPC_PORT_ALLOW_NS_CONNECT , son accesibles para los programas del núcleo y del espacio de usuario que se ejecutan en el lado no seguro.

El entorno de ejecución en el lado no seguro (núcleo y espacio de usuario) es drásticamente diferente del entorno de ejecución en el lado seguro. Por lo tanto, en lugar de una sola biblioteca para ambos entornos, existen dos conjuntos diferentes de API. En el kernel, la API del cliente es proporcionada por el controlador del kernel trusty-ipc y registra un nodo de dispositivo de caracteres que pueden usar los procesos del espacio del usuario para comunicarse con los servicios que se ejecutan en el lado seguro.

Espacio de usuario Trusty IPC Client API

La biblioteca API de Trusty IPC Client del espacio de usuario es una capa delgada sobre el nodo del dispositivo fd .

Un programa de espacio de usuario inicia una sesión de comunicación llamando a tipc_connect() , inicializando una conexión a un servicio Trusty específico. Internamente, la llamada tipc_connect() abre un nodo de dispositivo específico para obtener un descriptor de archivo e invoca una TIPC_IOC_CONNECT ioctl() con el parámetro argp que apunta a una cadena que contiene un nombre de servicio al que conectarse.

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

El descriptor de archivo resultante solo se puede utilizar para comunicarse con el servicio para el que se creó. El descriptor del archivo debe cerrarse llamando a tipc_close() cuando ya no se requiera la conexión.

El descriptor de archivo obtenido por la llamada tipc_connect() se comporta como un nodo de dispositivo de caracteres típico; el descriptor del archivo:

  • Se puede cambiar al modo sin bloqueo si es necesario
  • Se puede escribir usando una llamada de write() para enviar mensajes al otro lado
  • Se puede sondear (usando llamadas poll() o llamadas select() ) para conocer la disponibilidad de los mensajes entrantes como un descriptor de archivo regular
  • Se puede leer para recuperar mensajes entrantes

Una persona que llama envía un mensaje al servicio Trusty ejecutando una llamada de escritura para el fd especificado. El controlador trusty-ipc transforma todos los datos pasados ​​a la llamada write() anterior en un mensaje. El mensaje se entrega al lado seguro donde el subsistema IPC maneja los datos en el kernel Trusty y se enruta al destino adecuado y se entrega a un bucle de eventos de la aplicación como un evento IPC_HANDLE_POLL_MSG en un identificador de canal particular. Dependiendo del protocolo específico del servicio en particular, el servicio Trusty puede enviar uno o más mensajes de respuesta que se devuelven al lado no seguro y se colocan en la cola de mensajes del descriptor de archivo de canal apropiado para que los recupere la aplicación de espacio de usuario read() llamar.

tipc_conectar()

Abre un nodo de dispositivo tipc específico e inicia una conexión a un servicio Trusty específico.

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

[in] dev_name : Ruta al nodo del dispositivo Trusty IPC para abrir

[in] srv_name : Nombre de un servicio Trusty publicado al que conectarse

[retval]: descriptor de archivo válido en caso de éxito, -1 en caso contrario.

tipc_close()

Cierra la conexión al servicio Trusty especificado por un descriptor de archivo.

int tipc_close(int fd);

[in] fd : descriptor de archivo abierto previamente por una llamada tipc_connect()

API de cliente de Kernel Trusty IPC

La API del cliente Trusty IPC del kernel está disponible para los controladores del kernel. La API Trusty IPC del espacio de usuario se implementa sobre esta API.

En general, el uso típico de esta API consiste en que una persona que llama crea un objeto struct tipc_chan usando la función tipc_create_channel() y luego usa la llamada tipc_chan_connect() para iniciar una conexión con el servicio Trusty IPC que se ejecuta en el lado seguro. La conexión con el lado remoto se puede terminar llamando a tipc_chan_shutdown() seguido de tipc_chan_destroy() para limpiar los recursos.

Al recibir una notificación (a través de la devolución de llamada handle_event() ) de que se ha establecido correctamente una conexión, la persona que llama hace lo siguiente:

  • Obtiene un búfer de mensajes usando la llamada tipc_chan_get_txbuf_timeout()
  • Redacta un mensaje y
  • Pone en cola el mensaje usando el método tipc_chan_queue_msg() para entregarlo a un servicio Trusty (en el lado seguro), al cual está conectado el canal

Después de que la cola sea exitosa, la persona que llama debe olvidar el búfer de mensajes porque el búfer de mensajes finalmente regresa al grupo de búfer libre después de procesarlo en el lado remoto (para reutilizarlo más tarde, para otros mensajes). El usuario solo necesita llamar a tipc_chan_put_txbuf() si no puede poner en cola dicho búfer o si ya no es necesario.

Un usuario de la API recibe mensajes del lado remoto mediante el manejo de una devolución de llamada de notificación handle_msg() (que se llama en el contexto de la cola de trabajo trusty-ipc rx ) que proporciona un puntero a un búfer rx que contiene un mensaje entrante para ser manejado.

Se espera que la implementación de devolución de llamada handle_msg() devuelva un puntero a una struct tipc_msg_buf válida. Puede ser lo mismo que el búfer de mensajes entrantes si se maneja localmente y ya no es necesario. Alternativamente, puede ser un nuevo búfer obtenido por una llamada tipc_chan_get_rxbuf() si el búfer entrante está en cola para su posterior procesamiento. Se debe rastrear un búfer rx desconectado y, finalmente, liberarlo mediante una llamada tipc_chan_put_rxbuf() cuando ya no se necesite.

Métodos en la API del cliente Kernel Trusty IPC

tipc_create_channel()

Crea y configura una instancia de un canal Trusty IPC para un dispositivo trusty-ipc en particular.

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

[in] dev : Puntero al trusty-ipc para el que se crea el canal del dispositivo

[in] ops : Puntero a una struct tipc_chan_ops , con devoluciones de llamadas específicas de la persona que llama rellenadas

[in] cb_arg : Puntero a los datos que se pasarán a las devoluciones de llamada de tipc_chan_ops

[retval]: Puntero a una instancia recién creada de struct tipc_chan en caso de éxito, ERR_PTR(err) de lo contrario

En general, una persona que llama debe proporcionar dos devoluciones de llamada que se invocan de forma asíncrona cuando se produce la actividad correspondiente.

El void (*handle_event)(void *cb_arg, int event) se invoca para notificar a la persona que llama sobre un cambio de estado del canal.

[in] cb_arg : Puntero a datos pasados ​​a una llamada tipc_create_channel()

[in] event : Un evento que puede ser uno de los siguientes valores:

  • TIPC_CHANNEL_CONNECTED : indica una conexión exitosa con el lado remoto
  • TIPC_CHANNEL_DISCONNECTED : indica que el lado remoto denegó la nueva solicitud de conexión o solicitó la desconexión del canal previamente conectado
  • TIPC_CHANNEL_SHUTDOWN : indica que el lado remoto se está apagando, terminando permanentemente todas las conexiones

La devolución de struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) se invoca para notificar que se ha recibido un nuevo mensaje a través de un canal específico:

  • [in] cb_arg : Puntero a los datos pasados ​​a la llamada tipc_create_channel()
  • [in] mb : Puntero a una struct tipc_msg_buf que describe un mensaje entrante
  • [retval]: se espera que la implementación de devolución de llamada devuelva un puntero a una struct tipc_msg_buf que puede ser el mismo puntero recibido como un parámetro mb si el mensaje se maneja localmente y ya no es necesario (o puede ser un nuevo búfer obtenido por el tipc_chan_get_rxbuf() llamada)

tipc_chan_conectar()

Inicia una conexión con el servicio Trusty IPC especificado.

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

[in] chan : Puntero a un canal devuelto por la llamada tipc_create_chan()

[in] port : Puntero a una cadena que contiene el nombre del servicio al que conectarse

[retval]: 0 en caso de éxito, un error negativo de lo contrario

La persona que llama recibe una notificación cuando se establece una conexión al recibir una devolución de llamada handle_event .

tipc_chan_shutdown()

Finaliza una conexión al servicio Trusty IPC iniciada previamente por una llamada tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Puntero a un canal devuelto por una llamada tipc_create_chan()

tipc_chan_destroy()

Destruye un canal Trusty IPC especificado.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : Puntero a un canal devuelto por la llamada tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Obtiene un búfer de mensajes que se puede usar para enviar datos a través de un canal específico. Si el búfer no está disponible inmediatamente, la persona que llama puede quedar bloqueada durante el tiempo de espera especificado (en milisegundos).

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

[in] chan : Puntero al canal en el que poner en cola un mensaje

[in] chan : tiempo de espera máximo para esperar hasta que el búfer tx esté disponible

[retval]: un búfer de mensajes válido en caso de éxito, ERR_PTR(err) en caso de error

tipc_chan_queue_msg()

Pone en cola un mensaje para enviarlo a través de los canales Trusty IPC especificados.

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

[in] chan : Puntero al canal en el que poner en cola el mensaje

[in] mb: Puntero al mensaje a la cola (obtenido por una llamada tipc_chan_get_txbuf_timeout() )

[retval]: 0 en caso de éxito, un error negativo de lo contrario

tipc_chan_put_txbuf()

Libera el búfer de mensajes Tx especificado obtenido previamente por una llamada tipc_chan_get_txbuf_timeout() .

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

[in] chan : Puntero al canal al que pertenece este búfer de mensajes

[in] mb : Puntero al búfer de mensajes para liberar

[recuperación]: Ninguno

tipc_chan_get_rxbuf()

Obtiene un nuevo búfer de mensajes que se puede usar para recibir mensajes a través del canal especificado.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Puntero a un canal al que pertenece este búfer de mensajes

[retval]: un búfer de mensajes válido en caso de éxito, ERR_PTR(err) en caso de error

tipc_chan_put_rxbuf()

Libera un búfer de mensajes especificado obtenido previamente por una llamada tipc_chan_get_rxbuf() .

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

[in] chan : Puntero a un canal al que pertenece este búfer de mensajes

[in] mb : Puntero a un búfer de mensajes para liberar

[recuperación]: Ninguno