The guide: - explains the different OP-TEE transports available in NuttX - gives brief instructions on how to enable the OP-TEE driver - documents the IOCTLs supported, and - shows typical usage in a NuttX app Signed-off-by: George Poulios <gpoulios@census-labs.com>
307 lines
10 KiB
ReStructuredText
307 lines
10 KiB
ReStructuredText
=======================
|
|
Interfacing with OP-TEE
|
|
=======================
|
|
|
|
Overview
|
|
========
|
|
|
|
NuttX supports basic interfacing with OP-TEE OS through three different
|
|
transports: local network, RPMsg, and native Secure Monitor Calls (SMCs)
|
|
on arm. Tasks can interface with the OP-TEE driver (and in turn with the
|
|
OP-TEE OS) via IOCTLs on the TEE (``/dev/tee#``) character device. This
|
|
interface should allow use-of/integration-with libteec, although this is
|
|
not officially supported by NuttX, and is out of the scope of this guide.
|
|
|
|
The driver supports opening and closing sessions, allocating and registering
|
|
shared memory, and invoking functions on OP-TEE Trusted Applications (TAs).
|
|
It does not (yet) support reverse direction commands (TA -> Normal World)
|
|
to something like a TEE supplicant.
|
|
|
|
Enabling the OP-TEE Driver
|
|
==========================
|
|
|
|
The driver is enabled using one of:
|
|
|
|
- ``CONFIG_DEV_OPTEE_LOCAL``
|
|
- ``CONFIG_DEV_OPTEE_RPMSG``
|
|
- ``CONFIG_DEV_OPTEE_SMC``
|
|
|
|
All of the above require also ``CONFIG_ALLOW_BSD_COMPONENTS`` and
|
|
``CONFIG_LIBC_MEMFD_SHMFS``, which in turn requires ``CONFIG_FS_SHMFS``. So,
|
|
at a bare minimum, to enable the driver one would need something like the
|
|
following:
|
|
|
|
.. code-block::
|
|
|
|
CONFIG_ALLOW_BSD_COMPONENTS=y
|
|
CONFIG_DEV_OPTEE_SMC=y
|
|
CONFIG_FS_SHMFS=y
|
|
CONFIG_LIBC_MEMFD_SHMFS=y
|
|
|
|
Each implementation (local, RPMsg, or SMC) may have further dependencies
|
|
(e.g. RPMsg requires ``CONFIG_NET_RPMSG`` and more) and may have further
|
|
parameters to configure (e.g. RPMsg remote CPU name through
|
|
``CONFIG_OPTEE_REMOTE_CPU_NAME``).
|
|
|
|
.. warning::
|
|
``CONFIG_DEV_OPTEE_SMC`` has only been tested on arm64. Also, please note
|
|
that in configurations with ``CONFIG_ARM*_DCACHE_DISABLE=y`` you might
|
|
encounter issues with shared memory depending on the state of the data
|
|
cache in Secure World.
|
|
|
|
Successful registration of the driver can be verified by looking into
|
|
``/dev/tee0``. For instance, incompatibility with the TEE OS running in the
|
|
system, will prevent the ``/dev/tee0`` character device from being
|
|
registered.
|
|
|
|
IOCTLs supported
|
|
================
|
|
|
|
All IOCTLs return negative error codes on failure. All of them return 0
|
|
on success unless otherwise specified (see ``TEE_IOC_SHM_ALLOC``).
|
|
|
|
- ``TEE_IOC_VERSION`` : Query the version and capabilities of the TEE driver.
|
|
|
|
- Use the ``struct tee_ioctl_version_data`` to get the version and
|
|
capabilities. This driver supports OP-TEE so you should expect to
|
|
receive only ``TEE_IMPL_ID_OPTEE`` in ``.impl_id`` and ``TEE_OPTEE_CAP_TZ``
|
|
in ``.impl_caps``. The driver is GlobalPlatform compliant, and you should
|
|
always expect to receive ``TEE_GEN_CAP_GP | TEE_GEN_CAP_MEMREF_NULL`` in
|
|
``.gen_caps``. If using the SMC implementation, the driver supports also
|
|
shared memory registration, so you can expect also ``TEE_GEN_CAP_REG_MEM``
|
|
in ``.gen_caps``.
|
|
|
|
- ``TEE_IOC_OPEN_SESSION`` : Open a session with a Trusted Application.
|
|
|
|
- Expects a ``struct tee_ioctl_buf_data`` pointer, pointing to a
|
|
``struct tee_ioctl_open_session_arg`` instance with at minimum, the ``.uuid``
|
|
set. You can typically use ``uuid_enc_be()`` to encode a ``uuid_t`` struct
|
|
to the raw byte buffer expected in the ``.uuid`` field. After a successful
|
|
call, you can expect to get a session identifier back in the ``.session``
|
|
field.
|
|
|
|
- ``TEE_IOC_CLOSE_SESSION`` : Close a session with a Trusted Application.
|
|
|
|
- Expects a pointer to a ``struct tee_ioctl_close_session_arg`` with the
|
|
``.session`` field set to the identifier of the session to close.
|
|
|
|
- ``TEE_IOC_INVOKE`` : Invoke a function on a previously opened session to a Trusted Application.
|
|
|
|
- Expects a ``struct tee_ioctl_buf_data`` pointer, pointing to a
|
|
``struct tee_ioctl_invoke_arg`` instance. You can use the
|
|
``TEE_IOCTL_PARAM_SIZE()`` macro to calculate the size of the
|
|
variable-length array of ``struct tee_ioctl_param`` parameters in the
|
|
invoke arguments struct. At minimum, the interface expects the fields
|
|
``.func``, ``.session``, ``.num_params``, and ``.params`` to be set.
|
|
``.cancel_id`` can be optionally set to enable later canceling of this
|
|
command if needed.
|
|
You might notice that ``struct tee_ioctl_param`` has rather obscure field
|
|
names (``.a``, ``.b``, ``.c``). This can be improved with a union in the
|
|
future, but until then, please refer to ``include/nuttx/tee.h`` for details.
|
|
In short, for shared memory references, ``.a`` is the offset into the
|
|
shared memory buffer, ``.b`` is the size of the buffer, and ``.c`` is the
|
|
the shared memory identifier (``.addr`` field used in
|
|
``TEE_IOC_SHM_REGISTER``).
|
|
|
|
- ``TEE_IOC_CANCEL`` : Cancel a currently invoked command.
|
|
|
|
- Expects a ``struct tee_ioctl_cancel_arg`` pointer with the ``.session``
|
|
and ``.cancel_id`` fields set.
|
|
|
|
- ``TEE_IOC_SHM_ALLOC`` : Allocate shared memory between the user space and the secure OS.
|
|
|
|
- Expects a ``struct tee_ioctl_shm_alloc_data`` pointer with the ``.size``
|
|
field set, and ignoring the ``.flags`` field. Upon successful return,
|
|
it returns the memory file descriptor one can use ``mmap()`` on (with
|
|
``MAP_SHARED``). It also returns an identifier for the allocation
|
|
in ``.id``.
|
|
|
|
- ``TEE_IOC_SHM_REGISTER`` : Register a shared memory reference with the secure OS.
|
|
|
|
- Expects a pointer to a ``struct tee_ioctl_shm_register_data`` instance
|
|
with all fields set except ``.id``. ``.flags`` can be any combination of
|
|
``TEE_SHM_REGISTER`` and ``TEE_SHM_SEC_REGISTER`` but not ``TEE_SHM_ALLOC``.
|
|
``TEE_SHM_REGISTER`` registers the memory with the driver for automatic
|
|
cleanup (not freeing!) during ``/dev/tee#`` character device close.
|
|
``TEE_SHM_SEC_REGISTER`` registers the memory with the secure OS for later
|
|
use in memrefs and is automatically de-registered during driver close if
|
|
``TEE_SHM_REGISTER`` is also specified. ``.addr`` shall point to the (user)
|
|
memory to register and ``.size`` shall indicate its size. The identifier
|
|
used to register shared memory with the secure OS is the value of the
|
|
``.addr`` field (what one needs to specify in ``tee_ioctl_param.c``
|
|
during a ``TEE_IOC_INVOKE``).
|
|
|
|
Typical usage
|
|
=============
|
|
|
|
#. Include the necessary headers:
|
|
|
|
.. code-block:: c
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <nuttx/tee.h>
|
|
#include <uuid.h>
|
|
|
|
#. Open the TEE character device
|
|
|
|
.. code-block:: c
|
|
|
|
int fd = open("/dev/tee0", O_RDONLY | O_NONBLOCK);
|
|
|
|
#. Check the version and capabilities
|
|
|
|
.. code-block:: c
|
|
|
|
struct tee_ioctl_version_data ioc_ver;
|
|
|
|
int ret = ioctl(fd, TEE_IOC_VERSION, (unsigned long)&ioc_ver);
|
|
if (ret < 0)
|
|
{
|
|
printf("Failed to query TEE driver version and caps: %d, %s\n",
|
|
ret, strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
[...check ioc_ver accordingly...]
|
|
|
|
#. Open a session with a Trusted Application
|
|
|
|
.. code-block:: c
|
|
|
|
const uuid_t *uuid = [...];
|
|
struct tee_ioctl_open_session_arg ioc_opn = { 0 };
|
|
struct tee_ioctl_buf_data ioc_buf;
|
|
|
|
uuid_enc_be(&ioc_opn.uuid, uuid);
|
|
|
|
ioc_buf.buf_ptr = (uintptr_t)&ioc_opn;
|
|
ioc_buf.buf_len = sizeof(struct tee_ioctl_open_session_arg);
|
|
|
|
ret = ioctl(fd, TEE_IOC_OPEN_SESSION, (unsigned long)&ioc_buf);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
[...use ioc_opn.session returned...]
|
|
|
|
#. Invoke a function of the Trusted Application
|
|
|
|
.. code-block:: c
|
|
|
|
const size_t num_params = 1;
|
|
struct tee_ioctl_invoke_arg *ioc_args;
|
|
struct tee_ioctl_buf_data ioc_buf;
|
|
size_t ioc_args_len;
|
|
|
|
ioc_args_len = sizeof(struct tee_ioctl_invoke_arg) +
|
|
TEE_IOCTL_PARAM_SIZE(num_params);
|
|
|
|
ioc_args = (struct tee_ioctl_invoke_arg *)calloc(1, ioc_args_len);
|
|
if (!ioc_args)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ioc_args->func = <SOME_FUNCTION_ID>;
|
|
ioc_args->session = ioc_opn.session;
|
|
ioc_args->num_params = num_params;
|
|
ioc_args->params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
|
|
|
|
ioc_buf.buf_ptr = (uintptr_t)ioc_args;
|
|
ioc_buf.buf_len = ioc_args_len;
|
|
|
|
ret = ioctl(fd, TEE_IOC_INVOKE, (unsigned long)&ioc_buf);
|
|
if (ret < 0)
|
|
{
|
|
goto err_with_args;
|
|
}
|
|
|
|
[...use result (if any) in ioc_args->params...]
|
|
|
|
#. Allocate shared memory through the driver
|
|
|
|
.. code-block:: c
|
|
|
|
struct tee_ioctl_shm_alloc_data ioc_alloc = { 0 };
|
|
int memfd;
|
|
void *shm;
|
|
|
|
ioc_alloc.size = 1024;
|
|
|
|
memfd = ioctl(fd, TEE_IOC_SHM_ALLOC, (unsigned long)&ioc_alloc);
|
|
if (memfd < 0)
|
|
{
|
|
return memfd;
|
|
}
|
|
|
|
shm = mmap(NULL, ioc_alloc.size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
|
memfd, 0);
|
|
if (shm == MAP_FAILED)
|
|
{
|
|
close(memfd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#. Register shared memory with the driver and the secure OS
|
|
|
|
.. code-block:: c
|
|
|
|
/* The following will fail if TEE_GEN_CAP_REG_MEM is not reported in
|
|
* the returned `ioc_ver.gen_caps` in step 1 above */
|
|
|
|
struct tee_ioctl_shm_register_data ioc_reg = { 0 };
|
|
|
|
ioc_reg.addr = (uintptr_t)ptr;
|
|
ioc_reg.length = ioc_alloc.size;
|
|
ioc_reg.flags = TEE_SHM_REGISTER | TEE_SHM_SEC_REGISTER;
|
|
|
|
ret = ioctl(fd, TEE_IOC_SHM_REGISTER, (unsigned long)&ioc_reg);
|
|
if (ret < 0)
|
|
{
|
|
munmap(shm, ioc_alloc.size);
|
|
close(memfd);
|
|
return ret;
|
|
}
|
|
|
|
#. Use the registered shared memory in an invocation
|
|
|
|
.. code-block:: c
|
|
|
|
const size_t num_params = 1;
|
|
struct tee_ioctl_invoke_arg *ioc_args;
|
|
struct tee_ioctl_buf_data ioc_buf;
|
|
size_t ioc_args_len;
|
|
|
|
ioc_args_len = sizeof(struct tee_ioctl_invoke_arg) +
|
|
TEE_IOCTL_PARAM_SIZE(num_params);
|
|
|
|
ioc_args = (struct tee_ioctl_invoke_arg *)calloc(1, ioc_args_len);
|
|
if (!ioc_args)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ioc_args->func = <SOME_FUNCTION_ID>;
|
|
ioc_args->session = ioc_opn.session;
|
|
ioc_args->num_params = num_params;
|
|
ioc_args->params[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT;
|
|
ioc_args->params[0].a = 0;
|
|
ioc_args->params[0].b = ioc_alloc.size;
|
|
ioc_args->params[0].a = (uintptr_t)shm;
|
|
|
|
ioc_buf.buf_ptr = (uintptr_t)ioc_args;
|
|
ioc_buf.buf_len = ioc_args_len;
|
|
|
|
ret = ioctl(fd, TEE_IOC_INVOKE, (unsigned long)&ioc_buf);
|
|
if (ret < 0)
|
|
{
|
|
goto err_with_args;
|
|
}
|
|
|
|
[...use result (if any) in ioc_args->params...]
|