From 16143b82b3a5b71ffbf98639cf5c859a82fae198 Mon Sep 17 00:00:00 2001 From: George Poulios Date: Mon, 5 May 2025 17:49:56 +0300 Subject: [PATCH] Documentation/guides/optee: Add an OP-TEE guide 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 --- Documentation/guides/index.rst | 1 + Documentation/guides/optee.rst | 307 +++++++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 Documentation/guides/optee.rst diff --git a/Documentation/guides/index.rst b/Documentation/guides/index.rst index 262bccf0808..c928003d83a 100644 --- a/Documentation/guides/index.rst +++ b/Documentation/guides/index.rst @@ -58,4 +58,5 @@ Guides reading_can_msgs.rst remove_device_drivers_nsh.rst rust.rst + optee.rst diff --git a/Documentation/guides/optee.rst b/Documentation/guides/optee.rst new file mode 100644 index 00000000000..e133d9387a0 --- /dev/null +++ b/Documentation/guides/optee.rst @@ -0,0 +1,307 @@ +======================= +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 + #include + #include + #include + #include + #include + #include + #include + +#. 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 = ; + 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 = ; + 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...]