Referência de API confiável

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 ​​executados no processador principal e que 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 se conectar 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 dos dados e a semântica dessas mensagens usando um protocolo em nível de 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 confiáveis ​​para expor terminais 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 uso dos clientes. 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 aceita, também recebe um canal. Em essência, as portas são usadas para procurar serviços e então a comunicação ocorre através 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 simétrica e bidirecional é estabelecida. Usando esse caminho full-duplex, clientes e servidores podem trocar mensagens arbitrárias até que qualquer 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).

Lidar com API

Os identificadores são inteiros não assinados que representam recursos como portas e canais, semelhantes 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 aplicativo e podem ser referenciados posteriormente.

Um chamador pode associar dados privados a um identificador 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 identificador deve ser interpretado apenas comparando-o com INVALID_IPC_HANDLE #define, que um aplicativo pode usar como uma indicação de que um identificador é inválido ou não definido.

Associa os dados privados fornecidos pelo chamador 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 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 ocorrem 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 identificadores podem ser aguardados por eventos usando a chamada wait() .

espere()

Aguarda que um evento ocorra 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 de 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 expirou, mas nenhum evento ocorreu; < 0 para outros erros

Após 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á 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, conforme segue:

  • Para portas, este valor indica que há uma conexão pendente
  • Para canais, este valor indica que uma conexão assíncrona (veja connect() ) foi estabelecida

Os seguintes eventos 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 existe uma mensagem pendente para este canal
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - indica que um chamador anteriormente bloqueado por envio pode tentar enviar uma mensagem novamente (veja a descrição de send_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 pegajosos. 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 após uma leitura e a aplicação tem apenas uma chance de tratá-lo.

Os identificadores podem ser destruídos chamando o método close() .

fechar()

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 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 terminais 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). Este 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 significaria que 1 buffer de envio e 1 buffer de recebimento seriam 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 apareça (por exemplo, uma mensagem como prólogo e outra como comando real). Os conjuntos de buffers alocados são por canal, portanto, duas conexões (canais) separadas teriam conjuntos de buffers separados.

[in] recv_buf_size : tamanho máximo de cada buffer individual no conjunto de buffers acima. Este valor depende do protocolo e limita efetivamente o tamanho máximo da mensagem que você pode trocar com pares

[in] flags : uma combinação de flags que especifica o comportamento adicional da porta

Este 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]: identificador para a porta criada se não for negativa 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 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 identificador) 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 : 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 aplicativo cliente conectado. Será definido como zero se a conexão for originada de um mundo não seguro

[retval]: identificador para um canal (se não for 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 confiável

[in] flags : especifica comportamento adicional e opcional

[retval]: Identificador de um canal através do qual as mensagens podem ser trocadas com o servidor; erro se negativo

Se nenhum flags for especificado (o parâmetro flags é definido como 0), a chamada 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. .

Este 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 deve 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 permitem o envio e a leitura de mensagens através de uma conexão (canal) previamente estabelecida. As chamadas da API Messaging são iguais 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 a partir de uma chamada accept() , descrita acima.

Estrutura de uma mensagem confiável

Conforme mostrado a seguir, as mensagens trocadas pela API Trusty possuem uma estrutura mínima, cabendo ao servidor e ao cliente concordarem sobre 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 . Trusty realiza leituras e gravações dispersas 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

enviar_msg()

Envia uma mensagem por um canal especificado.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : identificador para o canal pelo qual enviar a mensagem

[in] msg : ponteiro para a ipc_msg_t structure que descreve a mensagem

[retval]: Número total de bytes enviados em caso de 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 pelo retorno de um código de erro ERR_NOT_ENOUGH_BUFFER . Nesse caso, o chamador deve esperar até que o par 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 event da estrutura uevent_t retornada pela chamada wait() .

get_msg()

Obtém metainformaçõ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 : Handle 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 canal específico.

[retval]: NO_ERROR em caso de sucesso; um erro negativo caso contrário

leitura_msg()

Lê o conteúdo da mensagem com o ID especificado começando no deslocamento especificado.

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

[in] handle : Handle do canal do qual será lida a mensagem

[in] msg_id : ID da mensagem a ser lida

[in] offset : deslocamento na mensagem a partir da qual iniciar a leitura

[out] msg : ponteiro para a estrutura ipc_msg_t que descreve um conjunto de buffers nos quais armazenar dados de mensagens recebidas

[retval]: Número total de bytes armazenados nos buffers msg 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.

colocar_msg()

Retira uma mensagem com um ID especificado.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Handle do canal em que a mensagem chegou

[in] msg_id : ID da mensagem que está 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 de 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 arquivos tradicionalmente representados por pequenos números. Na implementação atual, o espaço do descritor de arquivo é separado do espaço de manipulação IPC. A API File Descriptor 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 é autônoma (já que não se espera que aplicativos confiáveis ​​tenham um console interativo), portanto, ler, escrever ou invocar ioctl() em fd 0 deve retornar um erro ERR_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() e ioctl() não funcionam e devem retornar um erro ERR_NOT_SUPPORTED .
  • 2 - erro padrão. Os dados gravados no erro padrão devem ser roteados para o UART ou para o 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 no erro padrão, pois é muito provável que esse fluxo não seja limitado. Os métodos read() e ioctl() não funcionam e devem retornar um erro ERR_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 dos descritores de arquivos precisa ser exercida com cautela. Estender descritores de arquivos pode criar conflitos e geralmente não é recomendado.

Métodos na API do descritor de arquivo

ler()

Tenta ler para 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 partir 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

escrever()

Grava para 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 gravar

[out] buf : Ponteiro para dados a serem gravados

[in] count : Número máximo de bytes para escrever

[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()

APIs diversas

Métodos na API Diversos

consiga tempo()

Retorna a hora atual do sistema (em nanossegundos).

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

[in] clock_id : Dependente da plataforma; passe zero para padrão

[in] flags : Reservado, deve ser zero

[out] 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 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 sono 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 exemplo a seguir mostra o uso das APIs acima. O exemplo cria um serviço de "eco" que trata de múltiplas conexões de entrada e reflete de volta ao chamador 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 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 confiáveis, publicados 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 de espaço do usuário para se comunicar com serviços em execução no lado seguro.

API do cliente Trusty IPC do espaço do usuário

A biblioteca Trusty IPC Client API 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 invoca uma chamada 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 comunicação 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 gravado usando uma chamada write() padrão para enviar mensagens para o outro lado
  • Pode ser pesquisado (usando chamadas poll() ou chamadas select() ) para disponibilidade de mensagens recebidas como um descritor de arquivo regular
  • Pode ser lido para recuperar mensagens recebidas

Um chamador envia uma mensagem ao 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() chamar.

tipc_connect()

Abre um nó de dispositivo tipc especificado e inicia uma conexão com um serviço confiável 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 confiável 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

A API Trusty IPC Client do kernel está disponível para drivers de kernel. A API Trusty IPC do espaço do usuário é implementada sobre esta API.

Em geral, o uso típico desta API consiste em um chamador criando um objeto struct tipc_chan usando a função tipc_create_channel() e depois 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 recursos.

Ao receber uma notificação (por meio do retorno de chamada 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
  • Coloca a mensagem na fila usando o método tipc_chan_queue_msg() para entrega a um serviço confiável (no lado seguro), ao qual o canal está conectado

Depois que o enfileiramento for bem-sucedido, o chamador deverá esquecer o buffer de mensagens porque o buffer de mensagens eventualmente retornará 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 esse buffer ou se ele 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 retorno de chamada handle_msg() retorne um ponteiro para uma struct tipc_msg_buf . Pode ser igual ao buffer de mensagens recebidas 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 estiver na fila 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 Kernel Trusty IPC Client

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 uma struct tipc_chan_ops , com retornos de chamada específicos do chamador preenchidos

[in] cb_arg : Ponteiro para dados que serão passados ​​para retornos de chamada 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 sejam invocados de forma assíncrona quando a atividade correspondente estiver 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 com o lado remoto
  • TIPC_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á desligando, encerrando permanentemente todas as conexões

O retorno de chamada struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) é invocado para fornecer notificação de que uma nova mensagem foi recebida em um canal especificado:

  • [in] cb_arg : Ponteiro para dados passados ​​para a chamada tipc_create_channel()
  • [in] mb : ponteiro para uma struct tipc_msg_buf que descreve uma mensagem recebida
  • [retval]: Espera-se que a implementação de retorno de chamada retorne um ponteiro para uma struct tipc_msg_buf que pode ser o mesmo ponteiro recebido como um parâmetro mb se a mensagem for tratada localmente e não for mais necessária (ou pode ser um novo buffer obtido pelo chamada tipc_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, caso contrário, um erro negativo

O chamador é notificado quando uma conexão é estabelecida recebendo um retorno de chamada handle_event .

tipc_chan_shutdown()

Encerra uma conexão com o serviço Trusty IPC 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 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 mensagens 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 no 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()

Coloca uma mensagem na fila 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 no qual enfileirar a mensagem

[in] mb: ponteiro para a mensagem a ser enfileirada (obtida por uma chamada tipc_chan_get_txbuf_timeout() )

[retval]: 0 em caso de sucesso, caso contrário, um erro negativo

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 mensagem pertence

[in] mb : Ponteiro para o buffer de mensagem a ser liberado

[reval]: Nenhum

tipc_chan_get_rxbuf()

Obtém um novo buffer de mensagens 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 obtido 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 mensagem pertence

[in] mb : ponteiro para um buffer de mensagem a ser liberado

[reval]: Nenhum