Riferimento API affidabile

Trusty fornisce API per lo sviluppo di due classi di applicazioni/servizi:

  • Applicazioni o servizi affidabili eseguiti sul processore TEE
  • Applicazioni normali/non attendibili che vengono eseguite sul processore principale e utilizzano i servizi forniti da applicazioni attendibili

L'API Trusty descrive generalmente il sistema di comunicazione interprocesso (IPC) Trusty, comprese le comunicazioni con il mondo non sicuro. Il software in esecuzione sul processore principale può utilizzare le API Trusty per connettersi ad applicazioni/servizi affidabili e scambiare messaggi arbitrari con essi proprio come un servizio di rete su IP. Spetta all'applicazione determinare il formato dei dati e la semantica di questi messaggi utilizzando un protocollo a livello di app. La consegna affidabile dei messaggi è garantita dall'infrastruttura Trusty sottostante (sotto forma di driver in esecuzione sul processore principale) e la comunicazione è completamente asincrona.

Porti e canali

Le porte vengono utilizzate dalle applicazioni Trusty per esporre gli endpoint del servizio sotto forma di un percorso denominato a cui si connettono i client. Ciò fornisce un ID servizio semplice, basato su stringa, che i client possono utilizzare. La convenzione di denominazione è in stile DNS inverso, ad esempio com.google.servicename .

Quando un client si connette a una porta, riceve un canale per interagire con un servizio. Il servizio deve accettare una connessione in entrata e, quando lo fa, riceve anch'esso un canale. In sostanza, le porte vengono utilizzate per cercare servizi e quindi la comunicazione avviene su una coppia di canali connessi (ovvero, istanze di connessione su una porta). Quando un client si connette a una porta, viene stabilita una connessione simmetrica e bidirezionale. Utilizzando questo percorso full-duplex, client e server possono scambiarsi messaggi arbitrari finché una delle due parti non decide di interrompere la connessione.

Solo le applicazioni attendibili sul lato sicuro o i moduli kernel Trusty possono creare porte. Le applicazioni in esecuzione sul lato non sicuro (nel mondo normale) possono connettersi solo ai servizi pubblicati dal lato sicuro.

A seconda delle esigenze, un'applicazione attendibile può essere contemporaneamente client e server. Un'applicazione attendibile che pubblica un servizio (come server) potrebbe dover connettersi ad altri servizi (come client).

Gestire l'API

Gli handle sono numeri interi senza segno che rappresentano risorse come porte e canali, simili ai descrittori di file in UNIX. Dopo aver creato gli handle, questi vengono inseriti in una tabella di handle specifica dell'applicazione e possono essere referenziati in seguito.

Un chiamante può associare dati privati ​​a un handle utilizzando il metodo set_cookie() .

Metodi nell'API Handle

Gli handle sono validi solo nel contesto di un'applicazione. Un'applicazione non deve passare il valore di un handle ad altre applicazioni a meno che non sia esplicitamente specificato. Un valore di handle dovrebbe essere interpretato solo confrontandolo con INVALID_IPC_HANDLE #define, che un'applicazione può utilizzare come indicazione che un handle non è valido o non impostato.

Associa i dati privati ​​forniti dal chiamante a un handle specificato.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : qualsiasi handle restituito da una delle chiamate API

[in] cookie : puntatore a dati arbitrari dello spazio utente nell'applicazione Trusty

[retval]: NO_ERROR in caso di successo, < 0 codice di errore altrimenti

Questa chiamata è utile per gestire gli eventi che si verificano in un secondo momento dopo la creazione dell'handle. Il meccanismo di gestione degli eventi fornisce l'handle e il relativo cookie al gestore dell'evento.

È possibile attendere gli handle per gli eventi utilizzando la chiamata wait() .

Aspettare()

Attende che si verifichi un evento su un determinato handle per il periodo di tempo specificato.

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

[in] handle_id : qualsiasi handle restituito da una delle chiamate API

[out] event : puntatore alla struttura che rappresenta un evento che si è verificato su questo handle

[in] timeout_msecs : un valore di timeout in millisecondi; un valore di -1 è un timeout infinito

[retval]: NO_ERROR se si è verificato un evento valido entro un intervallo di timeout specificato; ERR_TIMED_OUT se è trascorso il timeout specificato ma non si è verificato alcun evento; < 0 per altri errori

In caso di successo ( retval == NO_ERROR ), la chiamata wait() riempie una struttura uevent_t specificata con le informazioni sull'evento che si è verificato.

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;

Il campo event contiene una combinazione dei seguenti valori:

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 - nessun evento è effettivamente in sospeso, il chiamante dovrebbe riavviare l'attesa

IPC_HANDLE_POLL_ERROR - si è verificato un errore interno non specificato

IPC_HANDLE_POLL_READY - dipende dal tipo di handle, come segue:

  • Per le porte, questo valore indica che esiste una connessione in sospeso
  • Per i canali, questo valore indica che è stata stabilita una connessione asincrona (vedi connect() ).

I seguenti eventi sono rilevanti solo per i canali:

  • IPC_HANDLE_POLL_HUP - indica che un canale è stato chiuso da un peer
  • IPC_HANDLE_POLL_MSG - indica che c'è un messaggio in sospeso per questo canale
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - indica che un chiamante precedentemente bloccato con l'invio può tentare di inviare nuovamente un messaggio (vedere la descrizione di send_msg() per i dettagli)

Un gestore di eventi dovrebbe essere preparato a gestire una combinazione di eventi specificati, poiché potrebbero essere impostati più bit contemporaneamente. Ad esempio, per un canale è possibile avere contemporaneamente messaggi in sospeso e una connessione chiusa da un peer.

La maggior parte degli eventi sono appiccicosi. Persistono finché persiste la condizione sottostante (ad esempio, tutti i messaggi in sospeso vengono ricevuti e le richieste di connessione in sospeso vengono gestite). L'eccezione è il caso dell'evento IPC_HANDLE_POLL_SEND_UNBLOCKED , che viene cancellato durante una lettura e l'applicazione ha solo una possibilità di gestirlo.

Gli handle possono essere distrutti chiamando il metodo close() .

vicino()

Distrugge la risorsa associata all'handle specificato e la rimuove dalla tabella degli handle.

long close(uint32_t handle_id);

[in] handle_id : handle da distruggere

[retval]: 0 se successo; altrimenti un errore negativo

API del server

Un server inizia creando una o più porte denominate che rappresentano i suoi endpoint di servizio. Ogni porta è rappresentata da un handle.

Metodi nell'API del server

porta_crea()

Crea una porta di servizio denominata.

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

[in] path : il nome stringa della porta (come descritto sopra). Questo nome dovrebbe essere univoco in tutto il sistema; i tentativi di creare un duplicato falliranno.

[in] num_recv_bufs : il numero massimo di buffer che un canale su questa porta può preallocare per facilitare lo scambio di dati con il client. I buffer vengono conteggiati separatamente per i dati che vanno in entrambe le direzioni, quindi specificare 1 qui significherebbe che 1 buffer di invio e 1 buffer di ricezione sono preallocati. In generale il numero di buffer necessari dipende dall'accordo di protocollo di livello superiore tra client e server. Il numero può essere fino a 1 in caso di un protocollo molto sincrono (inviare un messaggio, ricevere una risposta prima di inviarne un altro). Ma il numero può essere maggiore se il client prevede di inviare più di un messaggio prima che possa apparire una risposta (ad esempio, un messaggio come prologo e un altro come comando vero e proprio). I set di buffer allocati sono per canale, quindi due connessioni separate (canali) avrebbero set di buffer separati.

[in] recv_buf_size : dimensione massima di ogni singolo buffer nel set di buffer precedente. Questo valore dipende dal protocollo e limita effettivamente la dimensione massima del messaggio che è possibile scambiare con il peer

[in] flags : una combinazione di flag che specifica il comportamento aggiuntivo della porta

Questo valore dovrebbe essere una combinazione dei seguenti valori:

IPC_PORT_ALLOW_TA_CONNECT : consente una connessione da altre app sicure

IPC_PORT_ALLOW_NS_CONNECT - consente una connessione dal mondo non sicuro

[retval]: handle per la porta creata se non negativo o un errore specifico se negativo

Il server quindi interroga l'elenco degli handle di porta per le connessioni in entrata utilizzando la chiamata wait() . Dopo aver ricevuto una richiesta di connessione indicata dal bit IPC_HANDLE_POLL_READY impostato nel campo event della struttura uevent_t , il server dovrebbe chiamare accept() per completare la creazione di una connessione e creare un canale (rappresentato da un altro handle) che può quindi essere interrogato per i messaggi in entrata .

accettare()

Accetta una connessione in entrata e ottiene un handle per un canale.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : handle che rappresenta la porta a cui si è connesso un client

[out] peer_uuid : puntatore a una struttura uuid_t da riempire con l'UUID dell'applicazione client che si connette. Verrà impostato su tutti zeri se la connessione ha avuto origine dal mondo non sicuro

[retval]: handle su un canale (se non negativo) su cui il server può scambiare messaggi con il client (o altrimenti un codice di errore)

API client

Questa sezione contiene i metodi nell'API client.

Metodi nell'API client

Collegare()

Avvia una connessione a una porta specificata dal nome.

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

[in] path : nome di una porta pubblicata da un'applicazione Trusty

[in] flags : specifica un comportamento aggiuntivo e facoltativo

[retval]: handle per un canale su cui i messaggi possono essere scambiati con il server; errore se negativo

Se non vengono specificati flags (il parametro flags è impostato su 0), la chiamata connect() avvia una connessione sincrona a una porta specificata che restituisce immediatamente un errore se la porta non esiste e crea un blocco finché il server non accetta altrimenti una connessione .

Questo comportamento può essere modificato specificando una combinazione di due valori, descritti di seguito:

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

IPC_CONNECT_WAIT_FOR_PORT - forza l'attesa di una chiamata connect() se la porta specificata non esiste immediatamente al momento dell'esecuzione, invece di fallire immediatamente.

IPC_CONNECT_ASYNC : se impostato, avvia una connessione asincrona. Un'applicazione deve interrogare l'handle restituito (chiamando wait() per un evento di completamento della connessione indicato dal bit IPC_HANDLE_POLL_READY impostato nel campo evento della struttura uevent_t prima di iniziare il normale funzionamento.

API di messaggistica

Le chiamate API di messaggistica consentono l'invio e la lettura di messaggi su una connessione (canale) precedentemente stabilita. Le chiamate API di messaggistica sono le stesse per server e client.

Un client riceve un handle per un canale emettendo una chiamata connect() e un server riceve un handle di canale da una chiamata accept() , descritta sopra.

Struttura di un messaggio Trusty

Come mostrato di seguito, i messaggi scambiati dall'API Trusty hanno una struttura minima, lasciando al server e al client il compito di concordare la semantica del contenuto effettivo:

/*
 *  IPC message
 */
typedef struct iovec {
        void   *base;
        size_t  len;
} iovec_t;

typedef struct ipc_msg {
        uint     num_iov; /* number of iovs in this message */
        iovec_t  *iov;    /* pointer to iov array */

        uint     num_handles; /* reserved, currently not supported */
        handle_t *handles;    /* reserved, currently not supported */
} ipc_msg_t;

Un messaggio può essere composto da uno o più buffer non contigui rappresentati da un array di strutture iovec_t . Trusty esegue letture e scritture scatter-gather su questi blocchi utilizzando l'array iov . Il contenuto dei buffer che può essere descritto dall'array iov è completamente arbitrario.

Metodi nell'API di messaggistica

invia_msg()

Invia un messaggio su un canale specificato.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : handle del canale su cui inviare il messaggio

[in] msg : puntatore alla ipc_msg_t structure che descrive il messaggio

[retval]: numero totale di byte inviati in caso di successo; altrimenti un errore negativo

Se il client (o il server) sta tentando di inviare un messaggio sul canale e non c'è spazio nella coda dei messaggi del peer di destinazione, il canale potrebbe entrare in uno stato di invio bloccato (questo non dovrebbe mai accadere per un semplice protocollo di richiesta/risposta sincrona ma potrebbe verificarsi in casi più complicati) che viene indicato restituendo un codice di errore ERR_NOT_ENOUGH_BUFFER . In tal caso il chiamante deve attendere finché il peer libera spazio nella coda di ricezione recuperando i messaggi di gestione e ritiro, indicati dal bit IPC_HANDLE_POLL_SEND_UNBLOCKED impostato nel campo event della struttura uevent_t restituita dalla chiamata wait() .

ricevi_msg()

Ottiene metainformazioni sul messaggio successivo in una coda di messaggi in entrata

di un canale specificato.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : handle del canale su cui deve essere recuperato un nuovo messaggio

[out] msg_info : struttura delle informazioni del messaggio descritta come segue:

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

A ogni messaggio viene assegnato un ID univoco nell'insieme dei messaggi in sospeso e viene compilata la lunghezza totale di ciascun messaggio. Se configurato e consentito dal protocollo, possono esserci più messaggi in sospeso (aperti) contemporaneamente per un particolare canale.

[retval]: NO_ERROR in caso di successo; altrimenti un errore negativo

leggi_msg()

Legge il contenuto del messaggio con l'ID specificato a partire dall'offset specificato.

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

[in] handle : handle del canale da cui leggere il messaggio

[in] msg_id : ID del messaggio da leggere

[in] offset : Offset nel messaggio da cui iniziare la lettura

[out] msg : puntatore alla struttura ipc_msg_t che descrive un insieme di buffer in cui memorizzare i dati dei messaggi in entrata

[retval]: numero totale di byte memorizzati nei buffer msg in caso di successo; altrimenti un errore negativo

Il metodo read_msg può essere chiamato più volte a partire da un offset diverso (non necessariamente sequenziale) in base alle esigenze.

put_msg()

Ritira un messaggio con un ID specificato.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : handle del canale su cui è arrivato il messaggio

[in] msg_id : ID del messaggio in fase di ritiro

[retval]: NO_ERROR in caso di successo; altrimenti un errore negativo

Non è possibile accedere al contenuto del messaggio dopo che un messaggio è stato ritirato e il buffer che occupava è stato liberato.

API descrittore di file

L'API del descrittore di file include le chiamate read() , write() e ioctl() . Tutte queste chiamate possono operare su un insieme predefinito (statico) di descrittori di file tradizionalmente rappresentati da piccoli numeri. Nell'implementazione corrente, lo spazio del descrittore di file è separato dallo spazio dell'handle IPC. L'API del descrittore di file in Trusty è simile a un'API tradizionale basata sul descrittore di file.

Per impostazione predefinita, ci sono 3 descrittori di file predefiniti (standard e conosciuti):

  • 0 - ingresso standard. L'implementazione predefinita dello standard input fd non è operativa (poiché non è previsto che le applicazioni affidabili dispongano di una console interattiva), quindi leggere, scrivere o invocare ioctl() su fd 0 dovrebbe restituire un errore ERR_NOT_SUPPORTED .
  • 1 - uscita standard. I dati scritti sull'output standard possono essere instradati (a seconda del livello di debug LK) a UART e/o a un registro di memoria disponibile sul lato non sicuro, a seconda della piattaforma e della configurazione. I log e i messaggi di debug non critici dovrebbero essere inseriti nell'output standard. I metodi read() e ioctl() non sono operativi e dovrebbero restituire un errore ERR_NOT_SUPPORTED .
  • 2 - errore standard. I dati scritti in errore standard devono essere instradati all'UART o al registro di memoria disponibile sul lato non sicuro, a seconda della piattaforma e della configurazione. Si consiglia di scrivere solo i messaggi critici nell'errore standard, poiché è molto probabile che questo flusso non venga limitato. I metodi read() e ioctl() non sono operativi e dovrebbero restituire un errore ERR_NOT_SUPPORTED .

Anche se questo insieme di descrittori di file può essere esteso per implementare più fds (per implementare estensioni specifiche della piattaforma), l'estensione dei descrittori di file deve essere esercitata con cautela. L'estensione dei descrittori di file tende a creare conflitti e generalmente non è consigliata.

Metodi nell'API del descrittore di file

Leggere()

Tenta di leggere fino a count i byte di dati da un descrittore di file specificato.

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

[in] fd : descrittore di file da cui leggere

[out] buf : puntatore a un buffer in cui memorizzare i dati

[in] count : numero massimo di byte da leggere

[retval]: numero restituito di byte letti; altrimenti un errore negativo

scrivere()

Scrive fino a count i byte di dati nel descrittore di file specificato.

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

[in] fd : descrittore di file su cui scrivere

[out] buf : puntatore ai dati da scrivere

[in] count : numero massimo di byte da scrivere

[retval]: numero restituito di byte scritti; altrimenti un errore negativo

ioctl()

Invoca un comando ioctl specificato per un determinato descrittore di file.

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

[in] fd : descrittore di file su cui invocare ioctl()

[in] cmd : il comando ioctl

[in/out] args : puntatore agli argomenti ioctl()

API varie

Metodi nell'API Varie

prendi tempo()

Restituisce l'ora corrente del sistema (in nanosecondi).

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

[in] clock_id : dipendente dalla piattaforma; passa zero per impostazione predefinita

[in] flags : riservato, dovrebbe essere zero

[out] time : puntatore a un valore int64_t in cui memorizzare l'ora corrente

[retval]: NO_ERROR in caso di successo; altrimenti un errore negativo

nanosonno()

Sospende l'esecuzione dell'applicazione chiamante per un periodo di tempo specificato e la riprende trascorso tale periodo.

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

[in] clock_id : riservato, dovrebbe essere zero

[in] flags : riservato, dovrebbe essere zero

[in] sleep_time : tempo di sonno in nanosecondi

[retval]: NO_ERROR in caso di successo; altrimenti un errore negativo

Esempio di server di applicazioni attendibile

La seguente applicazione di esempio mostra l'utilizzo delle API precedenti. L'esempio crea un servizio "eco" che gestisce più connessioni in ingresso e riflette al chiamante tutti i messaggi che riceve dai client originati dal lato sicuro o non sicuro.

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

Il metodo run_end_to_end_msg_test() invia 10.000 messaggi in modo asincrono al servizio "echo" e gestisce le risposte.

static int run_echo_test(void)
{
  int rc;
  handle_t chan;
  uevent_t uevt;
  uint8_t tx_buf[64];
  uint8_t rx_buf[64];
  ipc_msg_info_t inf;
  ipc_msg_t   tx_msg;
  iovec_t     tx_iov;
  ipc_msg_t   rx_msg;
  iovec_t     rx_iov;

  /* prepare tx message buffer */
  tx_iov.base = tx_buf;
  tx_iov.len  = sizeof(tx_buf);
  tx_msg.num_iov = 1;
  tx_msg.iov     = &tx_iov;
  tx_msg.num_handles = 0;
  tx_msg.handles = NULL;

  memset (tx_buf, 0x55, sizeof(tx_buf));

  /* prepare rx message buffer */
  rx_iov.base = rx_buf;
  rx_iov.len  = sizeof(rx_buf);
  rx_msg.num_iov = 1;
  rx_msg.iov     = &rx_iov;
  rx_msg.num_handles = 0;
  rx_msg.handles = NULL;

  /* open connection to echo service */
  rc = sync_connect(srv_name, 1000);
  if(rc < 0)
    return rc;

  /* got channel */
  chan = (handle_t)rc;

  /* send/receive 10000 messages asynchronously. */
  uint tx_cnt = 10000;
  uint rx_cnt = 10000;

  while (tx_cnt || rx_cnt) {
    /* send messages until all buffers are full */
while (tx_cnt) {
    rc = send_msg(chan, &tx_msg);
      if (rc == ERR_NOT_ENOUGH_BUFFER)
      break;  /* no more space */
    if (rc != 64) {
      if (rc > 0) {
        /* incomplete send */
        rc = ERR_NOT_VALID;
}
      goto abort_test;
}
    tx_cnt--;
  }

  /* wait for reply msg or room */
  rc = wait(chan, &uevt, 1000);
  if (rc != NO_ERROR)
    goto abort_test;

  /* drain all messages */
  while (rx_cnt) {
    /* get a reply */
      rc = get_msg(chan, &inf);
    if (rc == ERR_NO_MSG)
        break;  /* no more messages  */
  if (rc != NO_ERROR)
goto abort_test;

  /* read reply data */
    rc = read_msg(chan, inf.id, 0, &rx_msg);
  if (rc != 64) {
    /* unexpected reply length */
    rc = ERR_NOT_VALID;
    goto abort_test;
}

  /* discard reply */
  rc = put_msg(chan, inf.id);
  if (rc != NO_ERROR)
    goto abort_test;
  rx_cnt--;
  }
}

abort_test:
  close(chan);
  return rc;
}

API e applicazioni del mondo non sicuro

Un insieme di servizi Trusty, pubblicati dal lato sicuro e contrassegnati con l'attributo IPC_PORT_ALLOW_NS_CONNECT , sono accessibili al kernel e ai programmi dello spazio utente in esecuzione sul lato non sicuro.

L'ambiente di esecuzione sul lato non sicuro (kernel e spazio utente) è drasticamente diverso dall'ambiente di esecuzione sul lato sicuro. Pertanto, anziché un'unica libreria per entrambi gli ambienti, esistono due diversi set di API. Nel kernel, l'API client è fornita dal driver del kernel trusty-ipc e registra un nodo del dispositivo a caratteri che può essere utilizzato dai processi dello spazio utente per comunicare con i servizi in esecuzione sul lato sicuro.

Spazio utente API client IPC affidabile

La libreria API Trusty IPC Client dello spazio utente è uno strato sottile sopra il nodo del dispositivo fd .

Un programma in spazio utente avvia una sessione di comunicazione chiamando tipc_connect() , inizializzando una connessione a un servizio Trusty specificato. Internamente, la chiamata tipc_connect() apre un nodo del dispositivo specificato per ottenere un descrittore di file e invoca una chiamata TIPC_IOC_CONNECT ioctl() con il parametro argp che punta a una stringa contenente un nome di servizio a cui connettersi.

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

Il descrittore di file risultante può essere utilizzato solo per comunicare con il servizio per il quale è stato creato. Il descrittore di file dovrebbe essere chiuso chiamando tipc_close() quando la connessione non è più richiesta.

Il descrittore di file ottenuto dalla chiamata tipc_connect() si comporta come un tipico nodo di dispositivo a caratteri; il descrittore del file:

  • Se necessario, è possibile passare alla modalità non bloccante
  • È possibile scrivere utilizzando una chiamata write() standard per inviare messaggi all'altra parte
  • Può essere interrogato (usando chiamate poll() o select() ) per verificare la disponibilità dei messaggi in entrata come un normale descrittore di file
  • Può essere letto per recuperare i messaggi in arrivo

Un chiamante invia un messaggio al servizio Trusty eseguendo una chiamata di scrittura per l' fd specificato. Tutti i dati passati alla write() di cui sopra vengono trasformati in un messaggio dal driver trusty-ipc. Il messaggio viene recapitato al lato sicuro dove i dati vengono gestiti dal sottosistema IPC nel kernel Trusty e instradati alla destinazione corretta e recapitati a un ciclo di eventi dell'app come evento IPC_HANDLE_POLL_MSG su un particolare handle di canale. A seconda del particolare protocollo specifico del servizio, il servizio Trusty può inviare uno o più messaggi di risposta che vengono restituiti al lato non sicuro e inseriti nella coda di messaggi del descrittore di file del canale appropriato per essere recuperati dall'applicazione dello spazio utente read() chiamata.

tipc_connect()

Apre un nodo del dispositivo tipc specificato e avvia una connessione a un servizio Trusty specificato.

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

[in] dev_name : percorso del nodo del dispositivo Trusty IPC da aprire

[in] srv_name : nome di un servizio Trusty pubblicato a cui connettersi

[retval]: descrittore di file valido in caso di successo, -1 altrimenti.

tipc_close()

Chiude la connessione al servizio Trusty specificato da un descrittore di file.

int tipc_close(int fd);

[in] fd : descrittore di file precedentemente aperto da una chiamata tipc_connect()

API client IPC affidabile del kernel

L'API Trusty IPC Client del kernel è disponibile per i driver del kernel. L'API Trusty IPC dello spazio utente è implementata su questa API.

In generale, l'utilizzo tipico di questa API consiste nel fatto che un chiamante crea un oggetto struct tipc_chan utilizzando la funzione tipc_create_channel() e quindi utilizzando la chiamata tipc_chan_connect() per avviare una connessione al servizio Trusty IPC in esecuzione sul lato sicuro. La connessione al lato remoto può essere terminata chiamando tipc_chan_shutdown() seguito da tipc_chan_destroy() per ripulire le risorse.

Dopo aver ricevuto una notifica (tramite il callback handle_event() ) che una connessione è stata stabilita con successo, un chiamante esegue le seguenti operazioni:

  • Ottiene un buffer dei messaggi utilizzando la chiamata tipc_chan_get_txbuf_timeout()
  • Compone un messaggio e
  • Accoda il messaggio utilizzando il metodo tipc_chan_queue_msg() per la consegna a un servizio Trusty (sul lato sicuro), a cui è connesso il canale

Dopo che l'accodamento ha avuto esito positivo, il chiamante dovrebbe dimenticare il buffer dei messaggi poiché il buffer dei messaggi alla fine ritorna nel pool di buffer libero dopo l'elaborazione da parte del lato remoto (per riutilizzo successivo, per altri messaggi). L'utente deve solo chiamare tipc_chan_put_txbuf() se non riesce ad accodare tale buffer o se non è più richiesto.

Un utente API riceve messaggi dal lato remoto gestendo un callback di notifica handle_msg() (chiamato nel contesto della coda di lavoro trusty-ipc rx ) che fornisce un puntatore a un buffer rx contenente un messaggio in entrata da gestire.

Si prevede che l'implementazione della callback handle_msg() restituisca un puntatore a una struct tipc_msg_buf . Può essere uguale al buffer dei messaggi in entrata se viene gestito localmente e non è più necessario. In alternativa, può essere un nuovo buffer ottenuto da una chiamata tipc_chan_get_rxbuf() se il buffer in entrata è in coda per un'ulteriore elaborazione. Un buffer rx scollegato deve essere tracciato ed eventualmente rilasciato utilizzando una chiamata tipc_chan_put_rxbuf() quando non è più necessario.

Metodi nell'API client Kernel Trusty IPC

tipc_create_channel()

Crea e configura un'istanza di un canale Trusty IPC per un particolare dispositivo trusty-ipc.

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

[in] dev : puntatore al trusty-ipc per il quale viene creato il canale del dispositivo

[in] ops : puntatore a una struct tipc_chan_ops , con callback specifici del chiamante compilati

[in] cb_arg : puntatore ai dati che verranno passati ai callback tipc_chan_ops

[retval]: puntatore a un'istanza appena creata della struct tipc_chan in caso di successo, ERR_PTR(err) altrimenti

In generale, un chiamante deve fornire due callback che vengono richiamati in modo asincrono quando si verifica l'attività corrispondente.

L' void (*handle_event)(void *cb_arg, int event) viene richiamato per notificare a un chiamante una modifica dello stato del canale.

[in] cb_arg : puntatore ai dati passati a una chiamata tipc_create_channel()

[in] event : un evento che può essere uno dei seguenti valori:

  • TIPC_CHANNEL_CONNECTED - indica una connessione riuscita al lato remoto
  • TIPC_CHANNEL_DISCONNECTED - indica che il lato remoto ha negato la nuova richiesta di connessione o ha richiesto la disconnessione per il canale precedentemente connesso
  • TIPC_CHANNEL_SHUTDOWN - indica che il lato remoto si sta spegnendo, terminando permanentemente tutte le connessioni

La callback struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) viene invocata per fornire la notifica che un nuovo messaggio è stato ricevuto su un canale specificato:

  • [in] cb_arg : puntatore ai dati passati alla chiamata tipc_create_channel()
  • [in] mb : puntatore a una struct tipc_msg_buf che descrive un messaggio in entrata
  • [retval]: ci si aspetta che l'implementazione del callback restituisca un puntatore a una struct tipc_msg_buf che può essere lo stesso puntatore ricevuto come parametro mb se il messaggio è gestito localmente e non è più richiesto (o può essere un nuovo buffer ottenuto dal chiamata tipc_chan_get_rxbuf() )

tipc_chan_connect()

Avvia una connessione al servizio Trusty IPC specificato.

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

[in] chan : puntatore a un canale restituito dalla chiamata tipc_create_chan()

[in] port : puntatore a una stringa contenente il nome del servizio a cui connettersi

[retval]: 0 in caso di successo, altrimenti un errore negativo

Il chiamante viene avvisato quando viene stabilita una connessione ricevendo un callback handle_event .

tipc_chan_shutdown()

Termina una connessione al servizio Trusty IPC precedentemente avviata da una chiamata tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : puntatore a un canale restituito da una chiamata tipc_create_chan()

tipc_chan_destroy()

Distrugge un canale Trusty IPC specificato.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : puntatore a un canale restituito dalla chiamata tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Ottiene un buffer dei messaggi che può essere utilizzato per inviare dati su un canale specificato. Se il buffer non è immediatamente disponibile, il chiamante potrebbe essere bloccato per il timeout specificato (in millisecondi).

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

[in] chan : puntatore al canale su cui mettere in coda un messaggio

[in] chan : timeout massimo per attendere finché il buffer tx diventa disponibile

[retval]: un buffer di messaggio valido in caso di successo, ERR_PTR(err) in caso di errore

tipc_chan_queue_msg()

Accoda un messaggio da inviare sui canali Trusty IPC specificati.

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

[in] chan : puntatore al canale su cui mettere in coda il messaggio

[in] mb: puntatore al messaggio da mettere in coda (ottenuto da una chiamata tipc_chan_get_txbuf_timeout() )

[retval]: 0 in caso di successo, altrimenti un errore negativo

tipc_chan_put_txbuf()

Rilascia il buffer dei messaggi Tx specificato precedentemente ottenuto da una chiamata tipc_chan_get_txbuf_timeout() .

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

[in] chan : puntatore al canale a cui appartiene questo buffer dei messaggi

[in] mb : puntatore al buffer dei messaggi da rilasciare

[retval]: Nessuno

tipc_chan_get_rxbuf()

Ottiene un nuovo buffer dei messaggi che può essere utilizzato per ricevere messaggi sul canale specificato.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : puntatore a un canale a cui appartiene questo buffer di messaggi

[retval]: un buffer di messaggio valido in caso di successo, ERR_PTR(err) in caso di errore

tipc_chan_put_rxbuf()

Rilascia un buffer di messaggi specificato precedentemente ottenuto da una chiamata tipc_chan_get_rxbuf() .

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

[in] chan : puntatore a un canale a cui appartiene questo buffer di messaggi

[in] mb : puntatore a un buffer di messaggi da rilasciare

[retval]: Nessuno