Référence d'API fiable

Trusty fournit des API pour développer deux classes d'applications/services :

  • Applications ou services de confiance qui s'exécutent sur le processeur TEE
  • Applications normales/non fiables qui s'exécutent sur le processeur principal et utilisent les services fournis par les applications approuvées

L'API Trusty décrit généralement le système de communication inter-processus (IPC) Trusty, y compris les communications avec le monde non sécurisé. Les logiciels exécutés sur le processeur principal peuvent utiliser les API Trusty pour se connecter à des applications/services de confiance et échanger des messages arbitraires avec eux, tout comme un service réseau sur IP. Il appartient à l'application de déterminer le format de données et la sémantique de ces messages à l'aide d'un protocole au niveau de l'application. La livraison fiable des messages est garantie par l'infrastructure Trusty sous-jacente (sous la forme de pilotes exécutés sur le processeur principal) et la communication est complètement asynchrone.

Ports et canaux

Les ports sont utilisés par les applications Trusty pour exposer les points de terminaison du service sous la forme d'un chemin nommé auquel les clients se connectent. Cela donne un ID de service simple, basé sur une chaîne, que les clients peuvent utiliser. La convention de dénomination est celle d'un style DNS inversé, par exemple com.google.servicename .

Lorsqu'un client se connecte à un port, le client reçoit un canal pour interagir avec un service. Le service doit accepter une connexion entrante et, lorsqu'il le fait, il reçoit également un canal. Essentiellement, les ports sont utilisés pour rechercher des services, puis la communication s'effectue sur une paire de canaux connectés (c'est-à-dire des instances de connexion sur un port). Lorsqu'un client se connecte à un port, une connexion symétrique bidirectionnelle est établie. Grâce à ce chemin en duplex intégral, les clients et les serveurs peuvent échanger des messages arbitraires jusqu'à ce que l'un ou l'autre des côtés décide de couper la connexion.

Seules les applications approuvées du côté sécurisé ou les modules du noyau Trusty peuvent créer des ports. Les applications exécutées du côté non sécurisé (dans le monde normal) ne peuvent se connecter qu'aux services publiés du côté sécurisé.

Selon les besoins, une application de confiance peut être à la fois client et serveur. Une application approuvée qui publie un service (en tant que serveur) peut avoir besoin de se connecter à d'autres services (en tant que client).

Gérer l'API

Les handles sont des entiers non signés représentant des ressources telles que des ports et des canaux, similaires aux descripteurs de fichiers sous UNIX. Une fois les handles créés, ils sont placés dans une table de handles spécifique à l’application et peuvent être référencés ultérieurement.

Un appelant peut associer des données privées à un handle en utilisant la méthode set_cookie() .

Méthodes dans l'API Handle

Les handles ne sont valides que dans le contexte d’une application. Une application ne doit pas transmettre la valeur d’un handle à d’autres applications, sauf indication contraire explicite. Une valeur de handle doit uniquement être interprétée en la comparant avec le INVALID_IPC_HANDLE #define, qu'une application peut utiliser comme indication qu'un handle est invalide ou non défini.

Associe les données privées fournies par l’appelant à un handle spécifié.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : tout handle renvoyé par l'un des appels API

[in] cookie : pointeur vers des données arbitraires de l'espace utilisateur dans l'application Trusty

[retval] : NO_ERROR en cas de succès, < 0 code d'erreur sinon

Cet appel est utile pour gérer les événements lorsqu'ils se produisent ultérieurement après la création du handle. Le mécanisme de gestion des événements fournit le handle et son cookie au gestionnaire d'événements.

Les handles peuvent être attendus pour des événements en utilisant l'appel wait() .

attendez()

Attend qu'un événement se produise sur un handle donné pendant une période de temps spécifiée.

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

[in] handle_id : tout handle renvoyé par l'un des appels API

[out] event : Un pointeur vers la structure représentant un événement survenu sur ce handle

[in] timeout_msecs : une valeur de délai d'attente en millisecondes ; une valeur de -1 est un délai d'attente infini

[retval] : NO_ERROR si un événement valide s'est produit dans un intervalle de délai spécifié ; ERR_TIMED_OUT si un délai d'attente spécifié s'est écoulé mais qu'aucun événement ne s'est produit ; < 0 pour les autres erreurs

En cas de succès ( retval == NO_ERROR ), l'appel wait() remplit une structure uevent_t spécifiée avec des informations sur l'événement qui s'est produit.

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;

Le champ event contient une combinaison des valeurs suivantes :

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 - aucun événement n'est réellement en attente, l'appelant doit redémarrer l'attente

IPC_HANDLE_POLL_ERROR - une erreur interne non spécifiée s'est produite

IPC_HANDLE_POLL_READY - dépend du type de handle, comme suit :

  • Pour les ports, cette valeur indique qu'il y a une connexion en attente
  • Pour les canaux, cette valeur indique qu'une connexion asynchrone (voir connect() ) a été établie

Les événements suivants ne concernent que les chaînes :

  • IPC_HANDLE_POLL_HUP - indique qu'un canal a été fermé par un homologue
  • IPC_HANDLE_POLL_MSG - indique qu'il y a un message en attente pour ce canal
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - indique qu'un appelant précédemment bloqué en envoi peut tenter à nouveau d'envoyer un message (voir la description de send_msg() pour plus de détails)

Un gestionnaire d'événements doit être prêt à gérer une combinaison d'événements spécifiés, car plusieurs bits peuvent être définis en même temps. Par exemple, pour un canal, il est possible d'avoir des messages en attente, et une connexion fermée par un homologue en même temps.

La plupart des événements sont collants. Ils persistent tant que la condition sous-jacente persiste (par exemple, tous les messages en attente sont reçus et les demandes de connexion en attente sont traitées). L'exception est le cas de l'événement IPC_HANDLE_POLL_SEND_UNBLOCKED , qui est effacé lors d'une lecture et l'application n'a qu'une seule chance de le gérer.

Les handles peuvent être détruits en appelant la méthode close() .

fermer()

Détruit la ressource associée au handle spécifié et la supprime de la table des handles.

long close(uint32_t handle_id);

[in] handle_id : Handle à détruire

[retval] : 0 si succès ; une erreur négative sinon

API du serveur

Un serveur commence par créer un ou plusieurs ports nommés représentant ses points de terminaison de service. Chaque port est représenté par une poignée.

Méthodes dans l'API du serveur

port_create()

Crée un port de service nommé.

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

[in] path : Le nom de chaîne du port (comme décrit ci-dessus). Ce nom doit être unique dans tout le système ; les tentatives de création d’un duplicata échoueront.

[in] num_recv_bufs : Le nombre maximum de tampons qu'un canal sur ce port peut pré-allouer pour faciliter l'échange de données avec le client. Les tampons sont comptés séparément pour les données allant dans les deux sens, donc spécifier 1 ici signifierait qu'un tampon d'envoi et un tampon de réception sont pré-alloués. En général, le nombre de tampons requis dépend de l'accord de protocole de niveau supérieur entre le client et le serveur. Le nombre peut être aussi petit que 1 dans le cas d'un protocole très synchrone (envoyer un message, recevoir une réponse avant d'en envoyer une autre). Mais le nombre peut être plus élevé si le client s'attend à envoyer plus d'un message avant qu'une réponse puisse apparaître (par exemple, un message comme prologue et un autre comme commande réelle). Les jeux de tampons alloués sont par canal, donc deux connexions distinctes (canaux) auraient des jeux de tampons distincts.

[in] recv_buf_size : taille maximale de chaque tampon individuel dans l'ensemble de tampons ci-dessus. Cette valeur dépend du protocole et limite effectivement la taille maximale des messages que vous pouvez échanger avec un homologue.

[in] flags : une combinaison d'indicateurs qui spécifie un comportement de port supplémentaire

Cette valeur doit être une combinaison des valeurs suivantes :

IPC_PORT_ALLOW_TA_CONNECT - permet une connexion à partir d'autres applications sécurisées

IPC_PORT_ALLOW_NS_CONNECT - permet une connexion depuis le monde non sécurisé

[retval] : handle du port créé s'il est non négatif ou une erreur spécifique s'il est négatif

Le serveur interroge ensuite la liste des descripteurs de port pour les connexions entrantes à l’aide de l’appel wait() . À la réception d'une demande de connexion indiquée par le bit IPC_HANDLE_POLL_READY défini dans le champ event de la structure uevent_t , le serveur doit appeler accept() pour terminer l'établissement d'une connexion et créer un canal (représenté par un autre handle) qui peut ensuite être interrogé pour les messages entrants. .

accepter()

Accepte une connexion entrante et obtient un handle vers un canal.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : Handle représentant le port auquel un client s'est connecté

[out] peer_uuid : Pointeur vers une structure uuid_t à remplir avec l'UUID de l'application client qui se connecte. Il sera mis à zéro si la connexion provient d'un monde non sécurisé.

[retval] : Handle vers un canal (s'il est non négatif) sur lequel le serveur peut échanger des messages avec le client (ou un code d'erreur dans le cas contraire)

API cliente

Cette section contient les méthodes de l'API client.

Méthodes dans l'API client

connecter()

Initie une connexion à un port spécifié par son nom.

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

[in] path : Nom d'un port publié par une application Trusty

[in] flags : spécifie un comportement supplémentaire et facultatif

[retval] : handle vers un canal sur lequel les messages peuvent être échangés avec le serveur ; erreur si négatif

Si aucun flags n'est spécifié (le paramètre flags est défini sur 0), l'appel connect() initie une connexion synchrone à un port spécifié qui renvoie immédiatement une erreur si le port n'existe pas et crée un bloc jusqu'à ce que le serveur accepte une connexion. .

Ce comportement peut être modifié en spécifiant une combinaison de deux valeurs, décrites ci-dessous :

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

IPC_CONNECT_WAIT_FOR_PORT - force un appel connect() à attendre si le port spécifié n'existe pas immédiatement lors de l'exécution, au lieu d'échouer immédiatement.

IPC_CONNECT_ASYNC - si défini, initie une connexion asynchrone. Une application doit interroger le handle renvoyé (en appelant wait() pour un événement d'achèvement de connexion indiqué par le bit IPC_HANDLE_POLL_READY défini dans le champ d'événement de la structure uevent_t avant de commencer le fonctionnement normal.

API de messagerie

Les appels API de messagerie permettent l'envoi et la lecture de messages via une connexion (canal) préalablement établie. Les appels de l'API de messagerie sont les mêmes pour les serveurs et les clients.

Un client reçoit un handle vers un canal en émettant un appel connect() , et un serveur obtient un handle vers un canal à partir d'un appel accept() , décrit ci-dessus.

Structure d'un message Trusty

Comme le montre ce qui suit, les messages échangés par l'API Trusty ont une structure minimale, laissant au serveur et au client le soin de s'entendre sur la sémantique du contenu réel :

/*
 *  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 message peut être composé d'un ou plusieurs tampons non contigus représentés par un tableau de structures iovec_t . Trusty effectue des lectures et des écritures par dispersion sur ces blocs à l'aide du tableau iov . Le contenu des tampons pouvant être décrits par le tableau iov est complètement arbitraire.

Méthodes dans l'API de messagerie

envoyer_msg()

Envoie un message sur un canal spécifié.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Handle du canal sur lequel envoyer le message

[in] msg : Pointeur vers la ipc_msg_t structure décrivant le message

[retval] : nombre total d'octets envoyés en cas de succès ; une erreur négative sinon

Si le client (ou le serveur) tente d'envoyer un message sur le canal et qu'il n'y a pas d'espace dans la file d'attente des messages du homologue de destination, le canal peut entrer dans un état d'envoi bloqué (cela ne devrait jamais se produire pour un simple protocole de requête/réponse synchrone). mais cela peut se produire dans des cas plus compliqués) qui est indiqué par le renvoi d'un code d'erreur ERR_NOT_ENOUGH_BUFFER . Dans un tel cas, l'appelant doit attendre que l'homologue libère de l'espace dans sa file d'attente de réception en récupérant les messages de traitement et de retrait, indiqué par le bit IPC_HANDLE_POLL_SEND_UNBLOCKED défini dans le champ event de la structure uevent_t renvoyé par l'appel wait() .

get_msg()

Obtient des méta-informations sur le prochain message dans une file d'attente de messages entrants

d'un canal spécifié.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Handle du canal sur lequel un nouveau message doit être récupéré

[out] msg_info : Structure des informations du message décrite comme suit :

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

Chaque message se voit attribuer un identifiant unique parmi l'ensemble des messages en attente, et la longueur totale de chaque message est renseignée. Si cela est configuré et autorisé par le protocole, il peut y avoir plusieurs messages en attente (ouverts) à la fois pour un canal particulier.

[retval] : NO_ERROR en cas de succès ; une erreur négative sinon

read_msg()

Lit le contenu du message avec l'ID spécifié à partir du décalage spécifié.

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

[in] handle : Handle du canal à partir duquel lire le message

[in] msg_id : ID du message à lire

[in] offset : Décalage dans le message à partir duquel commencer la lecture

[out] msg : pointeur vers la structure ipc_msg_t décrivant un ensemble de tampons dans lesquels stocker les données des messages entrants

[retval] : nombre total d'octets stockés dans les tampons msg en cas de succès ; une erreur négative sinon

La méthode read_msg peut être appelée plusieurs fois en commençant à un décalage différent (pas nécessairement séquentiel) selon les besoins.

put_msg()

Retrait un message avec un ID spécifié.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Handle du canal sur lequel le message est arrivé

[in] msg_id : ID du message en cours de retrait

[retval] : NO_ERROR en cas de succès ; une erreur négative sinon

Le contenu du message n'est plus accessible une fois qu'un message a été retiré et que le tampon qu'il occupait a été libéré.

API de descripteur de fichier

L'API File Descriptor inclut les appels read() , write() et ioctl() . Tous ces appels peuvent fonctionner sur un ensemble prédéfini (statique) de descripteurs de fichiers traditionnellement représentés par de petits nombres. Dans l'implémentation actuelle, l'espace du descripteur de fichier est distinct de l'espace de gestion IPC. L'API File Descriptor de Trusty est similaire à une API traditionnelle basée sur un descripteur de fichier.

Par défaut, il existe 3 descripteurs de fichiers prédéfinis (standards et connus) :

  • 0 - entrée standard. L'implémentation par défaut de l'entrée standard fd est sans opération (car les applications de confiance ne sont pas censées avoir une console interactive), donc la lecture, l'écriture ou l'appel ioctl() sur fd 0 devrait renvoyer une erreur ERR_NOT_SUPPORTED .
  • 1 - sortie standard. Les données écrites sur la sortie standard peuvent être acheminées (en fonction du niveau de débogage LK) vers UART et/ou un journal mémoire disponible du côté non sécurisé, en fonction de la plate-forme et de la configuration. Les journaux et messages de débogage non critiques doivent apparaître dans la sortie standard. Les méthodes read() et ioctl() ne fonctionnent pas et devraient renvoyer une erreur ERR_NOT_SUPPORTED .
  • 2 - erreur standard. Les données écrites sur l'erreur standard doivent être acheminées vers l'UART ou le journal mémoire disponible du côté non sécurisé, en fonction de la plate-forme et de la configuration. Il est recommandé d'écrire uniquement les messages critiques dans l'erreur standard, car ce flux est très probablement non limité. Les méthodes read() et ioctl() ne fonctionnent pas et devraient renvoyer une erreur ERR_NOT_SUPPORTED .

Même si cet ensemble de descripteurs de fichiers peut être étendu pour implémenter davantage fds (pour implémenter des extensions spécifiques à la plate-forme), l'extension des descripteurs de fichiers doit être exercée avec prudence. L'extension des descripteurs de fichiers est susceptible de créer des conflits et n'est généralement pas recommandée.

Méthodes dans l'API du descripteur de fichier

lire()

Tente de lire jusqu'à count les octets de données à partir d'un descripteur de fichier spécifié.

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

[in] fd : Descripteur de fichier à partir duquel lire

[out] buf : pointeur vers un tampon dans lequel stocker les données

[in] count : Nombre maximum d'octets à lire

[retval] : nombre d'octets lus renvoyé ; une erreur négative sinon

écrire()

Écrit pour count les octets de données dans le descripteur de fichier spécifié.

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

[in] fd : Descripteur de fichier dans lequel écrire

[out] buf : Pointeur vers les données à écrire

[in] count : nombre maximum d'octets à écrire

[retval] : renvoyé le nombre d'octets écrits ; une erreur négative sinon

ioctl()

Appelle une commande ioctl spécifiée pour un descripteur de fichier donné.

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

[in] fd : Descripteur de fichier sur lequel invoquer ioctl()

[in] cmd : La commande ioctl

[in/out] args : pointeur vers les arguments ioctl()

API diverses

Méthodes dans l'API Diverse

obtenir du temps()

Renvoie l'heure actuelle du système (en nanosecondes).

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

[in] clock_id : dépendant de la plate-forme ; passer zéro par défaut

[in] flags : réservé, doit être nul

[out] time : pointeur vers une valeur int64_t dans laquelle stocker l'heure actuelle

[retval] : NO_ERROR en cas de succès ; une erreur négative sinon

nanosommeil()

Suspend l'exécution de l'application appelante pendant une période de temps spécifiée et la reprend après cette période.

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

[in] clock_id : réservé, doit être nul

[in] flags : réservé, doit être nul

[in] sleep_time : Temps de sommeil en nanosecondes

[retval] : NO_ERROR en cas de succès ; une erreur négative sinon

Exemple de serveur d'applications de confiance

L'exemple d'application suivant montre l'utilisation des API ci-dessus. L'exemple crée un service « écho » qui gère plusieurs connexions entrantes et renvoie à l'appelant tous les messages qu'il reçoit des clients provenant du côté sécurisé ou non sécurisé.

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

La méthode run_end_to_end_msg_test() envoie 10 000 messages de manière asynchrone au service « echo » et gère les réponses.

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 et applications mondiales non sécurisées

Un ensemble de services Trusty, publiés du côté sécurisé et marqués de l'attribut IPC_PORT_ALLOW_NS_CONNECT , sont accessibles aux programmes du noyau et de l'espace utilisateur exécutés du côté non sécurisé.

L’environnement d’exécution du côté non sécurisé (noyau et espace utilisateur) est radicalement différent de l’environnement d’exécution du côté sécurisé. Par conséquent, plutôt qu’une bibliothèque unique pour les deux environnements, il existe deux ensembles d’API différents. Dans le noyau, l'API client est fournie par le pilote du noyau trusty-ipc et enregistre un nœud de périphérique de caractères qui peut être utilisé par les processus de l'espace utilisateur pour communiquer avec les services exécutés du côté sécurisé.

Espace utilisateur API client Trusty IPC

La bibliothèque API client Trusty IPC de l'espace utilisateur est une fine couche au-dessus du nœud de périphérique fd .

Un programme en espace utilisateur démarre une session de communication en appelant tipc_connect() , initialisant une connexion à un service Trusty spécifié. En interne, l'appel tipc_connect() ouvre un nœud de périphérique spécifié pour obtenir un descripteur de fichier et appelle un appel TIPC_IOC_CONNECT ioctl() avec le paramètre argp pointant vers une chaîne contenant un nom de service auquel se connecter.

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

Le descripteur de fichier résultant ne peut être utilisé que pour communiquer avec le service pour lequel il a été créé. Le descripteur de fichier doit être fermé en appelant tipc_close() lorsque la connexion n'est plus requise.

Le descripteur de fichier obtenu par l'appel tipc_connect() se comporte comme un nœud de périphérique de caractères typique ; le descripteur de fichier :

  • Peut être commuté en mode non bloquant si nécessaire
  • Peut être écrit à l'aide d'un appel write() standard pour envoyer des messages à l'autre côté
  • Peut être interrogé (à l'aide d'appels poll() ou d'appels select() ) pour connaître la disponibilité des messages entrants en tant que descripteur de fichier standard
  • Peut être lu pour récupérer les messages entrants

Un appelant envoie un message au service Trusty en exécutant un appel d'écriture pour le fd spécifié. Toutes les données transmises à l'appel write() ci-dessus sont transformées en message par le pilote trusty-ipc. Le message est transmis au côté sécurisé où les données sont gérées par le sous-système IPC dans le noyau Trusty et acheminées vers la destination appropriée et transmises à une boucle d'événements d'application en tant qu'événement IPC_HANDLE_POLL_MSG sur un descripteur de canal particulier. En fonction du protocole particulier spécifique au service, le service Trusty peut envoyer un ou plusieurs messages de réponse qui sont renvoyés au côté non sécurisé et placés dans la file d'attente de messages du descripteur de fichier de canal approprié pour être récupérés par l'application d'espace utilisateur read() appel.

tipc_connect()

Ouvre un nœud de périphérique tipc spécifié et initie une connexion à un service Trusty spécifié.

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

[in] dev_name : Chemin d'accès au nœud du périphérique Trusty IPC à ouvrir

[in] srv_name : Nom d'un service Trusty publié auquel se connecter

[retval] : descripteur de fichier valide en cas de succès, -1 sinon.

tipc_close()

Ferme la connexion au service Trusty spécifié par un descripteur de fichier.

int tipc_close(int fd);

[in] fd : Descripteur de fichier précédemment ouvert par un appel tipc_connect()

API client IPC de confiance du noyau

L'API client Trusty IPC du noyau est disponible pour les pilotes du noyau. L'API Trusty IPC de l'espace utilisateur est implémentée au-dessus de cette API.

En général, l'utilisation typique de cette API consiste en un appelant créant un objet struct tipc_chan à l'aide de la fonction tipc_create_channel() , puis en utilisant l'appel tipc_chan_connect() pour initier une connexion au service Trusty IPC exécuté du côté sécurisé. La connexion au côté distant peut être interrompue en appelant tipc_chan_shutdown() suivi de tipc_chan_destroy() pour nettoyer les ressources.

Lors de la réception d'une notification (via le rappel handle_event() ) indiquant qu'une connexion a été établie avec succès, un appelant effectue les opérations suivantes :

  • Obtient un tampon de messages en utilisant l'appel tipc_chan_get_txbuf_timeout()
  • Compose un message, et
  • Met le message en file d'attente à l'aide de la méthode tipc_chan_queue_msg() pour le transmettre à un service Trusty (du côté sécurisé), auquel le canal est connecté.

Une fois la mise en file d'attente réussie, l'appelant doit oublier le tampon de message car le tampon de message retourne finalement dans le pool de tampons libres après traitement par le côté distant (pour une réutilisation ultérieure, pour d'autres messages). L'utilisateur n'a besoin d'appeler tipc_chan_put_txbuf() que s'il ne parvient pas à mettre ce tampon en file d'attente ou s'il n'est plus nécessaire.

Un utilisateur d'API reçoit des messages du côté distant en gérant un rappel de notification handle_msg() (qui est appelé dans le contexte de la file d'attente trusty-ipc rx ) qui fournit un pointeur vers un tampon rx contenant un message entrant à gérer.

On s'attend à ce que l'implémentation de rappel handle_msg() renvoie un pointeur vers une struct tipc_msg_buf . Il peut être identique au tampon des messages entrants s'il est géré localement et n'est plus nécessaire. Alternativement, il peut s'agir d'un nouveau tampon obtenu par un appel tipc_chan_get_rxbuf() si le tampon entrant est mis en file d'attente pour un traitement ultérieur. Un tampon rx détaché doit être suivi et éventuellement libéré à l'aide d'un appel tipc_chan_put_rxbuf() lorsqu'il n'est plus nécessaire.

Méthodes dans l'API client Kernel Trusty IPC

tipc_create_channel()

Crée et configure une instance d'un canal Trusty IPC pour un périphérique trusty-ipc particulier.

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

[in] dev : pointeur vers le trusty-ipc pour lequel le canal de périphérique est créé

[in] ops : pointeur vers une struct tipc_chan_ops , avec des rappels spécifiques à l'appelant renseignés

[in] cb_arg : Pointeur vers les données qui seront transmises aux rappels tipc_chan_ops

[retval] : pointeur vers une instance nouvellement créée de struct tipc_chan en cas de succès, ERR_PTR(err) sinon

En général, un appelant doit fournir deux rappels qui sont invoqués de manière asynchrone lorsque l'activité correspondante se produit.

L' void (*handle_event)(void *cb_arg, int event) est invoqué pour informer un appelant d'un changement d'état du canal.

[in] cb_arg : Pointeur vers les données transmises à un appel tipc_create_channel()

[in] event : un événement qui peut avoir l'une des valeurs suivantes :

  • TIPC_CHANNEL_CONNECTED - indique une connexion réussie au côté distant
  • TIPC_CHANNEL_DISCONNECTED - indique que le côté distant a refusé la nouvelle demande de connexion ou demandé la déconnexion pour le canal précédemment connecté
  • TIPC_CHANNEL_SHUTDOWN - indique que le côté distant est en train de s'arrêter, mettant fin définitivement à toutes les connexions

Le rappel struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) est invoqué pour fournir une notification indiquant qu'un nouveau message a été reçu sur un canal spécifié :

  • [in] cb_arg : Pointeur vers les données transmises à l'appel tipc_create_channel()
  • [in] mb : Pointeur vers une struct tipc_msg_buf décrivant un message entrant
  • [retval] : l'implémentation de rappel est censée renvoyer un pointeur vers une struct tipc_msg_buf qui peut être le même pointeur reçu en tant que paramètre mb si le message est géré localement et n'est plus requis (ou il peut s'agir d'un nouveau tampon obtenu par le appel tipc_chan_get_rxbuf() )

tipc_chan_connect()

Initie une connexion au service Trusty IPC spécifié.

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

[in] chan : Pointeur vers un canal renvoyé par l'appel tipc_create_chan()

[in] port : Pointeur vers une chaîne contenant le nom du service auquel se connecter

[retval] : 0 en cas de succès, une erreur négative sinon

L'appelant est averti lorsqu'une connexion est établie en recevant un rappel handle_event .

tipc_chan_shutdown()

Termine une connexion au service Trusty IPC précédemment initiée par un appel tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : Pointeur vers un canal renvoyé par un appel tipc_create_chan()

tipc_chan_destroy()

Détruit un canal Trusty IPC spécifié.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : Pointeur vers un canal renvoyé par l'appel tipc_create_chan()

tipc_chan_get_txbuf_timeout()

Obtient un tampon de messages qui peut être utilisé pour envoyer des données sur un canal spécifié. Si le tampon n'est pas immédiatement disponible, l'appelant peut être bloqué pendant le délai d'attente spécifié (en millisecondes).

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

[in] chan : Pointeur vers le canal sur lequel mettre un message en file d'attente

[in] chan : Délai d'attente maximum pour attendre que le tampon tx soit disponible

[retval] : Un tampon de message valide en cas de succès, ERR_PTR(err) en cas d'erreur

tipc_chan_queue_msg()

Met en file d'attente un message à envoyer sur les canaux Trusty IPC spécifiés.

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

[in] chan : Pointeur vers le canal sur lequel mettre le message en file d'attente

[in] mb: Pointeur vers le message à mettre en file d'attente (obtenu par un appel tipc_chan_get_txbuf_timeout() )

[retval] : 0 en cas de succès, une erreur négative sinon

tipc_chan_put_txbuf()

Libère le tampon de message Tx spécifié précédemment obtenu par un appel tipc_chan_get_txbuf_timeout() .

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

[in] chan : Pointeur vers le canal auquel appartient ce tampon de messages

[in] mb : Pointeur vers le tampon de messages à libérer

[récupération] : Aucun

tipc_chan_get_rxbuf()

Obtient un nouveau tampon de messages qui peut être utilisé pour recevoir des messages sur le canal spécifié.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Pointeur vers un canal auquel appartient ce tampon de messages

[retval] : Un tampon de message valide en cas de succès, ERR_PTR(err) en cas d'erreur

tipc_chan_put_rxbuf()

Libère un tampon de message spécifié précédemment obtenu par un appel tipc_chan_get_rxbuf() .

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

[in] chan : Pointeur vers un canal auquel appartient ce tampon de messages

[in] mb : Pointeur vers un tampon de messages à libérer

[récupération] : Aucun