O Trusty oferece APIs para desenvolver duas classes de apps e serviços:
- Apps e serviços confiáveis executados no processador TEE
- Apps normais e não confiáveis que são executados no processador principal e usam os serviços fornecidos por apps confiáveis
A API Trusty geralmente descreve o sistema de comunicação entre processos (IPC) do Trusty, incluindo comunicações com o mundo não seguro. O software executado no processador principal pode usar APIs Trusty para se conectar a apps e serviços confiáveis e trocar mensagens arbitrárias com eles, assim como um serviço de rede por IP. Cabe ao app determinar o formato de dados e a semântica dessas mensagens usando um protocolo no nível do app. A entrega confiável de mensagens é garantida pela infraestrutura Trusty (na forma de drivers executados no processador principal), e a comunicação é totalmente assíncrona.
Portas e canais
As portas são usadas por apps do Trusty para expor endpoints de serviço na forma
de um caminho nomeado ao qual os clientes se conectam. Isso fornece um ID de serviço simples e baseado em string para os clientes usarem. A convenção de nomenclatura é do estilo DNS reverso,
por exemplo, com.google.servicename
.
Quando um cliente se conecta a uma porta, ele recebe um canal para interagir com um serviço. O serviço precisa aceitar uma conexão de entrada e, quando o faz, também recebe um canal. Em essência, as portas são usadas para procurar serviços e, em seguida, a comunicação ocorre por um par de canais conectados (ou seja, instâncias de conexão em uma porta). Quando um cliente se conecta a uma porta, uma conexão simétrica bidirecional é estabelecida. Usando esse caminho full-duplex, clientes e servidores podem trocar mensagens arbitrárias até que qualquer lado decida encerrar a conexão.
Somente apps confiáveis do lado seguro ou módulos do kernel Trusty podem criar portas. Os apps executados no lado não seguro (no mundo normal) só podem se conectar a serviços publicados pelo lado seguro.
Dependendo dos requisitos, um app confiável pode ser um cliente e um servidor ao mesmo tempo. Um app confiável que publica um serviço (como um servidor) pode precisar se conectar a outros serviços (como um cliente).
API Handle
Os identificadores são números inteiros não assinados que representam recursos, como portas e canais, de forma semelhante aos descritores de arquivos no UNIX. Depois que os identificadores são criados, eles são colocados em uma tabela de identificadores específica do app e podem ser referenciados mais tarde.
Um autor da chamada pode associar dados privados a um identificador usando
o método set_cookie()
.
Métodos na API Handle
Os identificadores só são válidos no contexto de um app. Um app não pode
transmitir o valor de um identificador para outros apps, a menos que seja especificado
explicitamente. Um valor de identificador só pode ser interpretado comparando-o com
o INVALID_IPC_HANDLE #define,
, que um app pode usar como uma
indicação de que um identificador é inválido ou não definido.
set_cookie()
Associa os dados privados fornecidos pelo autor da chamada a um identificador especificado.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: qualquer identificador retornado por uma das chamadas de API
[in] cookie
: ponteiro para dados arbitrários do espaço do usuário no app Trusty
[retval]: NO_ERROR
em caso de sucesso, código de erro < 0
, caso contrário
Essa chamada é útil para processar eventos quando eles ocorrem mais tarde, depois que o identificador foi criado. O mecanismo de processamento de eventos fornece o identificador e o cookie de volta ao manipulador de eventos.
É possível aguardar eventos para identificadores usando a chamada wait()
.
wait()
Aguarda que um evento ocorra em um determinado identificador por um período especificado.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: qualquer identificador retornado por uma das chamadas de API
[out] event
: um ponteiro para a estrutura que representa
um evento ocorrido nesse identificador
[in] timeout_msecs
: um valor de tempo limite em milissegundos. Um
valor de -1 é um tempo limite infinito.
[retval]: NO_ERROR
se um evento válido ocorreu em um
intervalo de tempo limite especificado. ERR_TIMED_OUT
se um tempo limite especificado expirou, mas nenhum
evento ocorreu. < 0
para outros erros.
Após o sucesso (retval == NO_ERROR
), a chamada wait()
preenche uma estrutura uevent_t
especificada com informações sobre
o evento que ocorreu.
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;
O campo event
contém uma combinação dos seguintes 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
: nenhum evento está pendente.
O autor da chamada precisa reiniciar a espera.
IPC_HANDLE_POLL_ERROR
: ocorreu um erro interno não especificado
IPC_HANDLE_POLL_READY
: depende do tipo de identificador, conforme abaixo:
- Para portas, esse valor indica que há uma conexão pendente.
- Para canais, esse valor indica que uma conexão assíncrona
(consulte
connect()
) foi estabelecida.
Os eventos a seguir são relevantes apenas para canais:
IPC_HANDLE_POLL_HUP
: indica que um canal foi fechado por um parIPC_HANDLE_POLL_MSG
: indica que há uma mensagem pendente para esse canalIPC_HANDLE_POLL_SEND_UNBLOCKED
: indica que um autor da chamada bloqueado anteriormente pode tentar enviar uma mensagem de novo. Consulte a descrição desend_msg()
para saber mais.
Um gerenciador de eventos precisa estar preparado para processar uma combinação de eventos especificados, já que vários bits podem ser definidos ao mesmo tempo. Por exemplo, em um canal, é possível ter mensagens pendentes e uma conexão fechada por um peer ao mesmo tempo.
A maioria dos eventos é fixa. Elas persistem enquanto a condição
subjacente persistir. Por exemplo, todas as mensagens pendentes são recebidas e as solicitações de conexão
pendentes são processadas. A exceção é o caso do
evento IPC_HANDLE_POLL_SEND_UNBLOCKED
, que
é limpo após uma leitura, e o app tem apenas uma chance de
processá-lo.
Os identificadores podem ser destruídos chamando o método close()
.
close()
Destrói o recurso associado ao identificador especificado e o remove da tabela de identificadores.
long close(uint32_t handle_id);
[in] handle_id
: identificador para destruir
[retval]: 0 em caso de sucesso. Caso contrário, um erro negativo
API Server
Um servidor começa criando uma ou mais portas nomeadas que representam os endpoints do serviço. Cada porta é representada por um identificador.
Métodos na API Server
port_create()
Cria uma porta de serviço nomeada.
long port_create (const char *path, uint num_recv_bufs, size_t recv_buf_size, uint32_t flags)
[in] path
: o nome da string da porta (conforme descrito acima). Esse
nome precisa ser exclusivo no sistema. As tentativas de criar uma duplicação falham.
[in] num_recv_bufs
: o número máximo de buffers que um canal nessa porta pode pré-alocar para facilitar a troca de dados com o cliente. Os buffers são contados
separadamente para dados que vão em ambas as direções. Portanto, especificar 1 aqui significa que 1
buffer de envio e 1 de recebimento são pré-alocados. Em geral, o número de buffers
necessários depende do contrato de protocolo de nível superior entre o cliente e o
servidor. O número pode ser tão baixo quanto 1 no caso de um protocolo muito síncrono
(enviar mensagem, receber resposta antes de enviar outra). Mas o número pode ser
maior se o cliente espera enviar mais de uma mensagem antes que uma resposta seja
mostrada (por exemplo, uma mensagem como prólogo e outra como o comando real). Os
conjuntos de buffer alocados são por canal. Portanto, duas conexões (canais)
separadas teriam conjuntos de buffer separados.
[in] recv_buf_size
: tamanho máximo de cada buffer individual no
conjunto de buffers acima. Esse valor depende do protocolo e limita o tamanho máximo da mensagem que você pode trocar com o peer.
[in] flags
: uma combinação de flags que especifica o comportamento adicional da porta
Esse valor precisa ser uma combinação dos seguintes valores:
IPC_PORT_ALLOW_TA_CONNECT
: permite uma conexão de outros apps seguros
IPC_PORT_ALLOW_NS_CONNECT
: permite uma conexão do mundo não seguro
[retval]: identificador da porta criada se não for negativo ou um erro específico se for negativo
Em seguida, o servidor pesquisa a lista de identificadores de porta para conexões recebidas
usando a chamada wait()
. Ao receber uma solicitação de conexão
indicada pelo bit IPC_HANDLE_POLL_READY
definido no
campo event
da estrutura uevent_t
, o
servidor precisa chamar accept()
para concluir o estabelecimento de uma conexão e criar um
canal (representado por
outro identificador) que pode ser consultado para mensagens recebidas.
accept()
Aceita uma conexão de entrada e recebe um identificador de um canal.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: identificador que representa a porta à qual um cliente se conectou
[out] peer_uuid
: ponteiro para uma estrutura uuid_t
a ser
preenchida com o UUID do app cliente de conexão. Ele
é definido como todos os zeros se a conexão tiver origem no mundo não seguro.
[retval]: identificador de um canal (se não for negativo) em que o servidor pode trocar mensagens com o cliente (ou um código de erro, caso contrário)
API do cliente
Esta seção contém os métodos na API Client.
Métodos na API Client
connect()
Inicia uma conexão com uma porta especificada por nome.
long connect(const char *path, uint flags);
[in] path
: nome de uma porta publicada por um app do Trusty
[in] flags
: especifica um comportamento opcional adicional
[retval]: identificador de um canal em que as mensagens podem ser trocadas com o servidor; erro se negativo
Se nenhum flags
for especificado (o parâmetro flags
for definido como 0), a chamada connect()
inicia uma conexão síncrona
para uma porta especificada que retorna imediatamente
um erro se a porta não existir e cria um bloqueio até que o
servidor aceite uma conexão.
Esse comportamento pode ser alterado especificando uma combinação de dois valores, conforme descrito abaixo:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
: força uma chamada connect()
a aguardar se a porta especificada não existir imediatamente na execução,
em vez de falhar imediatamente.
IPC_CONNECT_ASYNC
: se definido, inicia uma conexão assíncrona. Um
app precisa detectar
o identificador retornado chamando wait()
para
um evento de conclusão de conexão indicado pelo bit IPC_HANDLE_POLL_READY
definido no campo de evento da estrutura uevent_t
antes de iniciar
a operação normal.
API Messaging
As chamadas da API Messaging permitem o envio e a leitura de mensagens por uma conexão (canal) estabelecida anteriormente. As chamadas da API Messaging são as mesmas para servidores e clientes.
Um cliente recebe um identificador de canal emitindo uma chamada connect()
, e um servidor recebe um identificador de canal de uma chamada accept()
,
como descrito acima.
Estrutura de uma mensagem do Trusty
Como mostrado abaixo, as mensagens trocadas pela API Trusty têm uma estrutura mínima, deixando que o servidor e o cliente concordem com a semântica do conteúdo 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;
Uma mensagem pode ser composta por um ou mais buffers não contíguos representados por
uma matriz de estruturas iovec_t
. O Trusty realiza leituras e gravações de dispersão
nesses blocos
usando a matriz iov
. O conteúdo dos buffers que podem ser descritos
pela matriz iov
é completamente arbitrário.
Métodos na API Messaging
send_msg()
Envia uma mensagem por um canal especificado.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: identificador do canal pelo qual a mensagem será enviada
[in] msg
: ponteiro para o ipc_msg_t structure
que descreve a mensagem
[retval]: número total de bytes enviados em caso de sucesso. Caso contrário, uma mensagem de erro negativa
Se o cliente (ou servidor) estiver tentando enviar uma mensagem pelo canal e
não houver espaço na fila de mensagens de peer de destino, o canal poderá
entrar em um estado de envio bloqueado (isso nunca deve acontecer em um protocolo
de solicitação/resposta síncrono simples, mas pode acontecer em casos mais complicados) que é
indicado pelo retorno de um código de erro ERR_NOT_ENOUGH_BUFFER
.
Nesse caso, o autor da chamada precisa esperar até que o peer libere algum
espaço na fila de recebimento, recuperando as mensagens de processamento e desativação,
indicadas pelo bit IPC_HANDLE_POLL_SEND_UNBLOCKED
definido no
campo event
da estrutura uevent_t
retornado pela chamada wait()
.
get_msg()
Recebe metainformações sobre a próxima mensagem em uma fila de mensagens recebidas
de um canal específico.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: identificador do canal em que uma nova mensagem precisa ser recuperada
[out] msg_info
: a estrutura de informações da mensagem é descrita da seguinte forma:
typedef struct ipc_msg_info { size_t len; /* total message length */ uint32_t id; /* message id */ } ipc_msg_info_t;
Cada mensagem recebe um ID exclusivo no conjunto de mensagens pendentes, e o comprimento total de cada mensagem é preenchido. Se configurado e permitido pelo protocolo, pode haver várias mensagens pendentes (abertas) de uma só vez para um canal específico.
[retval]: NO_ERROR
em caso de sucesso. Caso contrário, um erro negativo
read_msg()
Lê o conteúdo da mensagem com o ID especificado a partir do deslocamento especificado.
long read_msg(uint32_t handle, uint32_t msg_id, uint32_t offset, ipc_msg_t *msg);
[in] handle
: identificador do canal em que a mensagem será lida
[in] msg_id
: ID da mensagem a ser lida
[in] offset
: deslocamento na mensagem a partir da qual começar a ler
[out] msg
: ponteiro para a estrutura ipc_msg_t
que descreve
um conjunto de buffers para armazenar dados de mensagens
de entrada.
[retval]: número total de bytes armazenados nos buffers msg
em
sucesso. Caso contrário, uma mensagem de erro negativa.
O método read_msg
pode ser chamado várias vezes, começando em
um deslocamento diferente (não necessariamente
sequência) conforme necessário.
put_msg()
Retira uma mensagem com um ID especificado.
long put_msg(uint32_t handle, uint32_t msg_id);
[in] handle
: identificador do canal em que a mensagem chegou
[in] msg_id
: ID da mensagem que está sendo desativada
[retval]: NO_ERROR
em caso de sucesso. Caso contrário, um erro negativo
O conteúdo da mensagem não pode ser acessado depois que ela é desativada e o buffer que ela ocupa é liberado.
API File Descriptor
A API File Descriptor inclui chamadas read()
, write()
e ioctl()
. Todas essas chamadas podem operar em um conjunto predefinido (estático) de descritores
de arquivos tradicionalmente representados por números pequenos. Na implementação
atual, o espaço do descritor de arquivo é separado do espaço do identificador
IPC. A API File Descriptor no Trusty é
semelhante a uma API tradicional baseada em descritor de arquivo.
Por padrão, há três descritores de arquivos predefinidos (padrão e conhecidos):
- 0: entrada padrão. A implementação padrão da entrada padrão
fd
é uma operação nula, já que não é esperado que apps confiáveis tenham um console interativo. Portanto, ler, gravar ou invocarioctl()
emfd
0 deve retornar um erroERR_NOT_SUPPORTED
. - 1 - saída padrão. Os dados gravados na saída padrão podem ser roteados (dependendo
do nível de depuração do LK) para UART e/ou um registro de memória disponível no lado não seguro, dependendo da plataforma e da configuração. Os registros e mensagens de depuração não críticos
precisam ser enviados para a saída padrão. Os métodos
read()
eioctl()
são no-ops e precisam retornar um erroERR_NOT_SUPPORTED
. - 2 - erro padrão. Os dados gravados no erro padrão precisam ser roteados para o registro de UART
ou de memória disponível no lado não seguro, dependendo da plataforma e
da configuração. É recomendável gravar apenas mensagens críticas no erro
padrão, porque é muito provável que esse fluxo não seja limitado. Os métodos
read()
eioctl()
são no-ops e precisam retornar um erroERR_NOT_SUPPORTED
.
Embora esse conjunto de descritores de arquivos possa ser estendido para implementar mais
fds
(para implementar extensões específicas da plataforma), a extensão de descritores de arquivos precisa
ser feita com cuidado. A extensão de descritores de arquivos tende a criar
conflitos e geralmente não é recomendada.
Métodos na API File Descriptor
read()
Tenta ler até count
bytes de dados de um descritor de arquivo especificado.
long read(uint32_t fd, void *buf, uint32_t count);
[in] fd
: descritor de arquivo a ser lido
[out] buf
: ponteiro para um buffer em que armazenar dados
[in] count
: número máximo de bytes a serem lidos
[retval]: número de bytes lidos retornados; caso contrário, uma mensagem de erro negativa
write()
Grava até count
bytes de dados no descritor de arquivo especificado.
long write(uint32_t fd, void *buf, uint32_t count);
[in] fd
: descritor de arquivo em que gravar
[out] buf
: ponteiro para dados a serem gravados
[in] count
: número máximo de bytes a serem gravados
[retval]: número de bytes gravados retornado. Caso contrário, um erro negativo
ioctl()
Invoca um comando ioctl
especificado para um determinado descritor de arquivo.
long ioctl(uint32_t fd, uint32_t cmd, void *args);
[in] fd
: descritor de arquivo em que ioctl()
será invocado
[in] cmd
: o comando ioctl
[in/out] args
: ponteiro para argumentos ioctl()
API Miscellaneous
Métodos na API Miscellaneous
gettime()
Retorna o horário atual do sistema (em nanossegundos).
long gettime(uint32_t clock_id, uint32_t flags, int64_t *time);
[in] clock_id
: depende da plataforma. Transmita zero para o padrão
[in] flags
: reservado, precisa ser zero
[out] time
: ponteiro para um valor int64_t
em que armazenar o horário atual
[retval]: NO_ERROR
em caso de sucesso. Caso contrário, um erro negativo
nanosleep()
Suspende a execução do app de chamada por um período especificado e a retoma após esse período.
long nanosleep(uint32_t clock_id, uint32_t flags, uint64_t sleep_time)
[in] clock_id
: reservado, precisa ser zero
[in] flags
: reservado, precisa ser zero
[in] sleep_time
: tempo de suspensão em nanossegundos
[retval]: NO_ERROR
em caso de sucesso. Caso contrário, um erro negativo
Exemplo de um servidor de apps confiável
O app de exemplo a seguir mostra o uso das APIs acima. O exemplo cria um serviço "eco" que processa várias conexões de entrada e reflete de volta ao autor da chamada todas as mensagens recebidas de clientes originadas do lado seguro ou não 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 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; }
O método run_end_to_end_msg_test()
envia 10.000 mensagens de forma assíncrona
para o serviço "echo" e processa
as respostas.
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; }
APIs e apps do mundo não seguro
Um conjunto de serviços Trusty, publicados do lado seguro e marcados com
o atributo IPC_PORT_ALLOW_NS_CONNECT
, é acessível para o kernel
e programas de espaço do usuário em execução no
lado não seguro.
O ambiente de execução no lado não seguro (kernel e espaço do usuário) é drasticamente diferente do ambiente de execução no lado seguro. Portanto, em vez de uma única biblioteca para os dois ambientes, há dois conjuntos diferentes de APIs. No kernel, a API Client é fornecida pelo driver do kernel trusty-ipc e registra um nó de dispositivo de caractere que pode ser usado por processos do espaço do usuário para se comunicar com serviços executados no lado seguro.
API Trusty IPC Client do espaço do usuário
A biblioteca de API do cliente Trusty IPC do espaço do usuário é uma camada fina sobre o
nó do dispositivo fd
.
Um programa do espaço do usuário inicia uma sessão de comunicação
chamando tipc_connect()
,
inicializando uma conexão com um serviço do Trusty especificado. Internamente,
a chamada tipc_connect()
abre um nó de dispositivo especificado para
extrair um descritor de arquivo e invocar uma chamada TIPC_IOC_CONNECT ioctl()
com o parâmetro argp
apontando para uma string que contém um
nome de serviço para se conectar.
#define TIPC_IOC_MAGIC 'r' #define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *)
O descritor de arquivo resultante só pode ser usado para se comunicar com o serviço
para o qual foi criado. O descritor de arquivo precisa ser fechado
chamando tipc_close()
quando a conexão não for mais necessária.
O descritor de arquivo recebido pela chamada tipc_connect()
se comporta como um nó de dispositivo de caractere típico. O descritor de arquivo:
- Pode ser alternado para o modo sem bloqueio, se necessário
- Pode ser escrito usando uma chamada
write()
padrão para enviar mensagens para o outro lado - Pode ser consultado (usando chamadas
poll()
ouselect()
) para verificar a disponibilidade de mensagens recebidas como um descritor de arquivo regular - Pode ser lido para recuperar mensagens recebidas
Um autor da chamada envia uma mensagem para o serviço Trusty executando uma chamada de gravação para
o fd
especificado. Todos os dados transmitidos para a chamada write()
acima
são transformados em uma mensagem pelo driver trusty-ipc. A mensagem é
enviada para o lado seguro, em que os dados são processados pelo subsistema IPC no
kernel do Trusty e encaminhados para o destino adequado e enviados para um loop de eventos
do app como um evento IPC_HANDLE_POLL_MSG
em um identificador de canal
específico. Dependendo do protocolo específico
do serviço, o serviço Trusty pode enviar uma ou mais mensagens de resposta
que são enviadas de volta para o lado não seguro e colocadas na
fila de mensagens do descritor de arquivo de canal adequada para serem recuperadas pela chamada read()
do app de espaço do usuário.
tipc_connect()
Abre um nó de dispositivo tipc
especificado e inicia uma
conexão com um serviço Trusty especificado.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: caminho para o nó do dispositivo Trusty IPC a ser aberto
[in] srv_name
: nome de um serviço do Trusty publicado para se conectar
[retval]: descritor de arquivo válido em caso de sucesso, -1 caso contrário.
tipc_close()
Fecha a conexão com o serviço Trusty especificado por um descritor de arquivo.
int tipc_close(int fd);
[in] fd
: descritor de arquivo aberto anteriormente por
uma chamada tipc_connect()
API do cliente Kernel Trusty IPC
A API cliente IPC do kernel Trusty está disponível para drivers do kernel. A API Trusty IPC do espaço do usuário é implementada em cima dessa API.
Em geral, o uso típico dessa API consiste em um autor da chamada criando
um objeto struct tipc_chan
usando a função tipc_create_channel()
e, em seguida, usando a chamada tipc_chan_connect()
para iniciar uma
conexão com o serviço Trusty IPC em execução no lado
seguro. A conexão com o lado remoto pode ser encerrada
chamando tipc_chan_shutdown()
seguido de
tipc_chan_destroy()
para limpar os recursos.
Ao receber uma notificação (pelo callback handle_event()
)
de que uma conexão foi estabelecida, um autor da chamada faz
o seguinte:
- Recebe um buffer de mensagem usando a chamada
tipc_chan_get_txbuf_timeout()
- Cria uma mensagem e
- Enfileira a mensagem usando o método
tipc_chan_queue_msg()
para entrega a um serviço Trusty (no lado seguro) ao qual o canal está conectado.
Depois que a fila for concluída, o autor da chamada vai precisar esquecer o buffer de mensagens,
porque ele vai retornar ao pool de buffer livre após
ser processado pelo lado remoto (para reutilização posterior, para outras mensagens). O usuário
só precisa chamar tipc_chan_put_txbuf()
se ele não conseguir
colocar esse buffer na fila ou se ele não for mais necessário.
Um usuário da API recebe mensagens do lado remoto processando um
callback de notificação handle_msg()
(chamado no
contexto do workqueue rx
do trusty-ipc) que
fornece um ponteiro para um buffer rx
contendo uma
mensagem de entrada a ser processada.
Espera-se que a implementação do callback
handle_msg()
retorne um ponteiro para um struct tipc_msg_buf
válido.
Ele pode ser o mesmo que o buffer de mensagens recebidas se for processado localmente
e não for mais necessário. Como alternativa, pode ser um novo buffer recebido por
uma chamada tipc_chan_get_rxbuf()
se o buffer de entrada for colocado na fila
para processamento posterior. Um buffer rx
separado precisa ser rastreado
e, eventualmente, liberado usando uma chamada tipc_chan_put_rxbuf()
quando
ele não for mais necessário.
Métodos na API Client IPC do Kernel Trusty
tipc_create_channel()
Cria e configura uma instância de um canal Trusty IPC para um dispositivo trusty-ipc específico.
struct tipc_chan *tipc_create_channel(struct device *dev, const struct tipc_chan_ops *ops, void *cb_arg);
[in] dev
: ponteiro para o trusty-ipc para o qual o canal
do dispositivo é criado.
[in] ops
: ponteiro para um struct tipc_chan_ops
,
com callbacks específicos
do autor da chamada preenchidos.
[in] cb_arg
: ponteiro para dados transmitidos
a callbacks tipc_chan_ops
[retval]: Ponteiro para uma instância recém-criada de
struct tipc_chan
em caso de sucesso,
ERR_PTR(err)
caso contrário
Em geral, um autor da chamada precisa fornecer dois callbacks que são invocados de forma assíncrona quando a atividade correspondente está ocorrendo.
O evento void (*handle_event)(void *cb_arg, int event)
é invocado
para notificar um autor da chamada sobre uma mudança de estado do canal.
[in] cb_arg
: ponteiro para dados transmitidos para uma
chamada tipc_create_channel()
[in] event
: um evento que pode ser um dos seguintes valores:
TIPC_CHANNEL_CONNECTED
: indica uma conexão bem-sucedida ao lado remotoTIPC_CHANNEL_DISCONNECTED
: indica que o lado remoto negou a nova solicitação de conexão ou solicitou a desconexão do canal conectado anteriormente.TIPC_CHANNEL_SHUTDOWN
: indica que o lado remoto está sendo encerrado, encerrando permanentemente todas as conexões.
O callback struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
é invocado para notificar que uma nova mensagem foi
recebida em um canal especificado:
- [in]
cb_arg
: ponteiro para dados transmitidos para a chamadatipc_create_channel()
- [in]
mb
: ponteiro para umstruct tipc_msg_buf
que descreve uma mensagem recebida - [retval]: a implementação do callback deve retornar um ponteiro para um
struct tipc_msg_buf
que pode ser o mesmo ponteiro recebido como um parâmetromb
se a mensagem for processada localmente e não for mais necessária (ou pode ser um novo buffer recebido pela chamadatipc_chan_get_rxbuf()
).
tipc_chan_connect()
Inicia uma conexão com o serviço Trusty IPC especificado.
int tipc_chan_connect(struct tipc_chan *chan, const char *port);
[in] chan
: ponteiro para um canal retornado pela
chamada tipc_create_chan()
[in] port
: ponteiro para uma string que contém o
nome do serviço a ser conectado
[retval]: 0 em caso de sucesso, um erro negativo, caso contrário
O autor da chamada é notificado quando uma conexão é estabelecida recebendo um
callback handle_event
.
tipc_chan_shutdown()
Encerra uma conexão com o serviço IPC do Trusty iniciada anteriormente
por uma chamada tipc_chan_connect()
.
int tipc_chan_shutdown(struct tipc_chan *chan);
[in] chan
: ponteiro para um canal retornado por
uma chamada tipc_create_chan()
tipc_chan_destroy()
Destrói um canal IPC do Trusty especificado.
void tipc_chan_destroy(struct tipc_chan *chan);
[in] chan
: ponteiro para um canal retornado pela
chamada tipc_create_chan()
tipc_chan_get_txbuf_timeout()
Obtém um buffer de mensagens que pode ser usado para enviar dados por um canal especificado. Se o buffer não estiver disponível imediatamente, o autor da chamada poderá ser bloqueado pelo tempo limite especificado (em milissegundos).
struct tipc_msg_buf * tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout);
[in] chan
: ponteiro para o canal em que uma mensagem será enfileirada
[in] chan
: tempo limite máximo para aguardar até que o
buffer tx
fique disponível
[retval]: um buffer de mensagem válido em caso de sucesso,
ERR_PTR(err)
em caso de erro
tipc_chan_queue_msg()
Enfileira uma mensagem para ser enviada pelos canais Trusty IPC especificados.
int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: ponteiro para o canal em que a mensagem vai ser enfileirada
[in] mb:
Ponteiro para a mensagem a ser enfileirada
(obtido por uma chamada tipc_chan_get_txbuf_timeout()
)
[retval]: 0 em caso de sucesso, um erro negativo, caso contrário
tipc_chan_put_txbuf()
Libera o buffer de mensagem Tx
especificado
obtido anteriormente por uma chamada tipc_chan_get_txbuf_timeout()
.
void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: ponteiro para o canal ao qual
este buffer de mensagens pertence
[in] mb
: ponteiro para o buffer de mensagens a ser liberado
[retval]: Nenhum
tipc_chan_get_rxbuf()
Recebe um novo buffer de mensagens que pode ser usado para receber mensagens no canal especificado.
struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);
[in] chan
: ponteiro para um canal ao qual este buffer de mensagens pertence
[retval]: um buffer de mensagem válido em caso de sucesso, ERR_PTR(err)
em caso de erro
tipc_chan_put_rxbuf()
Libera um buffer de mensagem especificado recebido anteriormente por uma
chamada tipc_chan_get_rxbuf()
.
void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb);
[in] chan
: ponteiro para um canal ao qual este buffer de mensagens pertence
[in] mb
: ponteiro para um buffer de mensagem a ser liberado
[retval]: Nenhum