Trusty fornece APIs para desenvolver duas classes de aplicativos/serviços:
- Aplicativos ou serviços confiáveis executados no processador TEE
- Aplicativos normais/não confiáveis que são executados no processador principal e usam os serviços fornecidos por aplicativos confiáveis
A API Trusty geralmente descreve o sistema Trusty de comunicação entre processos (IPC), incluindo comunicações com o mundo não seguro. O software executado no processador principal pode usar APIs confiáveis para conectar-se a aplicativos/serviços confiáveis e trocar mensagens arbitrárias com eles, como um serviço de rede sobre IP. Cabe ao aplicativo determinar o formato de dados e a semântica dessas mensagens usando um protocolo no nível do aplicativo. A entrega confiável de mensagens é garantida pela infraestrutura Trusty subjacente (na forma de drivers executados no processador principal) e a comunicação é completamente assíncrona.
Portas e canais
As portas são usadas por aplicativos Trusty para expor pontos de extremidade 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 é no estilo DNS reverso, por exemplo, com.google.servicename
.
Quando um cliente se conecta a uma porta, o cliente recebe um canal para interagir com um serviço. O serviço deve 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 meio de 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 bidirecional simétrica é estabelecida. Usando esse caminho full-duplex, clientes e servidores podem trocar mensagens arbitrárias até que um dos lados decida interromper a conexão.
Somente aplicativos confiáveis do lado seguro ou módulos de kernel confiáveis podem criar portas. Os aplicativos executados no lado não seguro (no mundo normal) só podem se conectar a serviços publicados pelo lado seguro.
Dependendo dos requisitos, um aplicativo confiável pode ser cliente e servidor ao mesmo tempo. Um aplicativo confiável que publica um serviço (como servidor) pode precisar se conectar a outros serviços (como cliente).
Manipular API
Os identificadores são inteiros não assinados que representam recursos como portas e canais, semelhantes aos descritores de arquivo no UNIX. Depois que os identificadores são criados, eles são colocados em uma tabela de identificadores específica do aplicativo e podem ser referenciados posteriormente.
Um chamador pode associar dados privados a um handle usando o método set_cookie()
.
Métodos na API Handle
Os identificadores são válidos apenas no contexto de um aplicativo. Um aplicativo não deve passar o valor de um identificador para outros aplicativos, a menos que seja explicitamente especificado. Um valor de handle deve ser interpretado apenas comparando-o com INVALID_IPC_HANDLE #define,
que um aplicativo pode usar como uma indicação de que um handle é inválido ou não está definido.
set_cookie()
Associa os dados privados fornecidos pelo chamador a um identificador especificado.
long set_cookie(uint32_t handle, void *cookie)
[in] handle
: Qualquer handle retornado por uma das chamadas da API
[in] cookie
: ponteiro para dados arbitrários do espaço do usuário no aplicativo Trusty
[retval]: NO_ERROR
em caso de sucesso, < 0
código de erro caso contrário
Essa chamada é útil para manipular eventos quando eles ocorrerem posteriormente após a criação do identificador. O mecanismo de manipulação de eventos fornece o identificador e seu cookie de volta ao manipulador de eventos.
Os handles podem ser aguardados por eventos usando a chamada wait()
.
esperar()
Aguarda a ocorrência de um evento em um determinado identificador por um período de tempo especificado.
long wait(uint32_t handle_id, uevent_t *event, unsigned long timeout_msecs)
[in] handle_id
: Qualquer identificador retornado por uma das chamadas da API
[out] event
: Um ponteiro para a estrutura que representa um evento que ocorreu neste 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 dentro de um intervalo de tempo limite especificado; ERR_TIMED_OUT
se um tempo limite especificado tiver decorrido, 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 de 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á realmente pendente, o chamador deve reiniciar a espera
IPC_HANDLE_POLL_ERROR
- ocorreu um erro interno não especificado
IPC_HANDLE_POLL_READY
- depende do tipo de identificador, como segue:
- Para portas, este 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 peer -
IPC_HANDLE_POLL_MSG
- indica que há uma mensagem pendente para este canal -
IPC_HANDLE_POLL_SEND_UNBLOCKED
- indica que um chamador bloqueado anteriormente pode tentar enviar uma mensagem novamente (veja a descrição desend_msg()
para detalhes)
Um manipulador de eventos deve estar preparado para lidar com uma combinação de eventos especificados, pois vários bits podem ser definidos ao mesmo tempo. Por exemplo, para um canal, é possível ter mensagens pendentes e uma conexão fechada por um peer ao mesmo tempo.
A maioria dos eventos são fixos. Eles 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 tratadas). A exceção é o caso do evento IPC_HANDLE_POLL_SEND_UNBLOCKED
, que é limpo em uma leitura e o aplicativo tem apenas uma chance de tratá-lo.
As alças podem ser destruídas chamando o método close()
.
perto()
Destrói o recurso associado ao identificador especificado e o remove da tabela de identificadores.
long close(uint32_t handle_id);
[in] handle_id
: Manipular para destruir
[retval]: 0 se for bem sucedido; um erro negativo caso contrário
API do servidor
Um servidor começa criando uma ou mais portas nomeadas que representam seus pontos de extremidade de serviço. Cada porta é representada por um identificador.
Métodos na API do servidor
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 deve ser exclusivo em todo o sistema; tentativas de criar uma duplicata falharão.
[in] num_recv_bufs
: O número máximo de buffers que um canal nesta 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 buffer de recebimento são pré-alocados. Em geral, o número de buffers necessários depende do acordo de protocolo de nível superior entre o cliente e o servidor. O número pode ser tão pequeno 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 possa aparecer (por exemplo, uma mensagem como prólogo e outra como o comando real). Os conjuntos de buffers alocados são por canal, portanto, duas conexões separadas (canais) teriam conjuntos de buffers separados.
[in] recv_buf_size
: Tamanho máximo de cada buffer individual no conjunto de buffers acima. Esse valor depende do protocolo e limita efetivamente o tamanho máximo da mensagem que você pode trocar com o peer
[in] flags
: uma combinação de sinalizadores que especifica o comportamento adicional da porta
Esse valor deve ser uma combinação dos seguintes valores:
IPC_PORT_ALLOW_TA_CONNECT
- permite uma conexão de outros aplicativos seguros
IPC_PORT_ALLOW_NS_CONNECT
- permite uma conexão do mundo não seguro
[retval]: Handle para a porta criada se não for negativo ou um erro específico se for negativo
O servidor então pesquisa a lista de identificadores de porta para conexões de entrada usando a chamada wait()
. Ao receber uma solicitação de conexão indicada pelo bit IPC_HANDLE_POLL_READY
definido no campo de event
da estrutura uevent_t
, o servidor deve chamar accept()
para concluir o estabelecimento de uma conexão e criar um canal (representado por outro handle) que pode então ser pesquisado para mensagens recebidas .
aceitar()
Aceita uma conexão de entrada e obtém um identificador para um canal.
long accept(uint32_t handle_id, uuid_t *peer_uuid);
[in] handle_id
: Handle representando a porta à qual um cliente se conectou
[out] peer_uuid
: Ponteiro para uma estrutura uuud_t
a ser preenchida com o UUID do aplicativo cliente conectado. Será definido como zero se a conexão for originada do mundo não seguro
[retval]: Handle para um canal (se não negativo) no qual 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 do cliente.
Métodos na API do cliente
conectar()
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 aplicativo Trusty
[in] flags
: Especifica o comportamento opcional adicional
[retval]: Handle para um canal pelo qual as mensagens podem ser trocadas com o servidor; erro se negativo
Se nenhum flags
for especificado (o parâmetro flags
estiver definido como 0), chamar connect()
inicia uma conexão síncrona com uma porta especificada que retorna imediatamente um erro se a porta não existir e cria um bloco até que o servidor aceite uma conexão .
Esse comportamento pode ser alterado especificando uma combinação de dois valores, descritos abaixo:
enum { IPC_CONNECT_WAIT_FOR_PORT = 0x1, IPC_CONNECT_ASYNC = 0x2, };
IPC_CONNECT_WAIT_FOR_PORT
- força uma chamada connect()
a esperar 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 aplicativo precisa pesquisar 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 de mensagens
As chamadas da API Messaging possibilitam o envio e a leitura de mensagens por meio de uma conexão (canal) previamente estabelecida. As chamadas da API de mensagens são as mesmas para servidores e clientes.
Um cliente recebe um identificador para um canal emitindo uma chamada connect()
, e um servidor obtém um identificador de canal de uma chamada accept()
, descrita acima.
Estrutura de uma mensagem Trusty
Conforme mostrado a seguir, as mensagens trocadas pela API Trusty têm uma estrutura mínima, deixando para o servidor e o cliente concordar 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 um array de estruturas iovec_t
. O Trusty executa leituras e gravações de coleta de dispersão nesses blocos usando o array iov
. O conteúdo dos buffers que podem ser descritos pelo array iov
é completamente arbitrário.
Métodos na API de mensagens
send_msg()
Envia uma mensagem por um canal especificado.
long send_msg(uint32_t handle, ipc_msg_t *msg);
[in] handle
: Handle para o canal pelo qual enviar a mensagem
[in] msg
: Ponteiro para a ipc_msg_t structure
descreve a mensagem
[retval]: Número total de bytes enviados com sucesso; um erro negativo caso contrário
Se o cliente (ou servidor) estiver tentando enviar uma mensagem pelo canal e não houver espaço na fila de mensagens do peer de destino, o canal poderá entrar em um estado de bloqueio de envio (isso nunca deve acontecer para um protocolo simples de solicitação/resposta síncrona mas pode acontecer em casos mais complicados) que é indicado retornando um código de erro ERR_NOT_ENOUGH_BUFFER
. Nesse caso, o chamador deve esperar até que o peer libere algum espaço em sua fila de recebimento recuperando as mensagens de manipulação e retirada, indicadas pelo bit IPC_HANDLE_POLL_SEND_UNBLOCKED
definido no campo de event
da estrutura uevent_t
retornada pela chamada wait()
.
get_msg()
Obtém meta-informações sobre a próxima mensagem em uma fila de mensagens recebidas
de um canal especificado.
long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);
[in] handle
: Identificador do canal no qual uma nova mensagem deve ser recuperada
[out] msg_info
: Estrutura de informações da mensagem descrita a seguir:
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 determinado canal.
[retval]: NO_ERROR
em caso de sucesso; um erro negativo caso contrário
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 do qual ler a mensagem
[in] msg_id
: ID da mensagem a ser lida
[in] offset
: Desloque na mensagem a partir da qual iniciar a leitura
[in] msg
: Ponteiro para a estrutura ipc_msg_t
descrevendo um conjunto de buffers nos quais armazenar dados de mensagens recebidas
[retval]: Número total de bytes armazenados nos buffers dst
em caso de sucesso; um erro negativo caso contrário
O método read_msg
pode ser chamado várias vezes começando em um deslocamento diferente (não necessariamente sequencial) 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 no qual a mensagem chegou
[in] msg_id
: ID da mensagem sendo retirada
[retval]: NO_ERROR
em caso de sucesso; um erro negativo caso contrário
O conteúdo da mensagem não pode ser acessado depois que uma mensagem foi retirada e o buffer que ela ocupava foi liberado.
API do descritor de arquivo
A API do descritor de arquivo inclui chamadas read()
, write()
e ioctl()
. Todas essas chamadas podem operar em um conjunto predefinido (estático) de descritores de arquivo tradicionalmente representados por pequenos números. Na implementação atual, o espaço do descritor de arquivo é separado do espaço de manipulação do IPC. A API do descritor de arquivo no Trusty é semelhante a uma API tradicional baseada em descritor de arquivo.
Por padrão, existem 3 descritores de arquivo predefinidos (padrão e conhecidos):
- 0 - entrada padrão. A implementação padrão da entrada padrão
fd
é um no-op (já que não se espera que aplicativos confiáveis tenham um console interativo), portanto, ler, escrever 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 LK) para UART e/ou um log de memória disponível no lado não seguro, dependendo da plataforma e da configuração. Logs e mensagens de depuração não críticos devem ir na saída padrão. Os métodos
read()
eioctl()
não são operacionais e devem retornar um erroERR_NOT_SUPPORTED
. - 2 - erro padrão. Os dados gravados com erro padrão devem ser roteados para o UART ou log de memória disponível no lado não seguro, dependendo da plataforma e da configuração. Recomenda-se gravar apenas mensagens críticas para erro padrão, pois é muito provável que esse fluxo seja desregulado. Os métodos
read()
eioctl()
não são operacionais e devem retornar um erroERR_NOT_SUPPORTED
.
Embora esse conjunto de descritores de arquivo possa ser estendido para implementar mais fds
(para implementar extensões específicas da plataforma), a extensão de descritores de arquivo precisa ser exercida com cautela. A extensão de descritores de arquivo tende a criar conflitos e geralmente não é recomendada.
Métodos na API do descritor de arquivo
ler()
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 do qual ler
[out] buf
: Ponteiro para um buffer no qual armazenar dados
[in] count
: Número máximo de bytes a serem lidos
[retval]: Número retornado de bytes lidos; um erro negativo caso contrário
Escreva()
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 no qual escrever
[out] buf
: Ponteiro para dados a serem gravados
[in] count
: número máximo de bytes a serem gravados
[retval]: Número retornado de bytes gravados; um erro negativo caso contrário
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 no qual invocar ioctl()
[in] cmd
: O comando ioctl
[in/out] args
: Ponteiro para argumentos ioctl()
API diversa
Métodos na API Diversos
consiga tempo()
Retorna a hora atual do sistema (em nanossegundos).
long gettime(uint32_t clock_id, uint32_t flags, uint64_t *time);
[in] clock_id
: Dependente da plataforma; passe zero por padrão
[in] flags
: Reservado, deve ser zero
[in] time
: Ponteiro para um valor int64_t
no qual armazenar a hora atual
[retval]: NO_ERROR
em caso de sucesso; um erro negativo caso contrário
nanosono()
Suspende a execução do aplicativo de chamada por um período de tempo 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, deve ser zero
[in] flags
: Reservado, deve ser zero
[in] sleep_time
: Tempo de suspensão em nanossegundos
[retval]: NO_ERROR
em caso de sucesso; um erro negativo caso contrário
Exemplo de um servidor de aplicativos confiável
O aplicativo de amostra a seguir mostra o uso das APIs acima. A amostra cria um serviço de "eco" que lida com várias conexões de entrada e reflete de volta para o chamador todas as mensagens que recebe de clientes originados 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 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; }
O método run_end_to_end_msg_test()
envia 10.000 mensagens de forma assíncrona para o serviço "echo" e trata das 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 aplicativos mundiais não seguros
Um conjunto de serviços Trusty, publicados a partir do lado seguro e marcados com o atributo IPC_PORT_ALLOW_NS_CONNECT
, são acessíveis ao kernel e aos programas de espaço do usuário executados 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 ambos os ambientes, existem dois conjuntos diferentes de APIs. No kernel, a API do cliente é 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 do cliente IPC confiável do espaço do usuário
A biblioteca da API Trusty IPC Client do espaço do usuário é uma camada fina na parte superior do nó do dispositivo fd
.
Um programa de espaço do usuário inicia uma sessão de comunicação chamando tipc_connect()
, inicializando uma conexão com um serviço Trusty especificado. Internamente, a chamada tipc_connect()
abre um nó de dispositivo especificado para obter um descritor de arquivo e chama uma TIPC_IOC_CONNECT ioctl()
com o parâmetro argp
apontando para uma string contendo um nome de serviço ao qual 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 deve ser fechado chamando tipc_close()
quando a conexão não for mais necessária.
O descritor de arquivo obtido pela chamada tipc_connect()
se comporta como um típico nó de dispositivo de caractere; o descritor do 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 pesquisado (usando chamadas
poll()
ou chamadasselect()
) para disponibilidade de mensagens recebidas como um descritor de arquivo regular - Pode ser lido para recuperar mensagens recebidas
Um chamador envia uma mensagem para o serviço Trusty executando uma chamada de gravação para o fd
especificado. Todos os dados passados para a chamada write()
acima são transformados em uma mensagem pelo driver trusty-ipc. A mensagem é entregue ao lado seguro onde os dados são manipulados pelo subsistema IPC no kernel Trusty e roteados para o destino adequado e entregues a um loop de eventos de aplicativo 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 entregues de volta ao lado não seguro e colocadas na fila de mensagens do descritor de arquivo de canal apropriado para serem recuperadas pelo aplicativo de espaço do usuário read()
chamada.
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 Trusty publicado ao qual 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 IPC confiável do kernel
O kernel Trusty IPC Client API está disponível para drivers de 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 chamador 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 por tipc_chan_destroy()
para limpar os recursos.
Ao receber uma notificação (através do callback handle_event()
) de que uma conexão foi estabelecida com sucesso, o chamador faz o seguinte:
- Obtém um buffer de mensagem usando a chamada
tipc_chan_get_txbuf_timeout()
- Compõe 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 o enfileiramento for bem-sucedido, o chamador deve esquecer o buffer de mensagem porque o buffer de mensagem eventualmente retorna ao buffer pool livre após o processamento pelo lado remoto (para reutilização posterior, para outras mensagens). O usuário só precisa chamar tipc_chan_put_txbuf()
se não conseguir enfileirar tal buffer ou não for mais necessário.
Um usuário da API recebe mensagens do lado remoto manipulando um retorno de chamada de notificação handle_msg()
(que é chamado no contexto da fila de trabalho rx
trusty-ipc) que fornece um ponteiro para um buffer rx
contendo uma mensagem recebida a ser tratada.
Espera-se que a implementação de callback handle_msg()
retorne um ponteiro para uma estrutura válida struct tipc_msg_buf
. Pode ser o mesmo que o buffer de mensagem de entrada se for tratado localmente e não for mais necessário. Alternativamente, pode ser um novo buffer obtido por uma chamada tipc_chan_get_rxbuf()
se o buffer de entrada for enfileirado para processamento adicional. Um buffer rx
desanexado deve ser rastreado e eventualmente liberado usando uma chamada tipc_chan_put_rxbuf()
quando não for mais necessário.
Métodos na API do cliente IPC confiável do kernel
tipc_create_channel()
Cria e configura uma instância de um canal Trusty IPC para um determinado dispositivo Trusty-IPC.
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 chamador preenchidos
[in] cb_arg
: Ponteiro para dados que serão passados para 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 chamador deve fornecer dois retornos de chamada que são invocados de forma assíncrona quando a atividade correspondente está ocorrendo.
O void (*handle_event)(void *cb_arg, int event)
é invocado para notificar um chamador sobre uma mudança de estado do canal.
[in] cb_arg
: Ponteiro para dados passados 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 remoto -
TIPC_CHANNEL_DISCONNECTED
- indica que o lado remoto negou a nova solicitação de conexão ou solicitou desconexão para o canal conectado anteriormente -
TIPC_CHANNEL_SHUTDOWN
- indica que o lado remoto está sendo desligado, encerrando permanentemente todas as conexões
O retorno de struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb)
é invocado para fornecer uma notificação de que uma nova mensagem foi recebida em um canal especificado:
- [in]
cb_arg
: Ponteiro para dados passados para a chamadatipc_create_channel()
- [in]
mb
: Ponteiro para umstruct tipc_msg_buf
descrevendo uma mensagem recebida - [retval]: Espera-se que a implementação do callback retorne um ponteiro para uma
struct tipc_msg_buf
que pode ser o mesmo ponteiro recebido como um parâmetromb
se a mensagem for tratada localmente e não for mais necessária (ou pode ser um novo buffer obtido pelo 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 contendo o nome do serviço ao qual se conectar
[retval]: 0 em caso de sucesso, um erro negativo caso contrário
O chamador é notificado quando uma conexão é estabelecida recebendo um retorno de chamada handle_event
.
tipc_chan_shutdown()
Termina uma conexão com o serviço Trusty IPC iniciado 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 Trusty IPC 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 mensagem que pode ser usado para enviar dados por um canal especificado. Se o buffer não estiver disponível imediatamente, o chamador 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 para o qual enfileirar uma mensagem
[in] chan
: Tempo limite máximo para esperar 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 a 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 para o qual enfileirar a mensagem
[in] mb:
Ponteiro para a mensagem para enfileirar (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 anteriormente obtido 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 mensagem pertence
[in] mb
: Ponteiro para o buffer de mensagem a ser liberado
[revalidação]: Nenhuma
tipc_chan_get_rxbuf()
Obtém um novo buffer de mensagem que pode ser usado para receber mensagens pelo 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 mensagem 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 anteriormente obtido 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 mensagem pertence
[in] mb
: Ponteiro para um buffer de mensagem para liberar
[revalidação]: Nenhuma