Trusty proporciona API para desarrollar dos clases de aplicaciones/servicios:
- Aplicaciones o servicios confiables que se ejecutan en el procesador TEE
- Aplicaciones normales/no confiables que se ejecutan en el procesador principal y utilizan los servicios proporcionados 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 utilizar las API de Trusty para conectarse a aplicaciones/servicios confiables e intercambiar mensajes arbitrarios con ellos como si fuera 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 a nivel de aplicación. La entrega confiable de mensajes está garantizada por la infraestructura subyacente de Trusty (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 un ID de servicio simple, basado en cadenas, para que lo utilicen los clientes. La convención de nomenclatura es la 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. Al utilizar esta ruta full-duplex, los clientes y servidores pueden intercambiar mensajes arbitrarios hasta que cualquiera de las partes decida cortar la conexión.
Sólo 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) sólo pueden conectarse a servicios publicados en el lado seguro.
Dependiendo de los requisitos, una aplicación confiable puede ser cliente y servidor al mismo tiempo. Es posible que una aplicación confiable que publica un servicio (como servidor) necesite conectarse a otros servicios (como cliente).
Manejar API
Los identificadores son números enteros sin signo que representan recursos como puertos y canales, similares a los descriptores de archivos en UNIX. Una vez creados 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 INVALID_IPC_HANDLE #define,
que una aplicación puede utilizar como indicación de que un identificador no es válido o no está configurado.
establecer_cookie()
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 arbitrarios del espacio del usuario en la aplicación Trusty
[retval]: NO_ERROR
en caso de éxito, < 0
código de error en caso contrario
Esta llamada es útil para manejar eventos cuando ocurren más adelante después de que se haya creado el identificador. El mecanismo de manejo de eventos devuelve el identificador y su cookie al controlador de eventos.
Se puede esperar a los identificadores de eventos mediante la llamada wait()
.
esperar()
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 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 se produjo ningún evento; < 0
para otros errores
Tras el éxito ( retval == NO_ERROR
), la llamada wait()
llena una estructura uevent_t
especificada 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 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 los 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 par ha cerrado un canal -
IPC_HANDLE_POLL_MSG
: indica que hay un mensaje pendiente para este canal -
IPC_HANDLE_POLL_SEND_UNBLOCKED
: indica que una persona que llama previamente con envío bloqueado puede intentar enviar un mensaje nuevamente (consulte la descripción desend_msg()
para obtener más detalles)
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 complicados. Persisten mientras persista la condición subyacente (por ejemplo, se reciben todos los mensajes pendientes y se manejan las solicitudes de conexión pendientes). La excepción es el caso del evento IPC_HANDLE_POLL_SEND_UNBLOCKED
, que se borra tras una lectura y la aplicación solo tiene una oportunidad de 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
: Manejar para destruir
[retval]: 0 si es exitoso; un error negativo en caso 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
puerto_create()
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 buffers 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 buffers necesarios depende del acuerdo de protocolo de nivel superior entre el cliente y el servidor. El número puede ser tan solo 1 en caso de un protocolo muy sincrónico (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 pueda aparecer una respuesta (por ejemplo, un mensaje como prólogo y otro como comando real). Los conjuntos de búfer asignados son por canal, por lo que dos conexiones (canales) separados 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 sus pares.
[in] flags
: una combinación de indicadores 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 detectar conexiones entrantes mediante la llamada wait()
. Al recibir una solicitud de conexión indicada por el bit IPC_HANDLE_POLL_READY
establecido en el campo event
de la estructura uevent_t
, el servidor debe llamar accept()
para terminar de establecer una conexión y crear un canal (representado por otro identificador) que luego puede ser sondeado para detectar 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
[salida] peer_uuid
: puntero a una estructura uuid_t
que se completará con el UUID de la aplicación cliente que se conecta. Se establecerá todo en ceros si la conexión se originó en un 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 de 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 flags
se establece en 0), llamar 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
: fuerza a que una llamada connect()
espere si el puerto especificado no existe inmediatamente en el momento de la ejecución, en lugar de fallar inmediatamente.
IPC_CONNECT_ASYNC
: si está configurado, inicia una conexión asincrónica. Una aplicación tiene que 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 a la API de mensajería 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 canal mediante una llamada connect()
, y un servidor obtiene un identificador de canal mediante una llamada accept()
, descrita anteriormente.
Estructura de un mensaje de confianza
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 buffers no contiguos representados por una matriz de estructuras iovec_t
. Trusty realiza lecturas y escrituras dispersas en estos bloques utilizando la matriz iov
. El contenido de los buffers que puede describir la matriz iov
es completamente arbitrario.
Métodos en la API de mensajería
enviar_msg()
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 por el cual enviar el mensaje
[in] msg
: puntero a la ipc_msg_t structure
que describe el mensaje
[retval]: número total de bytes enviados en caso de éxito; un error negativo en caso contrario
Si el cliente (o 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 envío bloqueado (esto nunca debería suceder para un protocolo simple de solicitud/respuesta síncrona). pero puede 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 recuperando los mensajes de manejo y retirada, indicado por el bit IPC_HANDLE_POLL_SEND_UNBLOCKED
establecido en el campo event
de la estructura uevent_t
devuelta por la llamada wait()
.
get_msg()
Obtiene metainformación sobre el siguiente mensaje en una cola de mensajes entrantes
de un canal específico.
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
[salida] msg_info
: estructura de información del mensaje que se describe a continuación:
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 en caso contrario
leer_msg()
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 cual leer el mensaje
[in] msg_id
: ID del mensaje a leer
[in] offset
: desplazamiento en el mensaje desde el cual comenzar a leer
[out] msg
: puntero a la estructura ipc_msg_t
que describe un conjunto de buffers en los que almacenar datos de mensajes entrantes.
[retval]: Número total de bytes almacenados en los buffers msg
en caso de éxito; un error negativo en caso contrario
El método read_msg
se puede llamar varias veces comenzando en un desplazamiento diferente (no necesariamente secuencial) según sea necesario.
poner_msg()
Retira un mensaje con un ID especificado.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: Identificador del canal al que llegó el mensaje
[in] msg_id
: ID del mensaje que se retira
[retval]: NO_ERROR
en caso de éxito; un error negativo en caso contrario
No se puede acceder al contenido del mensaje una vez que se ha retirado un mensaje y se ha liberado el búfer que ocupaba.
API de descriptor de archivos
La API de descriptor de archivos incluye llamadas read()
, write()
y 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 archivos está separado del espacio del identificador 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 archivos 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 invocarioctl()
enfd
0 debería devolver un errorERR_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 y/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 aparecer en la salida estándar. Los métodos
read()
eioctl()
no son operativos y deberían devolver un errorERR_NOT_SUPPORTED
. - 2 - error estándar. Los datos escritos en 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 sólo mensajes críticos en el error estándar, ya que es muy probable que este flujo no esté limitado. Los métodos
read()
eioctl()
no son operativos y deberían devolver un errorERR_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. Ampliar los descriptores de archivos tiende a crear conflictos y generalmente no se recomienda.
Métodos en la API del descriptor de archivos
leer()
Intenta leer hasta count
bytes de datos de un descriptor de archivo específico.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: descriptor de archivo desde el cual leer
[salida] 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 en caso contrario
escribir()
Escribe para 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
[salida] buf
: puntero a los datos a escribir
[en] count
: número máximo de bytes para escribir
[retval]: número devuelto de bytes escritos; un error negativo en caso contrario
ioctl()
Invoca un comando ioctl
específico para un descriptor de archivo determinado.
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
[entrada/salida] args
: puntero a argumentos ioctl()
API varias
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, int64_t *time);
[in] clock_id
: dependiente de la plataforma; pase cero por defecto
[en] flags
: Reservado, debe ser cero
[out] time
: puntero a un valor int64_t
en el que almacenar la hora actual
[retval]: NO_ERROR
en caso de éxito; un error negativo en caso contrario
nanosueño()
Suspende la ejecución de la aplicación que llama 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
[en] flags
: Reservado, debe ser cero
[in] sleep_time
: tiempo de sueño en nanosegundos
[retval]: NO_ERROR
en caso de éxito; un error negativo en caso contrario
Ejemplo de un servidor de aplicaciones confiable
La siguiente aplicación de ejemplo muestra el uso de las API anteriores. El ejemplo 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 asincrónica 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 kernel y del espacio de usuario que se ejecutan en el lado no seguro.
El entorno de ejecución en el lado no seguro (kernel y espacio de usuario) es drásticamente diferente del entorno de ejecución en el lado seguro. Por lo tanto, en lugar de una única biblioteca para ambos entornos, existen dos conjuntos diferentes de API. En el kernel, la API del cliente la proporciona el controlador del kernel trusty-ipc y registra un nodo de dispositivo de caracteres que pueden utilizar los procesos del espacio del usuario para comunicarse con los servicios que se ejecutan en el lado seguro.
Espacio de usuario API de cliente Trusty IPC
La biblioteca Trusty IPC Client API del espacio de usuario es una capa delgada encima del nodo del dispositivo fd
.
Un programa de espacio de usuario inicia una sesión de comunicación llamando 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 llamada TIPC_IOC_CONNECT ioctl()
con el parámetro argp
apuntando 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 fue creado. El descriptor de archivo debe cerrarse llamando tipc_close()
cuando la conexión ya no sea necesaria.
El descriptor de archivo obtenido mediante 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
write()
estándar para enviar mensajes al otro lado - Se puede sondear (mediante llamadas
poll()
o llamadasselect()
) para determinar la disponibilidad de los mensajes entrantes como un descriptor de archivo normal. - 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. Todos los datos pasados a la write()
anterior se transforman en un mensaje mediante el controlador Trusty-IPC. El mensaje se entrega al lado seguro donde los datos son manejados por el subsistema IPC 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 en particular. Dependiendo del protocolo particular, específico del servicio, 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 ser recuperados por 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 mediante una llamada tipc_connect()
API de cliente 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 al servicio Trusty IPC que se ejecuta en el lado seguro. La conexión con el lado remoto se puede finalizar llamando tipc_chan_shutdown()
seguido de tipc_chan_destroy()
para limpiar 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 utilizando el método
tipc_chan_queue_msg()
para entregarlo a un servicio Trusty (en el lado seguro), al que 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 eventualmente regresa al grupo de búfer libre después de ser procesado por el lado remoto (para reutilizarlo más adelante, para otros mensajes). El usuario solo necesita llamar tipc_chan_put_txbuf()
si no puede poner en cola dicho búfer o ya no es necesario.
Un usuario de API recibe mensajes del lado remoto manejando una devolución de llamada de notificación handle_msg()
(que se llama en el contexto de la cola de trabajo rx
de trusty-ipc) que proporciona un puntero a un búfer rx
que contiene un mensaje entrante que se debe manejar.
Se espera que la implementación de devolución de llamada handle_msg()
devuelva un puntero a una struct tipc_msg_buf
. Puede ser el 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 mediante una llamada tipc_chan_get_rxbuf()
si el búfer entrante está en cola para su posterior procesamiento. Se debe realizar un seguimiento de un búfer rx
desconectado y, finalmente, liberarlo mediante una llamada tipc_chan_put_rxbuf()
cuando ya no sea necesario.
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 cual se crea el canal del dispositivo
[in] ops
: puntero a una struct tipc_chan_ops
, con devoluciones de llamada específicas de la persona que llama completadas
[in] cb_arg
: puntero a los datos que se pasarán a las devoluciones de llamada tipc_chan_ops
[retval]: puntero a una instancia recién creada de struct tipc_chan
en caso de éxito, ERR_PTR(err)
en caso contrario
En general, una persona que llama debe proporcionar dos devoluciones de llamada que se invocan de forma asincrónica 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 los datos pasados a una llamada tipc_create_channel()
[in] event
: un evento que puede tener 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 para el canal previamente conectado -
TIPC_CHANNEL_SHUTDOWN
: indica que el lado remoto se está apagando, finalizando permanentemente todas las conexiones
La devolución de llamada struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
se invoca para proporcionar una notificación de 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 llamadatipc_create_channel()
- [in]
mb
: puntero a unastruct 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 parámetromb
si el mensaje se maneja localmente y ya no es necesario (o puede ser un nuevo búfer obtenido por eltipc_chan_get_rxbuf()
llamada)
tipc_chan_connect()
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()
[en] port
: puntero a una cadena que contiene el nombre del servicio al que conectarse
[retval]: 0 en caso de éxito, un error negativo en caso 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 mediante 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 utilizar para enviar datos a través de un canal específico. Si el búfer no está disponible inmediatamente, la persona que llama puede ser 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 al que poner en cola un mensaje
[in] chan
: Tiempo de espera máximo para esperar hasta que el búfer de 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 que se enviará 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 al que poner en cola el mensaje
[in] mb:
puntero al mensaje en cola (obtenido mediante una llamada tipc_chan_get_txbuf_timeout()
)
[retval]: 0 en caso de éxito, un error negativo en caso contrario
tipc_chan_put_txbuf()
Libera el búfer de mensajes Tx
especificado obtenido previamente mediante 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 que se va a liberar
[retval]: Ninguno
tipc_chan_get_rxbuf()
Obtiene un nuevo búfer de mensajes que se puede utilizar 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 específico obtenido previamente mediante 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
[retval]: Ninguno