OP-TEE Asynchronous notifications from secure world
Adds support in the SMC based OP-TEE driver to receive asynchronous notifications from secure world using an edge-triggered interrupt as delivery mechanism. -----BEGIN PGP SIGNATURE----- iQJOBAABCgA4FiEEFV+gSSXZJY9ZyuB5LinzTIcAHJcFAmG3HGsaHGplbnMud2lr bGFuZGVyQGxpbmFyby5vcmcACgkQLinzTIcAHJeeYBAAvF+s1AQEX26rbG4bwJ/3 UCkW4YJhZKTgdiSadCwarlHwTilSOMOZaRHx6I442qRi6lMN61+sRrFr6pRiQU2y EmSXAymgrRBzrl4K2zStAVfk0GueKsL7pjTMqNymcy4R+ljZLqREFAg79BtKEOEb 2d23IVMgTC6U34J+D4XstCQU3BvijeZ7GHfalK4sijm9TffQNQrUs4pVzvhNdqII ylWA0usDnkZ5poDey10bhQqHu619InwclntrlxdeTaS6qkNquUo6rg7RzHh+xOHP HQCoZiY+i5kYMQdYpkJvweP9A/XL/rFxpuWWmkAtsUprr+/zBDRWE2Q2liIbGW17 K3aiNSug5JHFG1BqBs+HfRG/VeqO/YQStos8ESOUeY76NWlECfuQNjKapSoLPzHy 1gqUNoKHX/IJzF9MMSp/U+Rcasf889+D9tMls5N8+qiMn/m3TS7F15f5e2HQY7U8 xiQfWgFQblrF+/sJW4Jz+2DCeGBiL052DjtL2YSV3j8EPGfBouGWNuvulow5Gd7c bRvNRjtBaptyGZysjHSroUtCxJCA0xM6yOZJOct8cW5XOPX33A+dhX78F6mjVejH H7J02/setyqnjfMla61fiuIZvCFkQO+U5f61Zq6JxFMunQhQ2PL9vMJsX9rdPsuX iB9k79w8F87CaQS6OXQ/Wco= =oohF -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmG4cZsACgkQmmx57+YA GNnzahAAn5+gkvx7pbIvVkNoCsT0ZAwTh6GGG6ndNCDXo/m3F/U/TRfCebwWdiEg xhKPfbA0Yb9ISi51r7DxuK79xRV0akpcZVvvBbgcXiMaKPjYsxWqZ2JumYeHjB4P AhGtCj7uTDdxHHg4t/ckMrQ9BTmhCm0KCvSBVfyaNYNao3DYpFJhfIZzPPthSIb8 GRs74aFr5xcSRkt+YZR7KF2pXcK7e8VZzZ3TlAGL1fS95h13+xWbgZvdmD6+0oYv BYebccY33UrONDqOzLUfLlmO0SAPvDe2GOxtk3pH9Z+vrnv6HjM2W6M34RBKxLLl nj0IxdRrEV/TmKtGmo436nSq6SWVJYUeH95oXjjFiyEikXLJ4vTzIYnwy1511+eb yYLGMcNSpck+MCh2DLpaBYB7t7ovEPR7hwBFb2hW357aSndFFuCbhRl4ualfk3gx nOBSwkV0b6KB1ZTErGlhjNXdq+wnQVZBwibRTkNp8ja+ZFhLsswIi1mtEAu8bnXh pXsZXv5Zi2sR27OQoM9LIY9GW4TPkt7ctaariyvafTZDsXvboMz9T6GZEy9hhp6/ kqjy2IeytfofbPYbnf1w2grXNqYAGT8S+kx7o3HQjK6EphuKSVWEGqU/ZYtfsldI wkP4r6PpwzMuygMIE8vy4u4vsnapER5v03wX6VWH3oO33QAQyms= =51x5 -----END PGP SIGNATURE----- Merge tag 'optee-async-notif-for-v5.17' of https://git.linaro.org/people/jens.wiklander/linux-tee into arm/drivers OP-TEE Asynchronous notifications from secure world Adds support in the SMC based OP-TEE driver to receive asynchronous notifications from secure world using an edge-triggered interrupt as delivery mechanism. * tag 'optee-async-notif-for-v5.17' of https://git.linaro.org/people/jens.wiklander/linux-tee: optee: Fix NULL but dereferenced coccicheck error optee: add asynchronous notifications optee: separate notification functions tee: export teedev_open() and teedev_close_context() tee: fix put order in teedev_close_context() dt-bindings: arm: optee: add interrupt property docs: staging/tee.rst: add a section on OP-TEE notifications Link: https://lore.kernel.org/r/20211213102359.GA1638682@jade Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
c6e5bdae04
|
|
@ -24,6 +24,12 @@ properties:
|
|||
compatible:
|
||||
const: linaro,optee-tz
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
description: |
|
||||
This interrupt which is used to signal an event by the secure world
|
||||
software is expected to be edge-triggered.
|
||||
|
||||
method:
|
||||
enum: [smc, hvc]
|
||||
description: |
|
||||
|
|
@ -42,10 +48,12 @@ additionalProperties: false
|
|||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
firmware {
|
||||
optee {
|
||||
compatible = "linaro,optee-tz";
|
||||
method = "smc";
|
||||
interrupts = <GIC_SPI 187 IRQ_TYPE_EDGE_RISING>;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -184,6 +184,36 @@ order to support device enumeration. In other words, OP-TEE driver invokes this
|
|||
application to retrieve a list of Trusted Applications which can be registered
|
||||
as devices on the TEE bus.
|
||||
|
||||
OP-TEE notifications
|
||||
--------------------
|
||||
|
||||
There are two kinds of notifications that secure world can use to make
|
||||
normal world aware of some event.
|
||||
|
||||
1. Synchronous notifications delivered with ``OPTEE_RPC_CMD_NOTIFICATION``
|
||||
using the ``OPTEE_RPC_NOTIFICATION_SEND`` parameter.
|
||||
2. Asynchronous notifications delivered with a combination of a non-secure
|
||||
edge-triggered interrupt and a fast call from the non-secure interrupt
|
||||
handler.
|
||||
|
||||
Synchronous notifications are limited by depending on RPC for delivery,
|
||||
this is only usable when secure world is entered with a yielding call via
|
||||
``OPTEE_SMC_CALL_WITH_ARG``. This excludes such notifications from secure
|
||||
world interrupt handlers.
|
||||
|
||||
An asynchronous notification is delivered via a non-secure edge-triggered
|
||||
interrupt to an interrupt handler registered in the OP-TEE driver. The
|
||||
actual notification value are retrieved with the fast call
|
||||
``OPTEE_SMC_GET_ASYNC_NOTIF_VALUE``. Note that one interrupt can represent
|
||||
multiple notifications.
|
||||
|
||||
One notification value ``OPTEE_SMC_ASYNC_NOTIF_VALUE_DO_BOTTOM_HALF`` has a
|
||||
special meaning. When this value is received it means that normal world is
|
||||
supposed to make a yielding call ``OPTEE_MSG_CMD_DO_BOTTOM_HALF``. This
|
||||
call is done from the thread assisting the interrupt handler. This is a
|
||||
building block for OP-TEE OS in secure world to implement the top half and
|
||||
bottom half style of device drivers.
|
||||
|
||||
AMD-TEE driver
|
||||
==============
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
obj-$(CONFIG_OPTEE) += optee.o
|
||||
optee-objs += core.o
|
||||
optee-objs += call.o
|
||||
optee-objs += notif.o
|
||||
optee-objs += rpc.o
|
||||
optee-objs += supp.o
|
||||
optee-objs += device.o
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ void optee_remove_common(struct optee *optee)
|
|||
/* Unregister OP-TEE specific client devices on TEE bus */
|
||||
optee_unregister_devices();
|
||||
|
||||
optee_notif_uninit(optee);
|
||||
/*
|
||||
* The two devices have to be unregistered before we can free the
|
||||
* other resources.
|
||||
|
|
@ -167,7 +168,6 @@ void optee_remove_common(struct optee *optee)
|
|||
tee_device_unregister(optee->teedev);
|
||||
|
||||
tee_shm_pool_free(optee->pool);
|
||||
optee_wait_queue_exit(&optee->wait_queue);
|
||||
optee_supp_uninit(&optee->supp);
|
||||
mutex_destroy(&optee->call_queue.mutex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -856,9 +856,13 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev)
|
|||
mutex_init(&optee->ffa.mutex);
|
||||
mutex_init(&optee->call_queue.mutex);
|
||||
INIT_LIST_HEAD(&optee->call_queue.waiters);
|
||||
optee_wait_queue_init(&optee->wait_queue);
|
||||
optee_supp_init(&optee->supp);
|
||||
ffa_dev_set_drvdata(ffa_dev, optee);
|
||||
rc = optee_notif_init(optee, OPTEE_DEFAULT_MAX_NOTIF_VALUE);
|
||||
if (rc) {
|
||||
optee_ffa_remove(ffa_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES);
|
||||
if (rc) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2015-2021, Linaro Limited
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/tee_drv.h>
|
||||
#include "optee_private.h"
|
||||
|
||||
struct notif_entry {
|
||||
struct list_head link;
|
||||
struct completion c;
|
||||
u_int key;
|
||||
};
|
||||
|
||||
static bool have_key(struct optee *optee, u_int key)
|
||||
{
|
||||
struct notif_entry *entry;
|
||||
|
||||
list_for_each_entry(entry, &optee->notif.db, link)
|
||||
if (entry->key == key)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int optee_notif_wait(struct optee *optee, u_int key)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct notif_entry *entry;
|
||||
int rc = 0;
|
||||
|
||||
if (key > optee->notif.max_key)
|
||||
return -EINVAL;
|
||||
|
||||
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
init_completion(&entry->c);
|
||||
entry->key = key;
|
||||
|
||||
spin_lock_irqsave(&optee->notif.lock, flags);
|
||||
|
||||
/*
|
||||
* If the bit is already set it means that the key has already
|
||||
* been posted and we must not wait.
|
||||
*/
|
||||
if (test_bit(key, optee->notif.bitmap)) {
|
||||
clear_bit(key, optee->notif.bitmap);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if someone is already waiting for this key. If there is
|
||||
* it's a programming error.
|
||||
*/
|
||||
if (have_key(optee, key)) {
|
||||
rc = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
list_add_tail(&entry->link, &optee->notif.db);
|
||||
|
||||
/*
|
||||
* Unlock temporarily and wait for completion.
|
||||
*/
|
||||
spin_unlock_irqrestore(&optee->notif.lock, flags);
|
||||
wait_for_completion(&entry->c);
|
||||
spin_lock_irqsave(&optee->notif.lock, flags);
|
||||
|
||||
list_del(&entry->link);
|
||||
out:
|
||||
spin_unlock_irqrestore(&optee->notif.lock, flags);
|
||||
|
||||
kfree(entry);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int optee_notif_send(struct optee *optee, u_int key)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct notif_entry *entry;
|
||||
|
||||
if (key > optee->notif.max_key)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&optee->notif.lock, flags);
|
||||
|
||||
list_for_each_entry(entry, &optee->notif.db, link)
|
||||
if (entry->key == key) {
|
||||
complete(&entry->c);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Only set the bit in case there where nobody waiting */
|
||||
set_bit(key, optee->notif.bitmap);
|
||||
out:
|
||||
spin_unlock_irqrestore(&optee->notif.lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int optee_notif_init(struct optee *optee, u_int max_key)
|
||||
{
|
||||
spin_lock_init(&optee->notif.lock);
|
||||
INIT_LIST_HEAD(&optee->notif.db);
|
||||
optee->notif.bitmap = bitmap_zalloc(max_key, GFP_KERNEL);
|
||||
if (!optee->notif.bitmap)
|
||||
return -ENOMEM;
|
||||
|
||||
optee->notif.max_key = max_key;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void optee_notif_uninit(struct optee *optee)
|
||||
{
|
||||
kfree(optee->notif.bitmap);
|
||||
}
|
||||
|
|
@ -318,6 +318,13 @@ struct optee_msg_arg {
|
|||
* [in] param[0].u.rmem.shm_ref holds shared memory reference
|
||||
* [in] param[0].u.rmem.offs 0
|
||||
* [in] param[0].u.rmem.size 0
|
||||
*
|
||||
* OPTEE_MSG_CMD_DO_BOTTOM_HALF does the scheduled bottom half processing
|
||||
* of a driver.
|
||||
*
|
||||
* OPTEE_MSG_CMD_STOP_ASYNC_NOTIF informs secure world that from now is
|
||||
* normal world unable to process asynchronous notifications. Typically
|
||||
* used when the driver is shut down.
|
||||
*/
|
||||
#define OPTEE_MSG_CMD_OPEN_SESSION 0
|
||||
#define OPTEE_MSG_CMD_INVOKE_COMMAND 1
|
||||
|
|
@ -325,6 +332,8 @@ struct optee_msg_arg {
|
|||
#define OPTEE_MSG_CMD_CANCEL 3
|
||||
#define OPTEE_MSG_CMD_REGISTER_SHM 4
|
||||
#define OPTEE_MSG_CMD_UNREGISTER_SHM 5
|
||||
#define OPTEE_MSG_CMD_DO_BOTTOM_HALF 6
|
||||
#define OPTEE_MSG_CMD_STOP_ASYNC_NOTIF 7
|
||||
#define OPTEE_MSG_FUNCID_CALL_WITH_ARG 0x0004
|
||||
|
||||
#endif /* _OPTEE_MSG_H */
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@
|
|||
|
||||
#define TEEC_ORIGIN_COMMS 0x00000002
|
||||
|
||||
/*
|
||||
* This value should be larger than the number threads in secure world to
|
||||
* meet the need from secure world. The number of threads in secure world
|
||||
* are usually not even close to 255 so we should be safe for now.
|
||||
*/
|
||||
#define OPTEE_DEFAULT_MAX_NOTIF_VALUE 255
|
||||
|
||||
typedef void (optee_invoke_fn)(unsigned long, unsigned long, unsigned long,
|
||||
unsigned long, unsigned long, unsigned long,
|
||||
unsigned long, unsigned long,
|
||||
|
|
@ -44,10 +51,13 @@ struct optee_call_queue {
|
|||
struct list_head waiters;
|
||||
};
|
||||
|
||||
struct optee_wait_queue {
|
||||
/* Serializes access to this struct */
|
||||
struct mutex mu;
|
||||
struct optee_notif {
|
||||
u_int max_key;
|
||||
struct tee_context *ctx;
|
||||
/* Serializes access to the elements below in this struct */
|
||||
spinlock_t lock;
|
||||
struct list_head db;
|
||||
u_long *bitmap;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -79,6 +89,7 @@ struct optee_smc {
|
|||
optee_invoke_fn *invoke_fn;
|
||||
void *memremaped_shm;
|
||||
u32 sec_caps;
|
||||
unsigned int notif_irq;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -129,8 +140,7 @@ struct optee_ops {
|
|||
* @smc: specific to SMC ABI
|
||||
* @ffa: specific to FF-A ABI
|
||||
* @call_queue: queue of threads waiting to call @invoke_fn
|
||||
* @wait_queue: queue of threads from secure world waiting for a
|
||||
* secure world sync object
|
||||
* @notif: notification synchronization struct
|
||||
* @supp: supplicant synchronization struct for RPC to supplicant
|
||||
* @pool: shared memory pool
|
||||
* @rpc_arg_count: If > 0 number of RPC parameters to make room for
|
||||
|
|
@ -147,7 +157,7 @@ struct optee {
|
|||
struct optee_ffa ffa;
|
||||
};
|
||||
struct optee_call_queue call_queue;
|
||||
struct optee_wait_queue wait_queue;
|
||||
struct optee_notif notif;
|
||||
struct optee_supp supp;
|
||||
struct tee_shm_pool *pool;
|
||||
unsigned int rpc_arg_count;
|
||||
|
|
@ -185,8 +195,10 @@ struct optee_call_ctx {
|
|||
size_t num_entries;
|
||||
};
|
||||
|
||||
void optee_wait_queue_init(struct optee_wait_queue *wq);
|
||||
void optee_wait_queue_exit(struct optee_wait_queue *wq);
|
||||
int optee_notif_init(struct optee *optee, u_int max_key);
|
||||
void optee_notif_uninit(struct optee *optee);
|
||||
int optee_notif_wait(struct optee *optee, u_int key);
|
||||
int optee_notif_send(struct optee *optee, u_int key);
|
||||
|
||||
u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
|
||||
struct tee_param *param);
|
||||
|
|
|
|||
|
|
@ -28,24 +28,27 @@
|
|||
#define OPTEE_RPC_CMD_GET_TIME 3
|
||||
|
||||
/*
|
||||
* Wait queue primitive, helper for secure world to implement a wait queue.
|
||||
* Notification from/to secure world.
|
||||
*
|
||||
* If secure world needs to wait for a secure world mutex it issues a sleep
|
||||
* request instead of spinning in secure world. Conversely is a wakeup
|
||||
* request issued when a secure world mutex with a thread waiting thread is
|
||||
* unlocked.
|
||||
* If secure world needs to wait for something, for instance a mutex, it
|
||||
* does a notification wait request instead of spinning in secure world.
|
||||
* Conversely can a synchronous notification can be sent when a secure
|
||||
* world mutex with a thread waiting thread is unlocked.
|
||||
*
|
||||
* Waiting on a key
|
||||
* [in] value[0].a OPTEE_RPC_WAIT_QUEUE_SLEEP
|
||||
* [in] value[0].b Wait key
|
||||
* This interface can also be used to wait for a asynchronous notification
|
||||
* which instead is sent via a non-secure interrupt.
|
||||
*
|
||||
* Waking up a key
|
||||
* [in] value[0].a OPTEE_RPC_WAIT_QUEUE_WAKEUP
|
||||
* [in] value[0].b Wakeup key
|
||||
* Waiting on notification
|
||||
* [in] value[0].a OPTEE_RPC_NOTIFICATION_WAIT
|
||||
* [in] value[0].b notification value
|
||||
*
|
||||
* Sending a synchronous notification
|
||||
* [in] value[0].a OPTEE_RPC_NOTIFICATION_SEND
|
||||
* [in] value[0].b notification value
|
||||
*/
|
||||
#define OPTEE_RPC_CMD_WAIT_QUEUE 4
|
||||
#define OPTEE_RPC_WAIT_QUEUE_SLEEP 0
|
||||
#define OPTEE_RPC_WAIT_QUEUE_WAKEUP 1
|
||||
#define OPTEE_RPC_CMD_NOTIFICATION 4
|
||||
#define OPTEE_RPC_NOTIFICATION_WAIT 0
|
||||
#define OPTEE_RPC_NOTIFICATION_SEND 1
|
||||
|
||||
/*
|
||||
* Suspend execution
|
||||
|
|
|
|||
|
|
@ -107,6 +107,12 @@ struct optee_smc_call_get_os_revision_result {
|
|||
/*
|
||||
* Call with struct optee_msg_arg as argument
|
||||
*
|
||||
* When calling this function normal world has a few responsibilities:
|
||||
* 1. It must be able to handle eventual RPCs
|
||||
* 2. Non-secure interrupts should not be masked
|
||||
* 3. If asynchronous notifications has been negotiated successfully, then
|
||||
* asynchronous notifications should be unmasked during this call.
|
||||
*
|
||||
* Call register usage:
|
||||
* a0 SMC Function ID, OPTEE_SMC*CALL_WITH_ARG
|
||||
* a1 Upper 32 bits of a 64-bit physical pointer to a struct optee_msg_arg
|
||||
|
|
@ -195,7 +201,8 @@ struct optee_smc_get_shm_config_result {
|
|||
* Normal return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_OK
|
||||
* a1 bitfield of secure world capabilities OPTEE_SMC_SEC_CAP_*
|
||||
* a2-7 Preserved
|
||||
* a2 The maximum secure world notification number
|
||||
* a3-7 Preserved
|
||||
*
|
||||
* Error return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_ENOTAVAIL, can't use the capabilities from normal world
|
||||
|
|
@ -218,6 +225,8 @@ struct optee_smc_get_shm_config_result {
|
|||
#define OPTEE_SMC_SEC_CAP_VIRTUALIZATION BIT(3)
|
||||
/* Secure world supports Shared Memory with a NULL reference */
|
||||
#define OPTEE_SMC_SEC_CAP_MEMREF_NULL BIT(4)
|
||||
/* Secure world supports asynchronous notification of normal world */
|
||||
#define OPTEE_SMC_SEC_CAP_ASYNC_NOTIF BIT(5)
|
||||
|
||||
#define OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES 9
|
||||
#define OPTEE_SMC_EXCHANGE_CAPABILITIES \
|
||||
|
|
@ -226,8 +235,8 @@ struct optee_smc_get_shm_config_result {
|
|||
struct optee_smc_exchange_capabilities_result {
|
||||
unsigned long status;
|
||||
unsigned long capabilities;
|
||||
unsigned long max_notif_value;
|
||||
unsigned long reserved0;
|
||||
unsigned long reserved1;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -319,6 +328,68 @@ struct optee_smc_disable_shm_cache_result {
|
|||
#define OPTEE_SMC_GET_THREAD_COUNT \
|
||||
OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_THREAD_COUNT)
|
||||
|
||||
/*
|
||||
* Inform OP-TEE that normal world is able to receive asynchronous
|
||||
* notifications.
|
||||
*
|
||||
* Call requests usage:
|
||||
* a0 SMC Function ID, OPTEE_SMC_ENABLE_ASYNC_NOTIF
|
||||
* a1-6 Not used
|
||||
* a7 Hypervisor Client ID register
|
||||
*
|
||||
* Normal return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_OK
|
||||
* a1-7 Preserved
|
||||
*
|
||||
* Not supported return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_ENOTAVAIL
|
||||
* a1-7 Preserved
|
||||
*/
|
||||
#define OPTEE_SMC_FUNCID_ENABLE_ASYNC_NOTIF 16
|
||||
#define OPTEE_SMC_ENABLE_ASYNC_NOTIF \
|
||||
OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_ENABLE_ASYNC_NOTIF)
|
||||
|
||||
/*
|
||||
* Retrieve a value of notifications pending since the last call of this
|
||||
* function.
|
||||
*
|
||||
* OP-TEE keeps a record of all posted values. When an interrupt is
|
||||
* received which indicates that there are posted values this function
|
||||
* should be called until all pended values have been retrieved. When a
|
||||
* value is retrieved, it's cleared from the record in secure world.
|
||||
*
|
||||
* Call requests usage:
|
||||
* a0 SMC Function ID, OPTEE_SMC_GET_ASYNC_NOTIF_VALUE
|
||||
* a1-6 Not used
|
||||
* a7 Hypervisor Client ID register
|
||||
*
|
||||
* Normal return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_OK
|
||||
* a1 value
|
||||
* a2 Bit[0]: OPTEE_SMC_ASYNC_NOTIF_VALUE_VALID if the value in a1 is
|
||||
* valid, else 0 if no values where pending
|
||||
* a2 Bit[1]: OPTEE_SMC_ASYNC_NOTIF_VALUE_PENDING if another value is
|
||||
* pending, else 0.
|
||||
* Bit[31:2]: MBZ
|
||||
* a3-7 Preserved
|
||||
*
|
||||
* Not supported return register usage:
|
||||
* a0 OPTEE_SMC_RETURN_ENOTAVAIL
|
||||
* a1-7 Preserved
|
||||
*/
|
||||
#define OPTEE_SMC_ASYNC_NOTIF_VALUE_VALID BIT(0)
|
||||
#define OPTEE_SMC_ASYNC_NOTIF_VALUE_PENDING BIT(1)
|
||||
|
||||
/*
|
||||
* Notification that OP-TEE expects a yielding call to do some bottom half
|
||||
* work in a driver.
|
||||
*/
|
||||
#define OPTEE_SMC_ASYNC_NOTIF_VALUE_DO_BOTTOM_HALF 0
|
||||
|
||||
#define OPTEE_SMC_FUNCID_GET_ASYNC_NOTIF_VALUE 17
|
||||
#define OPTEE_SMC_GET_ASYNC_NOTIF_VALUE \
|
||||
OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_ASYNC_NOTIF_VALUE)
|
||||
|
||||
/*
|
||||
* Resume from RPC (for example after processing a foreign interrupt)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -12,23 +12,6 @@
|
|||
#include "optee_private.h"
|
||||
#include "optee_rpc_cmd.h"
|
||||
|
||||
struct wq_entry {
|
||||
struct list_head link;
|
||||
struct completion c;
|
||||
u32 key;
|
||||
};
|
||||
|
||||
void optee_wait_queue_init(struct optee_wait_queue *priv)
|
||||
{
|
||||
mutex_init(&priv->mu);
|
||||
INIT_LIST_HEAD(&priv->db);
|
||||
}
|
||||
|
||||
void optee_wait_queue_exit(struct optee_wait_queue *priv)
|
||||
{
|
||||
mutex_destroy(&priv->mu);
|
||||
}
|
||||
|
||||
static void handle_rpc_func_cmd_get_time(struct optee_msg_arg *arg)
|
||||
{
|
||||
struct timespec64 ts;
|
||||
|
|
@ -144,48 +127,6 @@ static void handle_rpc_func_cmd_i2c_transfer(struct tee_context *ctx,
|
|||
}
|
||||
#endif
|
||||
|
||||
static struct wq_entry *wq_entry_get(struct optee_wait_queue *wq, u32 key)
|
||||
{
|
||||
struct wq_entry *w;
|
||||
|
||||
mutex_lock(&wq->mu);
|
||||
|
||||
list_for_each_entry(w, &wq->db, link)
|
||||
if (w->key == key)
|
||||
goto out;
|
||||
|
||||
w = kmalloc(sizeof(*w), GFP_KERNEL);
|
||||
if (w) {
|
||||
init_completion(&w->c);
|
||||
w->key = key;
|
||||
list_add_tail(&w->link, &wq->db);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&wq->mu);
|
||||
return w;
|
||||
}
|
||||
|
||||
static void wq_sleep(struct optee_wait_queue *wq, u32 key)
|
||||
{
|
||||
struct wq_entry *w = wq_entry_get(wq, key);
|
||||
|
||||
if (w) {
|
||||
wait_for_completion(&w->c);
|
||||
mutex_lock(&wq->mu);
|
||||
list_del(&w->link);
|
||||
mutex_unlock(&wq->mu);
|
||||
kfree(w);
|
||||
}
|
||||
}
|
||||
|
||||
static void wq_wakeup(struct optee_wait_queue *wq, u32 key)
|
||||
{
|
||||
struct wq_entry *w = wq_entry_get(wq, key);
|
||||
|
||||
if (w)
|
||||
complete(&w->c);
|
||||
}
|
||||
|
||||
static void handle_rpc_func_cmd_wq(struct optee *optee,
|
||||
struct optee_msg_arg *arg)
|
||||
{
|
||||
|
|
@ -197,11 +138,13 @@ static void handle_rpc_func_cmd_wq(struct optee *optee,
|
|||
goto bad;
|
||||
|
||||
switch (arg->params[0].u.value.a) {
|
||||
case OPTEE_RPC_WAIT_QUEUE_SLEEP:
|
||||
wq_sleep(&optee->wait_queue, arg->params[0].u.value.b);
|
||||
case OPTEE_RPC_NOTIFICATION_WAIT:
|
||||
if (optee_notif_wait(optee, arg->params[0].u.value.b))
|
||||
goto bad;
|
||||
break;
|
||||
case OPTEE_RPC_WAIT_QUEUE_WAKEUP:
|
||||
wq_wakeup(&optee->wait_queue, arg->params[0].u.value.b);
|
||||
case OPTEE_RPC_NOTIFICATION_SEND:
|
||||
if (optee_notif_send(optee, arg->params[0].u.value.b))
|
||||
goto bad;
|
||||
break;
|
||||
default:
|
||||
goto bad;
|
||||
|
|
@ -319,7 +262,7 @@ void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee,
|
|||
case OPTEE_RPC_CMD_GET_TIME:
|
||||
handle_rpc_func_cmd_get_time(arg);
|
||||
break;
|
||||
case OPTEE_RPC_CMD_WAIT_QUEUE:
|
||||
case OPTEE_RPC_CMD_NOTIFICATION:
|
||||
handle_rpc_func_cmd_wq(optee, arg);
|
||||
break;
|
||||
case OPTEE_RPC_CMD_SUSPEND:
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@
|
|||
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/tee_drv.h>
|
||||
|
|
@ -34,7 +37,8 @@
|
|||
* 2. Low level support functions to register shared memory in secure world
|
||||
* 3. Dynamic shared memory pool based on alloc_pages()
|
||||
* 4. Do a normal scheduled call into secure world
|
||||
* 5. Driver initialization.
|
||||
* 5. Asynchronous notification
|
||||
* 6. Driver initialization.
|
||||
*/
|
||||
|
||||
#define OPTEE_SHM_NUM_PRIV_PAGES CONFIG_OPTEE_SHM_NUM_PRIV_PAGES
|
||||
|
|
@ -875,10 +879,137 @@ static int optee_smc_do_call_with_arg(struct tee_context *ctx,
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int simple_call_with_arg(struct tee_context *ctx, u32 cmd)
|
||||
{
|
||||
struct optee_msg_arg *msg_arg;
|
||||
struct tee_shm *shm;
|
||||
|
||||
shm = optee_get_msg_arg(ctx, 0, &msg_arg);
|
||||
if (IS_ERR(shm))
|
||||
return PTR_ERR(shm);
|
||||
|
||||
msg_arg->cmd = cmd;
|
||||
optee_smc_do_call_with_arg(ctx, shm);
|
||||
|
||||
tee_shm_free(shm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int optee_smc_do_bottom_half(struct tee_context *ctx)
|
||||
{
|
||||
return simple_call_with_arg(ctx, OPTEE_MSG_CMD_DO_BOTTOM_HALF);
|
||||
}
|
||||
|
||||
static int optee_smc_stop_async_notif(struct tee_context *ctx)
|
||||
{
|
||||
return simple_call_with_arg(ctx, OPTEE_MSG_CMD_STOP_ASYNC_NOTIF);
|
||||
}
|
||||
|
||||
/*
|
||||
* 5. Driver initialization
|
||||
* 5. Asynchronous notification
|
||||
*/
|
||||
|
||||
static u32 get_async_notif_value(optee_invoke_fn *invoke_fn, bool *value_valid,
|
||||
bool *value_pending)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
|
||||
invoke_fn(OPTEE_SMC_GET_ASYNC_NOTIF_VALUE, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
|
||||
if (res.a0)
|
||||
return 0;
|
||||
*value_valid = (res.a2 & OPTEE_SMC_ASYNC_NOTIF_VALUE_VALID);
|
||||
*value_pending = (res.a2 & OPTEE_SMC_ASYNC_NOTIF_VALUE_PENDING);
|
||||
return res.a1;
|
||||
}
|
||||
|
||||
static irqreturn_t notif_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct optee *optee = dev_id;
|
||||
bool do_bottom_half = false;
|
||||
bool value_valid;
|
||||
bool value_pending;
|
||||
u32 value;
|
||||
|
||||
do {
|
||||
value = get_async_notif_value(optee->smc.invoke_fn,
|
||||
&value_valid, &value_pending);
|
||||
if (!value_valid)
|
||||
break;
|
||||
|
||||
if (value == OPTEE_SMC_ASYNC_NOTIF_VALUE_DO_BOTTOM_HALF)
|
||||
do_bottom_half = true;
|
||||
else
|
||||
optee_notif_send(optee, value);
|
||||
} while (value_pending);
|
||||
|
||||
if (do_bottom_half)
|
||||
return IRQ_WAKE_THREAD;
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t notif_irq_thread_fn(int irq, void *dev_id)
|
||||
{
|
||||
struct optee *optee = dev_id;
|
||||
|
||||
optee_smc_do_bottom_half(optee->notif.ctx);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int optee_smc_notif_init_irq(struct optee *optee, u_int irq)
|
||||
{
|
||||
struct tee_context *ctx;
|
||||
int rc;
|
||||
|
||||
ctx = teedev_open(optee->teedev);
|
||||
if (IS_ERR(ctx))
|
||||
return PTR_ERR(ctx);
|
||||
|
||||
optee->notif.ctx = ctx;
|
||||
rc = request_threaded_irq(irq, notif_irq_handler,
|
||||
notif_irq_thread_fn,
|
||||
0, "optee_notification", optee);
|
||||
if (rc)
|
||||
goto err_close_ctx;
|
||||
|
||||
optee->smc.notif_irq = irq;
|
||||
|
||||
return 0;
|
||||
|
||||
err_close_ctx:
|
||||
teedev_close_context(optee->notif.ctx);
|
||||
optee->notif.ctx = NULL;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void optee_smc_notif_uninit_irq(struct optee *optee)
|
||||
{
|
||||
if (optee->notif.ctx) {
|
||||
optee_smc_stop_async_notif(optee->notif.ctx);
|
||||
if (optee->smc.notif_irq) {
|
||||
free_irq(optee->smc.notif_irq, optee);
|
||||
irq_dispose_mapping(optee->smc.notif_irq);
|
||||
}
|
||||
|
||||
/*
|
||||
* The thread normally working with optee->notif.ctx was
|
||||
* stopped with free_irq() above.
|
||||
*
|
||||
* Note we're not using teedev_close_context() or
|
||||
* tee_client_close_context() since we have already called
|
||||
* tee_device_put() while initializing to avoid a circular
|
||||
* reference counting.
|
||||
*/
|
||||
teedev_close_context(optee->notif.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 6. Driver initialization
|
||||
*
|
||||
* During driver inititialization is secure world probed to find out which
|
||||
* During driver initialization is secure world probed to find out which
|
||||
* features it supports so the driver can be initialized with a matching
|
||||
* configuration. This involves for instance support for dynamic shared
|
||||
* memory instead of a static memory carvout.
|
||||
|
|
@ -950,6 +1081,17 @@ static const struct optee_ops optee_ops = {
|
|||
.from_msg_param = optee_from_msg_param,
|
||||
};
|
||||
|
||||
static int enable_async_notif(optee_invoke_fn *invoke_fn)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
|
||||
invoke_fn(OPTEE_SMC_ENABLE_ASYNC_NOTIF, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
|
||||
if (res.a0)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn)
|
||||
{
|
||||
struct arm_smccc_res res;
|
||||
|
|
@ -999,7 +1141,7 @@ static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn)
|
|||
}
|
||||
|
||||
static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn,
|
||||
u32 *sec_caps)
|
||||
u32 *sec_caps, u32 *max_notif_value)
|
||||
{
|
||||
union {
|
||||
struct arm_smccc_res smccc;
|
||||
|
|
@ -1022,6 +1164,11 @@ static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn,
|
|||
return false;
|
||||
|
||||
*sec_caps = res.result.capabilities;
|
||||
if (*sec_caps & OPTEE_SMC_SEC_CAP_ASYNC_NOTIF)
|
||||
*max_notif_value = res.result.max_notif_value;
|
||||
else
|
||||
*max_notif_value = OPTEE_DEFAULT_MAX_NOTIF_VALUE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1186,6 +1333,8 @@ static int optee_smc_remove(struct platform_device *pdev)
|
|||
*/
|
||||
optee_disable_shm_cache(optee);
|
||||
|
||||
optee_smc_notif_uninit_irq(optee);
|
||||
|
||||
optee_remove_common(optee);
|
||||
|
||||
if (optee->smc.memremaped_shm)
|
||||
|
|
@ -1215,6 +1364,7 @@ static int optee_probe(struct platform_device *pdev)
|
|||
struct optee *optee = NULL;
|
||||
void *memremaped_shm = NULL;
|
||||
struct tee_device *teedev;
|
||||
u32 max_notif_value;
|
||||
u32 sec_caps;
|
||||
int rc;
|
||||
|
||||
|
|
@ -1234,7 +1384,8 @@ static int optee_probe(struct platform_device *pdev)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) {
|
||||
if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps,
|
||||
&max_notif_value)) {
|
||||
pr_warn("capabilities mismatch\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -1257,7 +1408,7 @@ static int optee_probe(struct platform_device *pdev)
|
|||
optee = kzalloc(sizeof(*optee), GFP_KERNEL);
|
||||
if (!optee) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
goto err_free_pool;
|
||||
}
|
||||
|
||||
optee->ops = &optee_ops;
|
||||
|
|
@ -1267,32 +1418,55 @@ static int optee_probe(struct platform_device *pdev)
|
|||
teedev = tee_device_alloc(&optee_clnt_desc, NULL, pool, optee);
|
||||
if (IS_ERR(teedev)) {
|
||||
rc = PTR_ERR(teedev);
|
||||
goto err;
|
||||
goto err_free_optee;
|
||||
}
|
||||
optee->teedev = teedev;
|
||||
|
||||
teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
|
||||
if (IS_ERR(teedev)) {
|
||||
rc = PTR_ERR(teedev);
|
||||
goto err;
|
||||
goto err_unreg_teedev;
|
||||
}
|
||||
optee->supp_teedev = teedev;
|
||||
|
||||
rc = tee_device_register(optee->teedev);
|
||||
if (rc)
|
||||
goto err;
|
||||
goto err_unreg_supp_teedev;
|
||||
|
||||
rc = tee_device_register(optee->supp_teedev);
|
||||
if (rc)
|
||||
goto err;
|
||||
goto err_unreg_supp_teedev;
|
||||
|
||||
mutex_init(&optee->call_queue.mutex);
|
||||
INIT_LIST_HEAD(&optee->call_queue.waiters);
|
||||
optee_wait_queue_init(&optee->wait_queue);
|
||||
optee_supp_init(&optee->supp);
|
||||
optee->smc.memremaped_shm = memremaped_shm;
|
||||
optee->pool = pool;
|
||||
|
||||
platform_set_drvdata(pdev, optee);
|
||||
rc = optee_notif_init(optee, max_notif_value);
|
||||
if (rc)
|
||||
goto err_supp_uninit;
|
||||
|
||||
if (sec_caps & OPTEE_SMC_SEC_CAP_ASYNC_NOTIF) {
|
||||
unsigned int irq;
|
||||
|
||||
rc = platform_get_irq(pdev, 0);
|
||||
if (rc < 0) {
|
||||
pr_err("platform_get_irq: ret %d\n", rc);
|
||||
goto err_notif_uninit;
|
||||
}
|
||||
irq = rc;
|
||||
|
||||
rc = optee_smc_notif_init_irq(optee, irq);
|
||||
if (rc) {
|
||||
irq_dispose_mapping(irq);
|
||||
goto err_notif_uninit;
|
||||
}
|
||||
enable_async_notif(optee->smc.invoke_fn);
|
||||
pr_info("Asynchronous notifications enabled\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that there are no pre-existing shm objects before enabling
|
||||
* the shm cache so that there's no chance of receiving an invalid
|
||||
|
|
@ -1307,29 +1481,30 @@ static int optee_probe(struct platform_device *pdev)
|
|||
if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM)
|
||||
pr_info("dynamic shared memory is enabled\n");
|
||||
|
||||
platform_set_drvdata(pdev, optee);
|
||||
|
||||
rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES);
|
||||
if (rc) {
|
||||
optee_smc_remove(pdev);
|
||||
return rc;
|
||||
}
|
||||
if (rc)
|
||||
goto err_disable_shm_cache;
|
||||
|
||||
pr_info("initialized driver\n");
|
||||
return 0;
|
||||
err:
|
||||
if (optee) {
|
||||
/*
|
||||
* tee_device_unregister() is safe to call even if the
|
||||
* devices hasn't been registered with
|
||||
* tee_device_register() yet.
|
||||
*/
|
||||
tee_device_unregister(optee->supp_teedev);
|
||||
tee_device_unregister(optee->teedev);
|
||||
kfree(optee);
|
||||
}
|
||||
if (pool)
|
||||
tee_shm_pool_free(pool);
|
||||
|
||||
err_disable_shm_cache:
|
||||
optee_disable_shm_cache(optee);
|
||||
optee_smc_notif_uninit_irq(optee);
|
||||
optee_unregister_devices();
|
||||
err_notif_uninit:
|
||||
optee_notif_uninit(optee);
|
||||
err_supp_uninit:
|
||||
optee_supp_uninit(&optee->supp);
|
||||
mutex_destroy(&optee->call_queue.mutex);
|
||||
err_unreg_supp_teedev:
|
||||
tee_device_unregister(optee->supp_teedev);
|
||||
err_unreg_teedev:
|
||||
tee_device_unregister(optee->teedev);
|
||||
err_free_optee:
|
||||
kfree(optee);
|
||||
err_free_pool:
|
||||
tee_shm_pool_free(pool);
|
||||
if (memremaped_shm)
|
||||
memunmap(memremaped_shm);
|
||||
return rc;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ static DEFINE_SPINLOCK(driver_lock);
|
|||
static struct class *tee_class;
|
||||
static dev_t tee_devt;
|
||||
|
||||
static struct tee_context *teedev_open(struct tee_device *teedev)
|
||||
struct tee_context *teedev_open(struct tee_device *teedev)
|
||||
{
|
||||
int rc;
|
||||
struct tee_context *ctx;
|
||||
|
|
@ -70,6 +70,7 @@ err:
|
|||
return ERR_PTR(rc);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(teedev_open);
|
||||
|
||||
void teedev_ctx_get(struct tee_context *ctx)
|
||||
{
|
||||
|
|
@ -96,11 +97,14 @@ void teedev_ctx_put(struct tee_context *ctx)
|
|||
kref_put(&ctx->refcount, teedev_ctx_release);
|
||||
}
|
||||
|
||||
static void teedev_close_context(struct tee_context *ctx)
|
||||
void teedev_close_context(struct tee_context *ctx)
|
||||
{
|
||||
tee_device_put(ctx->teedev);
|
||||
struct tee_device *teedev = ctx->teedev;
|
||||
|
||||
teedev_ctx_put(ctx);
|
||||
tee_device_put(teedev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(teedev_close_context);
|
||||
|
||||
static int tee_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -587,4 +587,18 @@ struct tee_client_driver {
|
|||
#define to_tee_client_driver(d) \
|
||||
container_of(d, struct tee_client_driver, driver)
|
||||
|
||||
/**
|
||||
* teedev_open() - Open a struct tee_device
|
||||
* @teedev: Device to open
|
||||
*
|
||||
* @return a pointer to struct tee_context on success or an ERR_PTR on failure.
|
||||
*/
|
||||
struct tee_context *teedev_open(struct tee_device *teedev);
|
||||
|
||||
/**
|
||||
* teedev_close_context() - closes a struct tee_context
|
||||
* @ctx: The struct tee_context to close
|
||||
*/
|
||||
void teedev_close_context(struct tee_context *ctx);
|
||||
|
||||
#endif /*__TEE_DRV_H*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue