Google 致力于为黑人社区推动种族平等。查看具体举措
Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Справочник по Trusty API

Trusty предоставляет API для разработки двух классов приложений / сервисов:

  • Надежные приложения или службы, работающие на процессоре TEE
  • Обычные / ненадежные приложения, которые работают на основном процессоре и используют службы, предоставляемые доверенными приложениями

Trusty API обычно описывает систему межпроцессного взаимодействия Trusty (IPC), включая связь с небезопасным миром. Программное обеспечение, работающее на главном процессоре, может использовать Trusty API для подключения к доверенным приложениям / службам и обмена с ними произвольными сообщениями, как сетевая служба по IP. Приложение должно определять формат данных и семантику этих сообщений с использованием протокола уровня приложения. Надежная доставка сообщений гарантируется базовой инфраструктурой Trusty (в виде драйверов, работающих на главном процессоре), а обмен данными является полностью асинхронным.

Порты и каналы

Порты используются приложениями Trusty для предоставления конечных точек службы в форме именованного пути, к которому подключаются клиенты. Это дает клиентам простой строковый идентификатор службы. Соглашение об именовании - это именование в стиле обратного DNS, например com.google.servicename .

Когда клиент подключается к порту, клиент получает канал для взаимодействия со службой. Служба должна принимать входящее соединение, и когда это происходит, она также получает канал. По сути, порты используются для поиска сервисов, а затем связь происходит по паре подключенных каналов (т. Е. Экземпляры соединения на порте). Когда клиент подключается к порту, устанавливается симметричное двунаправленное соединение. Используя этот полнодуплексный путь, клиенты и серверы могут обмениваться произвольными сообщениями до тех пор, пока одна из сторон не решит разорвать соединение.

Только доверенные приложения на стороне безопасности или модули ядра Trusty могут создавать порты. Приложения, работающие на незащищенной стороне (в обычном мире), могут подключаться только к службам, опубликованным на защищенной стороне.

В зависимости от требований доверенное приложение может быть одновременно и клиентом, и сервером. Доверенному приложению, публикующему службу (в качестве сервера), может потребоваться подключение к другим службам (в качестве клиента).

Обработка API

Дескрипторы - это целые числа без знака, представляющие ресурсы, такие как порты и каналы, аналогично файловым дескрипторам в UNIX. После создания дескрипторов они помещаются в таблицу дескрипторов для конкретного приложения, и на них можно будет ссылаться позже.

Вызывающий может связать личные данные с дескриптором с помощью set_cookie() .

Методы в Handle API

Дескрипторы действительны только в контексте приложения. Приложение не должно передавать значение дескриптора другим приложениям, если явно не указано иное. Значение дескриптора следует интерпретировать только путем сравнения его с INVALID_IPC_HANDLE #define, которое приложение может использовать как указание на то, что дескриптор недействителен или не установлен.

Связывает частные данные, предоставленные вызывающим абонентом, с указанным дескриптором.

long set_cookie(uint32_t handle, void *cookie)

[in] handle : Любой дескриптор, возвращаемый одним из вызовов API.

[in] cookie : указатель на произвольные данные пользовательского пространства в приложении Trusty.

[retval]: NO_ERROR в случае успеха, < 0 код ошибки в противном случае

Этот вызов полезен для обработки событий, когда они происходят позже после создания дескриптора. Механизм обработки событий возвращает дескриптор и его cookie обработчику событий.

Обработчики событий можно ожидать с помощью wait() .

Подождите()

Ожидает возникновения события на заданном дескрипторе в течение указанного периода времени.

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

[in] handle_id : любой дескриптор, возвращаемый одним из вызовов API.

[out] event : указатель на структуру, представляющую событие, которое произошло в этом дескрипторе.

[in] timeout_msecs : значение тайм-аута в миллисекундах; значение -1 - бесконечный тайм-аут

[retval]: NO_ERROR если допустимое событие произошло в течение указанного интервала тайм-аута; ERR_TIMED_OUT если указанный тайм-аут ERR_TIMED_OUT но событие не произошло; < 0 для других ошибок

В случае успеха ( retval == NO_ERROR ) вызов wait() заполняет указанную структуру uevent_t информацией о произошедшем событии.

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;

Поле event содержит комбинацию следующих значений:

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 - на самом деле нет ожидающих событий, вызывающий должен перезапустить ожидание

IPC_HANDLE_POLL_ERROR - произошла неуказанная внутренняя ошибка

IPC_HANDLE_POLL_READY - зависит от типа дескриптора, а именно:

  • Для портов это значение указывает на наличие ожидающего подключения.
  • Для каналов это значение указывает на то, что было установлено асинхронное соединение (см. connect() ).

Следующие события актуальны только для каналов:

  • IPC_HANDLE_POLL_HUP - указывает, что канал был закрыт одноранговым IPC_HANDLE_POLL_HUP
  • IPC_HANDLE_POLL_MSG - указывает, что для этого канала есть ожидающее сообщение
  • IPC_HANDLE_POLL_SEND_UNBLOCKED - указывает, что ранее заблокированный вызывающий абонент может попытаться отправить сообщение еще раз (подробности см. В описании send_msg() )

Обработчик событий должен быть подготовлен к обработке комбинации указанных событий, поскольку одновременно может быть установлено несколько битов. Например, для канала могут быть ожидающие сообщения сообщения и соединение, закрытое одноранговым узлом одновременно.

Большинство событий липкие. Они сохраняются до тех пор, пока сохраняется основное условие (например, все ожидающие сообщения получены и ожидающие запросы соединения обрабатываются). Исключением является случай события IPC_HANDLE_POLL_SEND_UNBLOCKED , которое сбрасывается при чтении, и приложение имеет только один шанс обработать его.

Дескрипторы можно уничтожить, вызвав метод close() .

близко()

Уничтожает ресурс, связанный с указанным дескриптором, и удаляет его из таблицы дескрипторов.

long close(uint32_t handle_id);

[in] handle_id : Дескриптор для уничтожения

[retval]: 0 в случае успеха; отрицательная ошибка в противном случае

Серверный API

Сервер начинает с создания одного или нескольких именованных портов, представляющих его конечные точки службы. Каждый порт представлен дескриптором.

Методы в серверном API

port_create ()

Создает именованный порт службы.

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

[in] path : Строковое имя порта (как описано выше). Это имя должно быть уникальным в системе; попытки создать дубликат потерпят неудачу.

[in] num_recv_bufs : максимальное количество буферов, которые канал на этом порту может предварительно выделить для облегчения обмена данными с клиентом. Буферы подсчитываются отдельно для данных, идущих в обоих направлениях, поэтому указание здесь 1 будет означать, что предварительно выделены 1 буфер отправки и 1 буфер приема. В общем, количество требуемых буферов зависит от соглашения о протоколе более высокого уровня между клиентом и сервером. В случае очень синхронного протокола это число может быть всего 1 (отправить сообщение, получить ответ перед отправкой другого). Но число может быть больше, если клиент ожидает отправить более одного сообщения, прежде чем может появиться ответ (например, одно сообщение как пролог, а другое как фактическая команда). Распределенные наборы буферов относятся к каждому каналу, поэтому два отдельных соединения (канала) будут иметь отдельные наборы буферов.

[in] recv_buf_size : Максимальный размер каждого отдельного буфера в указанном выше наборе буферов. Это значение зависит от протокола и эффективно ограничивает максимальный размер сообщения, которым вы можете обмениваться с одноранговым узлом.

[in] flags : комбинация флагов, определяющая дополнительное поведение порта.

Это значение должно быть комбинацией следующих значений:

IPC_PORT_ALLOW_TA_CONNECT - разрешает соединение из других безопасных приложений

IPC_PORT_ALLOW_NS_CONNECT - разрешает соединение из незащищенного мира

[retval]: дескриптор созданного порта, если неотрицательный, или конкретная ошибка, если отрицательный

Затем сервер опрашивает список дескрипторов портов для входящих подключений с помощью wait() . После приема запроса на установление соединения , указанного в IPC_HANDLE_POLL_READY битом в event поле uevent_t структуры, сервер должен вызвать accept() , чтобы завершить установление соединения и создать канал (представленный другой ручкой) , который затем может быть опрашивается для входящих сообщений .

accept ()

Принимает входящее соединение и получает дескриптор канала.

long accept(uint32_t handle_id, uuid_t *peer_uuid);

[in] handle_id : Дескриптор, представляющий порт, к которому подключился клиент.

[out] peer_uuid : указатель на структуру uuud_t должна быть заполнена UUID подключающегося клиентского приложения. Он будет установлен на все нули, если соединение возникло из небезопасного мира.

[retval]: дескриптор канала (если неотрицательный), по которому сервер может обмениваться сообщениями с клиентом (или код ошибки в противном случае)

Клиентский API

Этот раздел содержит методы клиентского API.

Методы в клиентском API

подключить ()

Инициирует соединение с портом, указанным по имени.

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

[in] path : Имя порта, опубликованного надежным приложением.

[in] flags : Определяет дополнительное, необязательное поведение.

[retval]: Дескриптор канала, по которому можно обмениваться сообщениями с сервером; ошибка если отрицательный

Если flags не указаны (параметр flags установлен в 0), вызов connect() инициирует синхронное соединение с указанным портом, которое немедленно возвращает ошибку, если порт не существует, и создает блок, пока сервер в противном случае не примет соединение. .

Это поведение можно изменить, указав комбинацию двух значений, как описано ниже:

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

IPC_CONNECT_WAIT_FOR_PORT - заставляет вызов connect() ждать, если указанный порт не существует сразу при выполнении, вместо немедленного сбоя.

IPC_CONNECT_ASYNC - если установлен, инициирует асинхронное соединение. Приложение должно опрашивать возвращенный дескриптор (вызывая wait() для события завершения соединения, указанного битом IPC_HANDLE_POLL_READY установленным в поле uevent_t структуры uevent_t перед началом нормальной работы.

API обмена сообщениями

Вызовы API обмена сообщениями позволяют отправлять и читать сообщения через ранее установленное соединение (канал). Вызовы API обмена сообщениями одинаковы для серверов и клиентов.

Клиент получает дескриптор канала посредством вызова connect() , а сервер получает дескриптор канала из вызова accept() , описанного выше.

Структура доверенного сообщения

Как показано ниже, сообщения, которыми обменивается Trusty API, имеют минимальную структуру, оставляя на усмотрение сервера и клиента согласование семантики фактического содержимого:

/*
 *  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;

Сообщение может состоять из одного или нескольких несмежных буферов, представленных массивом структур iovec_t . Trusty выполняет чтение и запись в эти блоки с iov массива iov . Содержимое буферов, которое можно описать массивом iov совершенно произвольно.

Методы в API обмена сообщениями

send_msg ()

Отправляет сообщение по указанному каналу.

long send_msg(uint32_t handle, ipc_msg_t *msg);

[in] handle : Дескриптор канала, по которому нужно отправить сообщение.

[in] msg : Указатель на ipc_msg_t structure описывающую сообщение

[retval]: общее количество байтов, отправленных в случае успеха; отрицательная ошибка в противном случае

Если клиент (или сервер) пытается отправить сообщение по каналу и в очереди сообщений однорангового узла назначения нет места, канал может перейти в состояние блокировки отправки (этого никогда не должно происходить для простого синхронного протокола запроса / ответа. но может произойти в более сложных случаях), на что указывает возврат ERR_NOT_ENOUGH_BUFFER ошибки ERR_NOT_ENOUGH_BUFFER . В таком случае абонент не должен ждать , пока равноправного FreeS некоторого пространства в его очереди приема путем извлечения обработки и ухода сообщения, с указанием IPC_HANDLE_POLL_SEND_UNBLOCKED бита в event поле uevent_t структуры , возвращаемое wait() вызов.

get_msg ()

Получает метаинформацию о следующем сообщении в очереди входящих сообщений

указанного канала.

long get_msg(uint32_t handle, ipc_msg_info_t *msg_info);

[in] handle : Дескриптор канала, по которому должно быть получено новое сообщение.

[out] msg_info : Информационная структура сообщения описывается следующим образом:

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

Каждому сообщению назначается уникальный идентификатор в наборе ожидающих сообщений, и указывается общая длина каждого сообщения. Если это настроено и разрешено протоколом, для конкретного канала может быть несколько ожидающих (открытых) сообщений одновременно.

[retval]: NO_ERROR в случае успеха; отрицательная ошибка в противном случае

read_msg ()

Читает содержимое сообщения с указанным идентификатором, начиная с указанного смещения.

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

[in] handle : Дескриптор канала, из которого следует читать сообщение.

[in] msg_id : ID сообщения для чтения

[in] offset : Смещение в сообщении, с которого следует начать чтение.

[in] msg : Указатель на структуру ipc_msg_t описывающую набор буферов для хранения данных входящего сообщения.

[retval]: общее количество байтов, сохраненных в буферах dst в случае успеха; отрицательная ошибка в противном случае

Метод read_msg можно вызывать несколько раз, начиная с другого (не обязательно последовательного) смещения по мере необходимости.

put_msg ()

Удаляет сообщение с указанным идентификатором.

long put_msg(uint32_t handle, uint32_t msg_id);

[in] handle : Дескриптор канала, по которому пришло сообщение.

[in] msg_id : идентификатор msg_id сообщения

[retval]: NO_ERROR в случае успеха; отрицательная ошибка в противном случае

К содержимому сообщения нельзя получить доступ после того, как сообщение было удалено и буфер, который он занимал, был освобожден.

File Descriptor API

API дескриптора файла включает вызовы read() , write() и ioctl() . Все эти вызовы могут работать с предопределенным (статическим) набором файловых дескрипторов, традиционно представленных небольшими числами. В текущей реализации пространство дескриптора файла отделено от пространства дескриптора IPC. API файлового дескриптора в Trusty похож на традиционный API на основе файловых дескрипторов.

По умолчанию существует 3 предопределенных (стандартных и общеизвестных) файловых дескриптора:

  • 0 - стандартный ввод. Реализация стандартного ввода fd умолчанию не работает (поскольку доверенные приложения не должны иметь интерактивную консоль), поэтому чтение, запись или вызов ioctl() на fd 0 должны возвращать ошибку ERR_NOT_SUPPORTED .
  • 1 - стандартный вывод. Данные, записанные на стандартный вывод, могут быть перенаправлены (в зависимости от уровня отладки LK) в UART и / или в журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. Некритические журналы отладки и сообщения должны идти в стандартном выводе. Методы read() и ioctl() не работают и должны возвращать ошибку ERR_NOT_SUPPORTED .
  • 2 - стандартная ошибка. Данные, записанные в стандартную ошибку, должны направляться в UART или журнал памяти, доступный на незащищенной стороне, в зависимости от платформы и конфигурации. В стандартную ошибку рекомендуется записывать только критические сообщения, так как этот поток, скорее всего, не будет регулироваться. Методы read() и ioctl() не работают и должны возвращать ошибку ERR_NOT_SUPPORTED .

Даже если этот набор дескрипторов файлов может быть расширен для реализации более fds (для реализации расширения платформы конкретного), расширяя потребности дескрипторов файлов должны осуществляться с осторожностью. Расширение файловых дескрипторов может привести к конфликтам и обычно не рекомендуется.

Методы в API дескриптора файла

читать()

Пытается count до count байтов данных из указанного файлового дескриптора.

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

[in] fd : дескриптор файла, из которого следует читать

[out] buf : указатель на буфер для хранения данных.

[in] count : максимальное количество байтов для чтения

[retval]: Возвращенное количество прочитанных байтов; отрицательная ошибка в противном случае

записывать()

Записывает count байтов данных в указанный дескриптор файла.

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

[in] fd : дескриптор файла для записи

[out] buf : указатель на данные для записи

[in] count : максимальное количество байтов для записи

[retval]: Возвращенное количество записанных байтов; отрицательная ошибка в противном случае

ioctl ()

Вызывает указанную команду ioctl для заданного дескриптора файла.

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

[in] fd : дескриптор файла, для которого нужно вызвать ioctl()

[in] cmd : команда ioctl

[in / out] args : указатель на аргументы ioctl()

Разное API

Методы в Miscellaneous API

gettime ()

Возвращает текущее системное время (в наносекундах).

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

[in] clock_id : clock_id платформы; передать ноль по умолчанию

[in] flags : Зарезервировано, должно быть равно нулю

[in] time : указатель на значение int64_t в котором сохраняется текущее время.

[retval]: NO_ERROR в случае успеха; отрицательная ошибка в противном случае

наносон ()

Приостанавливает выполнение вызывающего приложения на указанный период времени и возобновляет его по истечении этого периода.

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

[in] clock_id : Зарезервировано, должно быть равно нулю

[in] flags : Зарезервировано, должно быть равно нулю

[in] sleep_time : время сна в наносекундах.

[retval]: NO_ERROR в случае успеха; отрицательная ошибка в противном случае

Пример доверенного сервера приложений

В следующем примере приложения показано использование вышеуказанных API. В этом примере создается «эхо-служба», которая обрабатывает несколько входящих подключений и возвращает вызывающему абоненту все сообщения, которые он получает от клиентов, отправленных с защищенной или незащищенной стороны.

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

Метод run_end_to_end_msg_test() отправляет 10 000 сообщений асинхронно службе «эхо» и обрабатывает ответы.

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 и приложения

Набор надежных сервисов, опубликованных с защищенной стороны и помеченных атрибутом IPC_PORT_ALLOW_NS_CONNECT , доступен для ядер и программ пользовательского пространства, работающих на незащищенной стороне.

Среда выполнения на незащищенной стороне (ядро и пользовательское пространство) кардинально отличается от среды выполнения на защищенной стороне. Следовательно, вместо одной библиотеки для обеих сред существует два разных набора API. В ядре клиентский API предоставляется драйвером ядра trusty-ipc и регистрирует узел символьного устройства, который может использоваться процессами пользовательского пространства для связи со службами, работающими на защищенной стороне.

Пользовательское пространство Trusty IPC Client API

Библиотека API клиента Trusty IPC для пользовательского пространства представляет собой тонкий слой поверх узла устройства fd .

Программа пользовательского пространства запускает сеанс связи, вызывая tipc_connect() , инициализируя соединение с указанной службой Trusty. Внутренне tipc_connect() открывает указанный узел устройства для получения дескриптора файла и вызывает TIPC_IOC_CONNECT ioctl() с параметром argp указывающим на строку, содержащую имя службы, к которой необходимо подключиться.

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

Полученный дескриптор файла может использоваться только для связи со службой, для которой он был создан. Дескриптор файла должен быть закрыт вызовом tipc_close() когда соединение больше не требуется.

Дескриптор файла, полученный tipc_connect() ведет себя как типичный узел символьного устройства; дескриптор файла:

  • При необходимости можно переключить в неблокирующий режим
  • Может быть записан с использованием стандартного вызова write() для отправки сообщений на другую сторону
  • Может опрашиваться (с использованием вызовов poll() или select() ) на предмет доступности входящих сообщений в качестве обычного файлового дескриптора
  • Может быть прочитан для получения входящих сообщений

Вызывающий отправляет сообщение в службу Trusty, выполняя вызов записи для указанного fd . Все данные, переданные вышеупомянутому вызову write() преобразуются в сообщение драйвером trusty-ipc. Сообщение доставляется на защищенную сторону, где данные обрабатываются подсистемой IPC в ядре Trusty, направляются в надлежащее место назначения и доставляются в цикл IPC_HANDLE_POLL_MSG событий приложения как событие IPC_HANDLE_POLL_MSG на конкретном дескрипторе канала. В зависимости от конкретного протокола, зависящего от службы, служба Trusty может отправлять одно или несколько ответных сообщений, которые доставляются обратно на незащищенную сторону и помещаются в соответствующую очередь сообщений дескриптора файла канала для извлечения приложением пользовательского пространства для read() звоните.

tipc_connect ()

Открывает указанный tipc устройства tipc и инициирует соединение с указанной службой Trusty.

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

[in] dev_name : Путь к открываемому узлу Trusty IPC.

[in] srv_name : Имя опубликованной доверенной службы, к которой нужно подключиться

[retval]: Действительный дескриптор файла в случае успеха, -1 в противном случае.

tipc_close ()

Закрывает соединение с надежной службой, указанной в файловом дескрипторе.

int tipc_close(int fd);

[in] fd : дескриптор файла, ранее открытый tipc_connect()

Kernel Trusty IPC Client API

Ядро Trusty IPC Client API доступно для драйверов ядра. Пользовательский интерфейс Trusty IPC API реализован поверх этого API.

В общем, типичное использование этого API состоит из вызывающего, создающего объект struct tipc_chan с помощью функции tipc_create_channel() а затем с помощью tipc_chan_connect() для инициирования подключения к службе Trusty IPC, работающей на защищенной стороне. Соединение с удаленной стороной можно прервать, вызвав tipc_chan_shutdown() а затем tipc_chan_destroy() для очистки ресурсов.

После получения уведомления (через обратный вызов handle_event() ) об успешном установлении соединения вызывающий абонент выполняет следующие действия:

  • Получает буфер сообщений с помощью tipc_chan_get_txbuf_timeout()
  • Составляет сообщение и
  • tipc_chan_queue_msg() сообщение в очередь с помощью tipc_chan_queue_msg() для доставки в надежную службу (на защищенной стороне), к которой подключен канал

После успешной постановки в очередь вызывающий должен забыть о буфере сообщений, поскольку буфер сообщений в конечном итоге возвращается в пул свободных буферов после обработки удаленной стороной (для повторного использования позже для других сообщений). Пользователю нужно вызвать tipc_chan_put_txbuf() случае, если он не может поставить такой буфер в очередь или он больше не требуется.

Пользователь API получает сообщения с удаленной стороны, обрабатывая обратный вызов уведомления handle_msg() (который вызывается в контексте рабочей очереди trusty-ipc rx ), который предоставляет указатель на буфер rx содержащий входящее сообщение для обработки.

Ожидается, что реализация обратного вызова handle_msg() вернет указатель на действительную struct tipc_msg_buf . Он может быть таким же, как буфер входящего сообщения, если он обрабатывается локально и больше не требуется. В качестве альтернативы, это может быть новый буфер, полученный tipc_chan_get_rxbuf() если входящий буфер поставлен в очередь для дальнейшей обработки. Отсоединенный буфер rx должен отслеживаться и в конечном итоге освобождаться с помощью tipc_chan_put_rxbuf() когда он больше не нужен.

Методы в Kernel Trusty IPC Client API

tipc_create_channel ()

Создает и настраивает экземпляр канала Trusty IPC для конкретного устройства trusty-ipc.

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

[in] dev : Указатель на trusty-ipc, для которого создается канал устройства.

[in] ops : Указатель на struct tipc_chan_ops с заполненными обратными вызовами для конкретного вызывающего абонента

[in] cb_arg : указатель на данные, которые будут переданы в tipc_chan_ops вызовы tipc_chan_ops

[retval]: указатель на вновь созданный экземпляр struct tipc_chan в случае успеха, ERR_PTR(err) противном случае

В общем, вызывающий должен предоставить два обратных вызова, которые асинхронно вызываются, когда происходит соответствующее действие.

void (*handle_event)(void *cb_arg, int event) вызывается для уведомления вызывающей стороны об изменении состояния канала.

[in] cb_arg : указатель на данные, переданные в tipc_create_channel()

[in] event : событие, которое может принимать одно из следующих значений:

  • TIPC_CHANNEL_CONNECTED - указывает на успешное подключение к удаленной стороне
  • TIPC_CHANNEL_DISCONNECTED - указывает, что удаленная сторона отклонила запрос на новое соединение или запросила отключение для ранее подключенного канала
  • TIPC_CHANNEL_SHUTDOWN - указывает, что удаленная сторона завершает работу, окончательно завершая все соединения

struct tipc_msg_buf *(*handle_msg)(void *cb_arg, struct tipc_msg_buf *mb) вызывается для уведомления о том, что новое сообщение было получено по указанному каналу:

  • [in] cb_arg : Указатель на данные, переданные в tipc_create_channel()
  • [in] mb : Указатель на struct tipc_msg_buf описывающую входящее сообщение
  • [retval]: ожидается, что реализация обратного вызова вернет указатель на struct tipc_msg_buf которая может быть тем же указателем, что и параметр mb если сообщение обрабатывается локально и больше не требуется (или это может быть новый буфер, полученный tipc_chan_get_rxbuf() вызов)

tipc_chan_connect ()

Инициирует соединение с указанной службой Trusty IPC.

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

[in] chan : указатель на канал, возвращаемый tipc_create_chan()

[in] port : указатель на строку, содержащую имя службы, к которой нужно подключиться.

[retval]: 0 в случае успеха, отрицательная ошибка в противном случае

Вызывающий получает уведомление, когда соединение установлено, получая обратный вызов handle_event .

tipc_chan_shutdown ()

Завершает соединение со службой Trusty IPC, ранее инициированное tipc_chan_connect() .

int tipc_chan_shutdown(struct tipc_chan *chan);

[in] chan : указатель на канал, возвращаемый tipc_create_chan()

tipc_chan_destroy ()

Уничтожает указанный надежный канал IPC.

void tipc_chan_destroy(struct tipc_chan *chan);

[in] chan : указатель на канал, возвращаемый tipc_create_chan()

tipc_chan_get_txbuf_timeout ()

Получает буфер сообщений, который можно использовать для отправки данных по указанному каналу. Если буфер недоступен немедленно, вызывающий может быть заблокирован на указанный тайм-аут (в миллисекундах).

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

[in] chan : Указатель на канал, на который следует поставить сообщение в очередь.

[in] chan : Максимальный тайм-аут для ожидания, пока буфер tx станет доступен

[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err) в случае ошибки

tipc_chan_queue_msg ()

Ставит в очередь сообщение для отправки по указанным надежным каналам IPC.

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

[in] chan : Указатель на канал, на который следует поставить сообщение в очередь.

[in] mb: указатель на сообщение в очередь (полученное с помощью tipc_chan_get_txbuf_timeout() )

[retval]: 0 в случае успеха, отрицательная ошибка в противном случае

tipc_chan_put_txbuf ()

Освобождает указанный буфер сообщений Tx ранее полученный tipc_chan_get_txbuf_timeout() .

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

[in] chan : Указатель на канал, которому принадлежит этот буфер сообщения

[in] mb : указатель на буфер сообщений, который нужно освободить

[retval]: Нет

tipc_chan_get_rxbuf ()

Получает новый буфер сообщений, который можно использовать для приема сообщений по указанному каналу.

struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan);

[in] chan : Указатель на канал, которому принадлежит этот буфер сообщения.

[retval]: действительный буфер сообщений в случае успеха, ERR_PTR(err) в случае ошибки

tipc_chan_put_rxbuf ()

Освобождает указанный буфер сообщений, ранее полученный tipc_chan_get_rxbuf() .

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

[in] chan : Указатель на канал, которому принадлежит этот буфер сообщения.

[in] mb : указатель на буфер сообщений, который нужно освободить

[retval]: Нет