driver: tee: optee: implement OCALLs#72
Open
HernanGatta wants to merge 37 commits into
Open
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR modifies the TEE and OP-TEE drivers to support OCALLs.
The main change introduced by this PR is the addition of the
TEE_IOC_ECALLIOCTL. This IOCTL mirrors and expands the functionality provided byTEE_IOC_INVOKEto support duplex communication. Where the latter provides a one-way command-and-reply transport from CA to TA, the former allows for two-way command-and-reply from CA to TA, and from TA to CA.Additionally, a shared memory flag,
TEE_IOCTL_SHM_OCALL, is added. When this flag is specified, shared memory is registered with the kernel, but not with OP-TEE. While the TEE Supplicant already can perform this operation by talking to a different device node with a different call table, introducing this flag enables all CAs to do it as well.Lastly, the driver can now check for an additional OP-TEE capability,
TEE_OPTEE_CAP_OCALL, which indicates that OP-TEE was built with and supports OCALLs.Related PRs: OP-TEE, OP-TEE Client, OP-TEE Examples.
ECALL IOCTL
The fundamental working principle behind in-thread OCALL handling is the ability for the IOCTL that carries the initial function invocation request to return before the function invocation is complete. That is, given a CA that invokes a function on a TA, if the TA requests an OCALL, the IOCTL through which the original function invocation request was passed must return prematurely and indicate to the CA that it is not the original function invocation that completed. Rather, the latter is still pending, but has been suspended to relay the OCALL request to the CA. Once the CA handles the OCALL request, it invokes the IOCTL again, whose contents contain the result of the OCALL.
Since OCALL requests may require passing memory references, and given that TA memory must not be exposed to the CA, a shared memory allocation may be required ahead of the actual OCALL. When this occurs, the original IOCTL returns once to relay the shared memory allocation request to the CA, whereupon the CA calls the IOCTL again with the result of the allocation. Once the OCALL proper is complete, another IOCTL return and invocation cycle is required to free the previously allocated memory.
One may regard this duplex communication as a communication protocol. Each return of the IOCTL must indicate whether the original function invocation request is complete, and when not, it must indicate to the CA what it must do on behalf of the TA, and carry sufficient information to restart the original function invocation, whose secure thread is awaiting RPC resumption.
OCALL Contexts
During an call to
TEE_IOC_INVOKE, several resources are allocated. These include memory shared with OP-TEE to carry the command arguments, any memory allocated to handle RPCs, as well as the call waiter. Seeing as an OCALL request is passed via an RPC, it is also necessary to keep RPC resumption information, such as the secure thread Id that sent the RPC.All this information is kept in
struct optee_ocall_ctx, which is allocated when the TA requests an OCALL. The resulting pointer to this structure is given an Id using the kernel's IDR mechanism, and is kept alive until the original function invocation is complete.Values
To carry the information required by the new functionality, two additional values are necessary in the IOCTL parameters, in contrast with
TEE_IOC_INVOKE. One value must carry a function ID that indicates the current state of the ECALL-OCALL cycle, and one other value must carry an identifier for the current OCALL context.These two values are named
funcandocall_idinstruct tee_ioctl_ecall_arg. The elementcmd_idis used to indicate the CA to TA command Id as well as the TA to CA OCALL Id in the opposite direction.Typical Flows
There are two flows an OCALL can take: one that does not require passing memory references, and one which does.
Flow 1: No Shared Memory
Flow 2: Shared Memory Requested
Abnormal Termination
Seeing as OCALL requests are transmitted to the normal world via RPCs, the requesting secure world thread remains suspended, and is not freed for reuse until the OCALL is complete one way or another. Should the CA terminate while handling an OCALL, or a shared memory allocation/free request, it is imperative that that OCALL be completed, albeit with an error, so that the secure world thread be resumed and allowed to run to completion. Failure to do this results in the eventual exhaustion of secure world threads, not mention that their corresponding TAs remain resident in memory.
If an OCALL does not require any shared memory, a CA that terminates during OCALL handling will see its TEE context released once its reference count reaches zero, which happens as that process' resources are released by Linux.
On the other hand, if an OCALL requires shared memory, a reference to it will be acquired by the OP-TEE driver, which in turn increases the reference count on the TEE context. If the CA terminates prematurely, the reference count on the TEE context will never reach zero.
To break this cycle, a new function callback is added in to the TEE driver function table,
pre_release. This function is called when Linux releases the TEE context, yet memory references are still outstanding. The callback implementation in the OP-TEE driver cancels all pending OCALLs, returning an error code to the secure world, and reducing the reference count on outstanding shared memory allocations.Signed-off-by: Hernan Gatta hegatta@microsoft.com