Trusty предоставляет API для разработки двух классов приложений/сервисов:
- Надежные приложения или службы, работающие на процессоре TEE
- Обычные/ненадежные приложения, которые работают на основном процессоре и используют службы, предоставляемые доверенными приложениями.
Trusty API обычно описывает систему межпроцессного взаимодействия (IPC) Trusty, включая связь с незащищенным миром. Программное обеспечение, работающее на основном процессоре, может использовать Trusty API для подключения к доверенным приложениям/службам и обмена с ними произвольными сообщениями точно так же, как сетевая служба по IP. Приложение должно определить формат данных и семантику этих сообщений, используя протокол уровня приложения. Надежная доставка сообщений гарантируется базовой инфраструктурой Trusty (в виде драйверов, работающих на главном процессоре), а связь полностью асинхронна.
Порты и каналы
Порты используются приложениями Trusty для предоставления конечных точек службы в виде именованного пути, к которому подключаются клиенты. Это дает простой строковый идентификатор службы для использования клиентами. Соглашение об именах — это обратное имя DNS, например com.google.servicename
.
Когда клиент подключается к порту, клиент получает канал для взаимодействия со службой. Служба должна принимать входящее соединение, и когда это происходит, она также получает канал. По сути, порты используются для поиска служб, а затем происходит обмен данными по паре подключенных каналов (т. е. экземпляры соединения на порту). Когда клиент подключается к порту, устанавливается симметричное двунаправленное соединение. Используя этот полнодуплексный путь, клиенты и серверы могут обмениваться произвольными сообщениями до тех пор, пока одна из сторон не решит разорвать соединение.
Только доверенные приложения на стороне безопасности или модули ядра Trusty могут создавать порты. Приложения, работающие на незащищенной стороне (в обычном мире), могут подключаться только к службам, опубликованным защищенной стороной.
В зависимости от требований доверенное приложение может быть одновременно и клиентом, и сервером. Доверенному приложению, которое публикует службу (в качестве сервера), может потребоваться подключение к другим службам (в качестве клиента).
Обработка API
Дескрипторы — это целые числа без знака, представляющие ресурсы, такие как порты и каналы, аналогичные файловым дескрипторам в UNIX. После создания дескрипторы помещаются в таблицу дескрипторов приложения, и на них можно ссылаться позже.
Вызывающий может связать частные данные с дескриптором, используя метод set_cookie()
.
Методы в Handle API
Дескрипторы допустимы только в контексте приложения. Приложение не должно передавать значение дескриптора другим приложениям, если это явно не указано. Значение дескриптора следует интерпретировать только путем сравнения его с параметром INVALID_IPC_HANDLE #define,
который приложение может использовать как указание на то, что дескриптор недействителен или не установлен.
set_cookie()
Связывает предоставленные вызывающей стороной частные данные с указанным дескриптором.
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
, если истек указанный тайм-аут, но не произошло никакого события; < 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_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()
, чтобы завершить установление соединения и создать канал (представленный другим дескриптором), который затем может быть опрошен для входящих сообщений. .
принимать()
Принимает входящее соединение и получает дескриптор канала.
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
: имя порта, опубликованное приложением Trusty.
[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
перед началом нормальной работы).
API обмена сообщениями
Вызовы Messaging API позволяют отправлять и читать сообщения по ранее установленному соединению (каналу). Вызовы Messaging 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
, совершенно произвольно.
Методы в 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
. В таком случае вызывающая сторона должна ждать, пока одноранговый узел освободит место в своей очереди приема, извлекая сообщения обработки и удаления, на что указывает бит 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
: идентификатор сообщения для чтения
[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
: идентификатор удаляемого сообщения.
[retval]: NO_ERROR
в случае успеха; отрицательная ошибка в противном случае
Доступ к содержимому сообщения невозможен после удаления сообщения и освобождения занимаемого им буфера.
API файлового дескриптора
API File Descriptor включает вызовы 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
байтов данных из указанного файлового дескриптора.
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
Методы в Разном API
получить время ()
Возвращает текущее системное время (в наносекундах).
long gettime(uint32_t clock_id, uint32_t flags, uint64_t *time);
[in] 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 и приложения
Набор служб Trusty, опубликованных с защищенной стороны и отмеченных атрибутом IPC_PORT_ALLOW_NS_CONNECT
, доступен для программ ядра и пользовательского пространства, работающих на незащищенной стороне.
Среда выполнения на незащищенной стороне (ядро и пользовательское пространство) кардинально отличается от среды выполнения на защищенной стороне. Поэтому вместо единой библиотеки для обеих сред существует два разных набора API. В ядре клиентский API предоставляется драйвером ядра trusty-ipc и регистрирует узел символьного устройства, который может использоваться процессами пользовательского пространства для связи со службами, работающими на защищенной стороне.
Пользовательское пространство Trusty IPC Client API
Библиотека Trusty IPC Client API пользовательского пространства представляет собой тонкий слой поверх узла 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
для определенного дескриптора канала. В зависимости от конкретного протокола, специфичного для службы, служба Trusty может отправить одно или несколько ответных сообщений, которые доставляются обратно на незащищенную сторону и помещаются в соответствующую очередь сообщений дескриптора файла канала для извлечения приложением пользовательского пространства read()
звонок.
tipc_connect()
Открывает указанный узел устройства tipc
и инициирует подключение к указанной службе Trusty.
int tipc_connect(const char *dev_name, const char *srv_name);
[in] dev_name
: Путь к узлу устройства Trusty IPC для открытия
[in] srv_name
: имя опубликованной службы Trusty, к которой необходимо подключиться.
[retval]: Действительный дескриптор файла в случае успеха, -1 в противном случае.
tipc_close()
Закрывает соединение со службой Trusty, указанной дескриптором файла.
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()
для доставки в службу Trusty (на защищенной стороне), к которой подключен канал
После успешного постановки в очередь вызывающая сторона должна забыть о буфере сообщений, потому что буфер сообщений в конечном итоге возвращается в пул свободных буферов после обработки удаленной стороной (для последующего повторного использования для других сообщений). Пользователю нужно вызвать tipc_chan_put_txbuf()
только в том случае, если он не может поставить такой буфер в очередь или он больше не требуется.
Пользователь API получает сообщения от удаленной стороны, обрабатывая обратный вызов уведомления handle_msg()
(который вызывается в контексте рабочей очереди rx
trusty-ipc), который предоставляет указатель на буфер 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
: указатель на доверенный IPC, для которого создан канал устройства.
[in] ops
: Указатель на struct tipc_chan_ops
с заполненными обратными вызовами для вызывающей стороны
[in] cb_arg
: Указатель на данные, которые будут переданы в обратные вызовы 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()
Помещает сообщение в очередь для отправки по указанным каналам Trusty 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
: Указатель на буфер сообщения, который необходимо освободить.
[ретвал]: Нет
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
: Указатель на буфер сообщения для освобождения
[ретвал]: Нет