linux/drivers/tee/tee_core.c
Arnd Bergmann ff0bebab77 Add Qualcomm TEE driver (QTEE)
This introduces a Trusted Execution Environment (TEE) driver for
 Qualcomm TEE (QTEE).
 
 QTEE enables Trusted Applications (TAs) and services to run securely. It
 uses an object-based interface, where each service is an object with
 sets of operations.
 
 Kernel and userspace services are also available to QTEE through a
 similar approach. QTEE makes callback requests that are converted into
 object invocations. These objects can represent services within the
 kernel or userspace process.
 
 We extend the TEE subsystem to understand object parameters and an ioctl
 call so client can invoke objects in QTEE:
   - TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_*
   - TEE_IOC_OBJECT_INVOKE
 
 The existing ioctl calls TEE_IOC_SUPPL_RECV and TEE_IOC_SUPPL_SEND are
 used for invoking services in the userspace process by QTEE.
 
 The TEE backend driver uses the QTEE Transport Message to communicate
 with QTEE. Interactions through the object INVOKE interface are
 translated into QTEE messages. Likewise, object invocations from QTEE
 for userspace objects are converted into SEND/RECV ioctl calls to
 supplicants.
 -----BEGIN PGP SIGNATURE-----
 
 iQJOBAABCgA4FiEE0qerISgy2SKkqO79Wr/6JGat8H4FAmjIS8AaHGplbnMud2lr
 bGFuZGVyQGxpbmFyby5vcmcACgkQWr/6JGat8H4Wzg/+KnIx5XCYpj1QmL2H8vz5
 dkqB5+QEHfaUIKxUrbk7X/Gow7ZTO8IuDPaiWPSIaGhOosio7fr9J6SdGWySSvBw
 qXazPLgRP7tvhhUA8H1zGO6J9GSGIGENtzRyeK9QzglmkBQcoK9fLRH7StGiwFdP
 f3NKPIx3YZKKL5+I4Xe8J0jvLZmiJW59cSj7m1sfDbPobuLLEKff4VFd4NSv8ufc
 JKpxlxwa3xCtpjNsDJFNlpRwtO0YvF10V3xlDtRGZQs7Gq/dbOA48koA0EqZtTc9
 Yhigl+F4gjleQcrpVT2QM7qJt8fdmuR77FI67YQCmr1cqY1pT/gT3l3Fri0Ok3XU
 Yl+EBI32QLFTjJeGvoEehaEhhpJsWJaLDNDgOV9gDJfZoJK3UgYVjUFwWlVF8Xju
 6iplkDBGpexogjDXoBo8vZEP+/EGwr+cGhWvokLymZCe8R8tfmbA4KkU1mhxo3ma
 eHleGKKghC78xQzUc7gwt0pVJm4FY+uoCzbdV/S/i5j5Kn6l4un5lkUOzXH6D7lj
 HBLExWqL8nxc7mC0Zxtvcd58FiVbLgjEmgMQEGOTliuO8/BXxKU1OJeyNGd4D9vz
 8F2vwBSgyCcrpkx1MmDC2NxYPzUOQ58ct6z07UxlpUCcBgCXlnrRBMjX1k8CwJpn
 BkiizW+aj53HRTQlWlBZLp0=
 =+Jqo
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmjTDEkACgkQmmx57+YA
 GNk5ww//TTwhmt8/XMNbozvUHYvz2/BsJJ9jVlFZUp9nuf7nVkDESGiNAvj439Fy
 xSwnFKjplRxDwLWixsi92QSF60VfhTm3pQ7ggsga/5IoHr+R2l0L2aMzkZUl4jKX
 y6pCg9A/FE1D87AfOh4dzuagddkwzOf1CEcJlE95t7NH9uome3QdsIzmc7/yg/MV
 01xq+30YaSRNbGxiQkmIqChU8bJBFqaH+ygPWZVyAX0gzk9nQHhzNgvbsi8v0Otv
 iFNO3/VF7uzsv2Q8Qx0unIBq6kJIxhHC3K3M1TXHJKRtax8N/8M6UVVkdfshes5+
 reg0CIsOEQ9FqevyabEkirtiwvCF61knmhkKJjCnysd+18PCzLjxnNEVtY+tUomH
 sFI++U5MLuybfCAx4jqjW9dEUrLNiGF8sbJTkQ4ToBjRJR1YihT9aBHeoH7OCKfb
 izS03PlJqDAK7qGH7PTjabi/YmYujizxVrh29CsP3Lk0FfB5m2h2dsX1gr9Z9V5d
 hq0z8nAsh6UJt26Nfq2+hhMaC4AiBn4foc+YaCx/Z8pf9pejzEu/NxRNox05LZem
 EThVNRF1zTtI+0SCHGDAwV3Tuj/uuvrOl9FkcdPbP0kFErsW8b5zwZWIDvzqc2FT
 L39E/C91Ptoe9ZpmCAfnyzKcfI6FeEfXwtMsLcagHsm6pcVbfyc=
 =BBL5
 -----END PGP SIGNATURE-----

Merge tag 'tee-qcomtee-for-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee into soc/drivers

Add Qualcomm TEE driver (QTEE)

This introduces a Trusted Execution Environment (TEE) driver for
Qualcomm TEE (QTEE).

QTEE enables Trusted Applications (TAs) and services to run securely. It
uses an object-based interface, where each service is an object with
sets of operations.

Kernel and userspace services are also available to QTEE through a
similar approach. QTEE makes callback requests that are converted into
object invocations. These objects can represent services within the
kernel or userspace process.

We extend the TEE subsystem to understand object parameters and an ioctl
call so client can invoke objects in QTEE:
  - TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_*
  - TEE_IOC_OBJECT_INVOKE

The existing ioctl calls TEE_IOC_SUPPL_RECV and TEE_IOC_SUPPL_SEND are
used for invoking services in the userspace process by QTEE.

The TEE backend driver uses the QTEE Transport Message to communicate
with QTEE. Interactions through the object INVOKE interface are
translated into QTEE messages. Likewise, object invocations from QTEE
for userspace objects are converted into SEND/RECV ioctl calls to
supplicants.

* tag 'tee-qcomtee-for-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee:
  Documentation: tee: Add Qualcomm TEE driver
  tee: qcom: enable TEE_IOC_SHM_ALLOC ioctl
  tee: qcom: add primordial object
  tee: add Qualcomm TEE driver
  tee: increase TEE_MAX_ARG_SIZE to 4096
  tee: add TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF
  tee: add TEE_IOCTL_PARAM_ATTR_TYPE_UBUF
  tee: add close_context to TEE driver operation
  tee: allow a driver to allocate a tee_device without a pool

Link: https://lore.kernel.org/r/20250915174957.GA2040478@rayden
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
2025-09-23 23:08:25 +02:00

1455 lines
35 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2016, Linaro Limited
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/cdev.h>
#include <linux/cred.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/tee_core.h>
#include <linux/uaccess.h>
#include <crypto/sha1.h>
#include "tee_private.h"
#define TEE_NUM_DEVICES 32
#define TEE_IOCTL_PARAM_SIZE(x) (size_mul(sizeof(struct tee_param), (x)))
#define TEE_UUID_NS_NAME_SIZE 128
/*
* TEE Client UUID name space identifier (UUIDv4)
*
* Value here is random UUID that is allocated as name space identifier for
* forming Client UUID's for TEE environment using UUIDv5 scheme.
*/
static const uuid_t tee_client_uuid_ns = UUID_INIT(0x58ac9ca0, 0x2086, 0x4683,
0xa1, 0xb8, 0xec, 0x4b,
0xc0, 0x8e, 0x01, 0xb6);
/*
* Unprivileged devices in the lower half range and privileged devices in
* the upper half range.
*/
static DECLARE_BITMAP(dev_mask, TEE_NUM_DEVICES);
static DEFINE_SPINLOCK(driver_lock);
static const struct class tee_class;
static dev_t tee_devt;
struct tee_context *teedev_open(struct tee_device *teedev)
{
int rc;
struct tee_context *ctx;
if (!tee_device_get(teedev))
return ERR_PTR(-EINVAL);
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
rc = -ENOMEM;
goto err;
}
kref_init(&ctx->refcount);
ctx->teedev = teedev;
rc = teedev->desc->ops->open(ctx);
if (rc)
goto err;
return ctx;
err:
kfree(ctx);
tee_device_put(teedev);
return ERR_PTR(rc);
}
EXPORT_SYMBOL_GPL(teedev_open);
void teedev_ctx_get(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_get(&ctx->refcount);
}
EXPORT_SYMBOL_GPL(teedev_ctx_get);
static void teedev_ctx_release(struct kref *ref)
{
struct tee_context *ctx = container_of(ref, struct tee_context,
refcount);
ctx->releasing = true;
ctx->teedev->desc->ops->release(ctx);
kfree(ctx);
}
void teedev_ctx_put(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_put(&ctx->refcount, teedev_ctx_release);
}
EXPORT_SYMBOL_GPL(teedev_ctx_put);
void teedev_close_context(struct tee_context *ctx)
{
struct tee_device *teedev = ctx->teedev;
if (teedev->desc->ops->close_context)
teedev->desc->ops->close_context(ctx);
teedev_ctx_put(ctx);
tee_device_put(teedev);
}
EXPORT_SYMBOL_GPL(teedev_close_context);
static int tee_open(struct inode *inode, struct file *filp)
{
struct tee_context *ctx;
ctx = teedev_open(container_of(inode->i_cdev, struct tee_device, cdev));
if (IS_ERR(ctx))
return PTR_ERR(ctx);
/*
* Default user-space behaviour is to wait for tee-supplicant
* if not present for any requests in this context.
*/
ctx->supp_nowait = false;
filp->private_data = ctx;
return 0;
}
static int tee_release(struct inode *inode, struct file *filp)
{
teedev_close_context(filp->private_data);
return 0;
}
/**
* uuid_v5() - Calculate UUIDv5
* @uuid: Resulting UUID
* @ns: Name space ID for UUIDv5 function
* @name: Name for UUIDv5 function
* @size: Size of name
*
* UUIDv5 is specific in RFC 4122.
*
* This implements section (for SHA-1):
* 4.3. Algorithm for Creating a Name-Based UUID
*/
static void uuid_v5(uuid_t *uuid, const uuid_t *ns, const void *name,
size_t size)
{
unsigned char hash[SHA1_DIGEST_SIZE];
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, (const u8 *)ns, sizeof(*ns));
sha1_update(&ctx, (const u8 *)name, size);
sha1_final(&ctx, hash);
memcpy(uuid->b, hash, UUID_SIZE);
/* Tag for version 5 */
uuid->b[6] = (hash[6] & 0x0F) | 0x50;
uuid->b[8] = (hash[8] & 0x3F) | 0x80;
}
int tee_session_calc_client_uuid(uuid_t *uuid, u32 connection_method,
const u8 connection_data[TEE_IOCTL_UUID_LEN])
{
gid_t ns_grp = (gid_t)-1;
kgid_t grp = INVALID_GID;
char *name = NULL;
int name_len;
int rc = 0;
if (connection_method == TEE_IOCTL_LOGIN_PUBLIC ||
connection_method == TEE_IOCTL_LOGIN_REE_KERNEL) {
/* Nil UUID to be passed to TEE environment */
uuid_copy(uuid, &uuid_null);
return 0;
}
/*
* In Linux environment client UUID is based on UUIDv5.
*
* Determine client UUID with following semantics for 'name':
*
* For TEEC_LOGIN_USER:
* uid=<uid>
*
* For TEEC_LOGIN_GROUP:
* gid=<gid>
*
*/
name = kzalloc(TEE_UUID_NS_NAME_SIZE, GFP_KERNEL);
if (!name)
return -ENOMEM;
switch (connection_method) {
case TEE_IOCTL_LOGIN_USER:
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "uid=%x",
current_euid().val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
case TEE_IOCTL_LOGIN_GROUP:
memcpy(&ns_grp, connection_data, sizeof(gid_t));
grp = make_kgid(current_user_ns(), ns_grp);
if (!gid_valid(grp) || !in_egroup_p(grp)) {
rc = -EPERM;
goto out_free_name;
}
name_len = snprintf(name, TEE_UUID_NS_NAME_SIZE, "gid=%x",
grp.val);
if (name_len >= TEE_UUID_NS_NAME_SIZE) {
rc = -E2BIG;
goto out_free_name;
}
break;
default:
rc = -EINVAL;
goto out_free_name;
}
uuid_v5(uuid, &tee_client_uuid_ns, name, name_len);
out_free_name:
kfree(name);
return rc;
}
EXPORT_SYMBOL_GPL(tee_session_calc_client_uuid);
static int tee_ioctl_version(struct tee_context *ctx,
struct tee_ioctl_version_data __user *uvers)
{
struct tee_ioctl_version_data vers;
ctx->teedev->desc->ops->get_version(ctx->teedev, &vers);
if (ctx->teedev->desc->flags & TEE_DESC_PRIVILEGED)
vers.gen_caps |= TEE_GEN_CAP_PRIVILEGED;
if (copy_to_user(uvers, &vers, sizeof(vers)))
return -EFAULT;
return 0;
}
static int tee_ioctl_shm_alloc(struct tee_context *ctx,
struct tee_ioctl_shm_alloc_data __user *udata)
{
long ret;
struct tee_ioctl_shm_alloc_data data;
struct tee_shm *shm;
if (copy_from_user(&data, udata, sizeof(data)))
return -EFAULT;
/* Currently no input flags are supported */
if (data.flags)
return -EINVAL;
shm = tee_shm_alloc_user_buf(ctx, data.size);
if (IS_ERR(shm))
return PTR_ERR(shm);
data.id = shm->id;
data.size = shm->size;
if (copy_to_user(udata, &data, sizeof(data)))
ret = -EFAULT;
else
ret = tee_shm_get_fd(shm);
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd() failed then it will
* be freed immediately.
*/
tee_shm_put(shm);
return ret;
}
static int
tee_ioctl_shm_register(struct tee_context *ctx,
struct tee_ioctl_shm_register_data __user *udata)
{
long ret;
struct tee_ioctl_shm_register_data data;
struct tee_shm *shm;
if (copy_from_user(&data, udata, sizeof(data)))
return -EFAULT;
/* Currently no input flags are supported */
if (data.flags)
return -EINVAL;
shm = tee_shm_register_user_buf(ctx, data.addr, data.length);
if (IS_ERR(shm))
return PTR_ERR(shm);
data.id = shm->id;
data.length = shm->size;
if (copy_to_user(udata, &data, sizeof(data)))
ret = -EFAULT;
else
ret = tee_shm_get_fd(shm);
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd() failed then it will
* be freed immediately.
*/
tee_shm_put(shm);
return ret;
}
static int
tee_ioctl_shm_register_fd(struct tee_context *ctx,
struct tee_ioctl_shm_register_fd_data __user *udata)
{
struct tee_ioctl_shm_register_fd_data data;
struct tee_shm *shm;
long ret;
if (copy_from_user(&data, udata, sizeof(data)))
return -EFAULT;
/* Currently no input flags are supported */
if (data.flags)
return -EINVAL;
shm = tee_shm_register_fd(ctx, data.fd);
if (IS_ERR(shm))
return -EINVAL;
data.id = shm->id;
data.flags = shm->flags;
data.size = shm->size;
if (copy_to_user(udata, &data, sizeof(data)))
ret = -EFAULT;
else
ret = tee_shm_get_fd(shm);
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd() failed then it will
* be freed immediately.
*/
tee_shm_put(shm);
return ret;
}
static int param_from_user_memref(struct tee_context *ctx,
struct tee_param_memref *memref,
struct tee_ioctl_param *ip)
{
struct tee_shm *shm;
size_t offs = 0;
/*
* If a NULL pointer is passed to a TA in the TEE,
* the ip.c IOCTL parameters is set to TEE_MEMREF_NULL
* indicating a NULL memory reference.
*/
if (ip->c != TEE_MEMREF_NULL) {
/*
* If we fail to get a pointer to a shared
* memory object (and increase the ref count)
* from an identifier we return an error. All
* pointers that has been added in params have
* an increased ref count. It's the callers
* responibility to do tee_shm_put() on all
* resolved pointers.
*/
shm = tee_shm_get_from_id(ctx, ip->c);
if (IS_ERR(shm))
return PTR_ERR(shm);
/*
* Ensure offset + size does not overflow
* offset and does not overflow the size of
* the referred shared memory object.
*/
if ((ip->a + ip->b) < ip->a ||
(ip->a + ip->b) > shm->size) {
tee_shm_put(shm);
return -EINVAL;
}
if (shm->flags & TEE_SHM_DMA_BUF) {
struct tee_shm_dmabuf_ref *ref;
ref = container_of(shm, struct tee_shm_dmabuf_ref, shm);
if (ref->parent_shm) {
/*
* The shm already has one reference to
* ref->parent_shm so we are clear of 0.
* We're getting another reference since
* this shm will be used in the parameter
* list instead of the shm we got with
* tee_shm_get_from_id() above.
*/
refcount_inc(&ref->parent_shm->refcount);
tee_shm_put(shm);
shm = ref->parent_shm;
offs = ref->offset;
}
}
} else if (ctx->cap_memref_null) {
/* Pass NULL pointer to OP-TEE */
shm = NULL;
} else {
return -EINVAL;
}
memref->shm_offs = ip->a + offs;
memref->size = ip->b;
memref->shm = shm;
return 0;
}
static int params_from_user(struct tee_context *ctx, struct tee_param *params,
size_t num_params,
struct tee_ioctl_param __user *uparams)
{
size_t n;
for (n = 0; n < num_params; n++) {
struct tee_ioctl_param ip;
int rc;
if (copy_from_user(&ip, uparams + n, sizeof(ip)))
return -EFAULT;
/* All unused attribute bits has to be zero */
if (ip.attr & ~TEE_IOCTL_PARAM_ATTR_MASK)
return -EINVAL;
params[n].attr = ip.attr;
switch (ip.attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) {
case TEE_IOCTL_PARAM_ATTR_TYPE_NONE:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
params[n].u.value.a = ip.a;
params[n].u.value.b = ip.b;
params[n].u.value.c = ip.c;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INOUT:
params[n].u.ubuf.uaddr = u64_to_user_ptr(ip.a);
params[n].u.ubuf.size = ip.b;
if (!access_ok(params[n].u.ubuf.uaddr,
params[n].u.ubuf.size))
return -EFAULT;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT:
params[n].u.objref.id = ip.a;
params[n].u.objref.flags = ip.b;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
rc = param_from_user_memref(ctx, &params[n].u.memref,
&ip);
if (rc)
return rc;
break;
default:
/* Unknown attribute */
return -EINVAL;
}
}
return 0;
}
static int params_to_user(struct tee_ioctl_param __user *uparams,
size_t num_params, struct tee_param *params)
{
size_t n;
for (n = 0; n < num_params; n++) {
struct tee_ioctl_param __user *up = uparams + n;
struct tee_param *p = params + n;
switch (p->attr) {
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
if (put_user(p->u.value.a, &up->a) ||
put_user(p->u.value.b, &up->b) ||
put_user(p->u.value.c, &up->c))
return -EFAULT;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INOUT:
if (put_user((u64)p->u.ubuf.size, &up->b))
return -EFAULT;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT:
if (put_user(p->u.objref.id, &up->a) ||
put_user(p->u.objref.flags, &up->b))
return -EFAULT;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
if (put_user((u64)p->u.memref.size, &up->b))
return -EFAULT;
break;
default:
break;
}
}
return 0;
}
static int tee_ioctl_open_session(struct tee_context *ctx,
struct tee_ioctl_buf_data __user *ubuf)
{
int rc;
size_t n;
struct tee_ioctl_buf_data buf;
struct tee_ioctl_open_session_arg __user *uarg;
struct tee_ioctl_open_session_arg arg;
struct tee_ioctl_param __user *uparams = NULL;
struct tee_param *params = NULL;
bool have_session = false;
if (!ctx->teedev->desc->ops->open_session)
return -EINVAL;
if (copy_from_user(&buf, ubuf, sizeof(buf)))
return -EFAULT;
if (buf.buf_len > TEE_MAX_ARG_SIZE ||
buf.buf_len < sizeof(struct tee_ioctl_open_session_arg))
return -EINVAL;
uarg = u64_to_user_ptr(buf.buf_ptr);
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
if (size_add(sizeof(arg), TEE_IOCTL_PARAM_SIZE(arg.num_params)) != buf.buf_len)
return -EINVAL;
if (arg.num_params) {
params = kcalloc(arg.num_params, sizeof(struct tee_param),
GFP_KERNEL);
if (!params)
return -ENOMEM;
uparams = uarg->params;
rc = params_from_user(ctx, params, arg.num_params, uparams);
if (rc)
goto out;
}
if (arg.clnt_login >= TEE_IOCTL_LOGIN_REE_KERNEL_MIN &&
arg.clnt_login <= TEE_IOCTL_LOGIN_REE_KERNEL_MAX) {
pr_debug("login method not allowed for user-space client\n");
rc = -EPERM;
goto out;
}
rc = ctx->teedev->desc->ops->open_session(ctx, &arg, params);
if (rc)
goto out;
have_session = true;
if (put_user(arg.session, &uarg->session) ||
put_user(arg.ret, &uarg->ret) ||
put_user(arg.ret_origin, &uarg->ret_origin)) {
rc = -EFAULT;
goto out;
}
rc = params_to_user(uparams, arg.num_params, params);
out:
/*
* If we've succeeded to open the session but failed to communicate
* it back to user space, close the session again to avoid leakage.
*/
if (rc && have_session && ctx->teedev->desc->ops->close_session)
ctx->teedev->desc->ops->close_session(ctx, arg.session);
if (params) {
/* Decrease ref count for all valid shared memory pointers */
for (n = 0; n < arg.num_params; n++)
if (tee_param_is_memref(params + n) &&
params[n].u.memref.shm)
tee_shm_put(params[n].u.memref.shm);
kfree(params);
}
return rc;
}
static int tee_ioctl_invoke(struct tee_context *ctx,
struct tee_ioctl_buf_data __user *ubuf)
{
int rc;
size_t n;
struct tee_ioctl_buf_data buf;
struct tee_ioctl_invoke_arg __user *uarg;
struct tee_ioctl_invoke_arg arg;
struct tee_ioctl_param __user *uparams = NULL;
struct tee_param *params = NULL;
if (!ctx->teedev->desc->ops->invoke_func)
return -EINVAL;
if (copy_from_user(&buf, ubuf, sizeof(buf)))
return -EFAULT;
if (buf.buf_len > TEE_MAX_ARG_SIZE ||
buf.buf_len < sizeof(struct tee_ioctl_invoke_arg))
return -EINVAL;
uarg = u64_to_user_ptr(buf.buf_ptr);
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
if (size_add(sizeof(arg), TEE_IOCTL_PARAM_SIZE(arg.num_params)) != buf.buf_len)
return -EINVAL;
if (arg.num_params) {
params = kcalloc(arg.num_params, sizeof(struct tee_param),
GFP_KERNEL);
if (!params)
return -ENOMEM;
uparams = uarg->params;
rc = params_from_user(ctx, params, arg.num_params, uparams);
if (rc)
goto out;
}
rc = ctx->teedev->desc->ops->invoke_func(ctx, &arg, params);
if (rc)
goto out;
if (put_user(arg.ret, &uarg->ret) ||
put_user(arg.ret_origin, &uarg->ret_origin)) {
rc = -EFAULT;
goto out;
}
rc = params_to_user(uparams, arg.num_params, params);
out:
if (params) {
/* Decrease ref count for all valid shared memory pointers */
for (n = 0; n < arg.num_params; n++)
if (tee_param_is_memref(params + n) &&
params[n].u.memref.shm)
tee_shm_put(params[n].u.memref.shm);
kfree(params);
}
return rc;
}
static int tee_ioctl_object_invoke(struct tee_context *ctx,
struct tee_ioctl_buf_data __user *ubuf)
{
int rc;
size_t n;
struct tee_ioctl_buf_data buf;
struct tee_ioctl_object_invoke_arg __user *uarg;
struct tee_ioctl_object_invoke_arg arg;
struct tee_ioctl_param __user *uparams = NULL;
struct tee_param *params = NULL;
if (!ctx->teedev->desc->ops->object_invoke_func)
return -EINVAL;
if (copy_from_user(&buf, ubuf, sizeof(buf)))
return -EFAULT;
if (buf.buf_len > TEE_MAX_ARG_SIZE ||
buf.buf_len < sizeof(struct tee_ioctl_object_invoke_arg))
return -EINVAL;
uarg = u64_to_user_ptr(buf.buf_ptr);
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
if (sizeof(arg) + TEE_IOCTL_PARAM_SIZE(arg.num_params) != buf.buf_len)
return -EINVAL;
if (arg.num_params) {
params = kcalloc(arg.num_params, sizeof(struct tee_param),
GFP_KERNEL);
if (!params)
return -ENOMEM;
uparams = uarg->params;
rc = params_from_user(ctx, params, arg.num_params, uparams);
if (rc)
goto out;
}
rc = ctx->teedev->desc->ops->object_invoke_func(ctx, &arg, params);
if (rc)
goto out;
if (put_user(arg.ret, &uarg->ret)) {
rc = -EFAULT;
goto out;
}
rc = params_to_user(uparams, arg.num_params, params);
out:
if (params) {
/* Decrease ref count for all valid shared memory pointers */
for (n = 0; n < arg.num_params; n++)
if (tee_param_is_memref(params + n) &&
params[n].u.memref.shm)
tee_shm_put(params[n].u.memref.shm);
kfree(params);
}
return rc;
}
static int tee_ioctl_cancel(struct tee_context *ctx,
struct tee_ioctl_cancel_arg __user *uarg)
{
struct tee_ioctl_cancel_arg arg;
if (!ctx->teedev->desc->ops->cancel_req)
return -EINVAL;
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
return ctx->teedev->desc->ops->cancel_req(ctx, arg.cancel_id,
arg.session);
}
static int
tee_ioctl_close_session(struct tee_context *ctx,
struct tee_ioctl_close_session_arg __user *uarg)
{
struct tee_ioctl_close_session_arg arg;
if (!ctx->teedev->desc->ops->close_session)
return -EINVAL;
if (copy_from_user(&arg, uarg, sizeof(arg)))
return -EFAULT;
return ctx->teedev->desc->ops->close_session(ctx, arg.session);
}
static int params_to_supp(struct tee_context *ctx,
struct tee_ioctl_param __user *uparams,
size_t num_params, struct tee_param *params)
{
size_t n;
for (n = 0; n < num_params; n++) {
struct tee_ioctl_param ip;
struct tee_param *p = params + n;
ip.attr = p->attr;
switch (p->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) {
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
ip.a = p->u.value.a;
ip.b = p->u.value.b;
ip.c = p->u.value.c;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INOUT:
ip.a = (__force unsigned long)p->u.ubuf.uaddr;
ip.b = p->u.ubuf.size;
ip.c = 0;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT:
ip.a = p->u.objref.id;
ip.b = p->u.objref.flags;
ip.c = 0;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
ip.b = p->u.memref.size;
if (!p->u.memref.shm) {
ip.a = 0;
ip.c = (u64)-1; /* invalid shm id */
break;
}
ip.a = p->u.memref.shm_offs;
ip.c = p->u.memref.shm->id;
break;
default:
ip.a = 0;
ip.b = 0;
ip.c = 0;
break;
}
if (copy_to_user(uparams + n, &ip, sizeof(ip)))
return -EFAULT;
}
return 0;
}
static int tee_ioctl_supp_recv(struct tee_context *ctx,
struct tee_ioctl_buf_data __user *ubuf)
{
int rc;
struct tee_ioctl_buf_data buf;
struct tee_iocl_supp_recv_arg __user *uarg;
struct tee_param *params;
u32 num_params;
u32 func;
if (!ctx->teedev->desc->ops->supp_recv)
return -EINVAL;
if (copy_from_user(&buf, ubuf, sizeof(buf)))
return -EFAULT;
if (buf.buf_len > TEE_MAX_ARG_SIZE ||
buf.buf_len < sizeof(struct tee_iocl_supp_recv_arg))
return -EINVAL;
uarg = u64_to_user_ptr(buf.buf_ptr);
if (get_user(num_params, &uarg->num_params))
return -EFAULT;
if (size_add(sizeof(*uarg), TEE_IOCTL_PARAM_SIZE(num_params)) != buf.buf_len)
return -EINVAL;
params = kcalloc(num_params, sizeof(struct tee_param), GFP_KERNEL);
if (!params)
return -ENOMEM;
rc = params_from_user(ctx, params, num_params, uarg->params);
if (rc)
goto out;
rc = ctx->teedev->desc->ops->supp_recv(ctx, &func, &num_params, params);
if (rc)
goto out;
if (put_user(func, &uarg->func) ||
put_user(num_params, &uarg->num_params)) {
rc = -EFAULT;
goto out;
}
rc = params_to_supp(ctx, uarg->params, num_params, params);
out:
kfree(params);
return rc;
}
static int params_from_supp(struct tee_param *params, size_t num_params,
struct tee_ioctl_param __user *uparams)
{
size_t n;
for (n = 0; n < num_params; n++) {
struct tee_param *p = params + n;
struct tee_ioctl_param ip;
if (copy_from_user(&ip, uparams + n, sizeof(ip)))
return -EFAULT;
/* All unused attribute bits has to be zero */
if (ip.attr & ~TEE_IOCTL_PARAM_ATTR_MASK)
return -EINVAL;
p->attr = ip.attr;
switch (ip.attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) {
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
/* Only out and in/out values can be updated */
p->u.value.a = ip.a;
p->u.value.b = ip.b;
p->u.value.c = ip.c;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INOUT:
p->u.ubuf.uaddr = u64_to_user_ptr(ip.a);
p->u.ubuf.size = ip.b;
if (!access_ok(params[n].u.ubuf.uaddr,
params[n].u.ubuf.size))
return -EFAULT;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT:
p->u.objref.id = ip.a;
p->u.objref.flags = ip.b;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
/*
* Only the size of the memref can be updated.
* Since we don't have access to the original
* parameters here, only store the supplied size.
* The driver will copy the updated size into the
* original parameters.
*/
p->u.memref.shm = NULL;
p->u.memref.shm_offs = 0;
p->u.memref.size = ip.b;
break;
default:
memset(&p->u, 0, sizeof(p->u));
break;
}
}
return 0;
}
static int tee_ioctl_supp_send(struct tee_context *ctx,
struct tee_ioctl_buf_data __user *ubuf)
{
long rc;
struct tee_ioctl_buf_data buf;
struct tee_iocl_supp_send_arg __user *uarg;
struct tee_param *params;
u32 num_params;
u32 ret;
/* Not valid for this driver */
if (!ctx->teedev->desc->ops->supp_send)
return -EINVAL;
if (copy_from_user(&buf, ubuf, sizeof(buf)))
return -EFAULT;
if (buf.buf_len > TEE_MAX_ARG_SIZE ||
buf.buf_len < sizeof(struct tee_iocl_supp_send_arg))
return -EINVAL;
uarg = u64_to_user_ptr(buf.buf_ptr);
if (get_user(ret, &uarg->ret) ||
get_user(num_params, &uarg->num_params))
return -EFAULT;
if (size_add(sizeof(*uarg), TEE_IOCTL_PARAM_SIZE(num_params)) > buf.buf_len)
return -EINVAL;
params = kcalloc(num_params, sizeof(struct tee_param), GFP_KERNEL);
if (!params)
return -ENOMEM;
rc = params_from_supp(params, num_params, uarg->params);
if (rc)
goto out;
rc = ctx->teedev->desc->ops->supp_send(ctx, ret, num_params, params);
out:
kfree(params);
return rc;
}
static long tee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct tee_context *ctx = filp->private_data;
void __user *uarg = (void __user *)arg;
switch (cmd) {
case TEE_IOC_VERSION:
return tee_ioctl_version(ctx, uarg);
case TEE_IOC_SHM_ALLOC:
return tee_ioctl_shm_alloc(ctx, uarg);
case TEE_IOC_SHM_REGISTER:
return tee_ioctl_shm_register(ctx, uarg);
case TEE_IOC_SHM_REGISTER_FD:
return tee_ioctl_shm_register_fd(ctx, uarg);
case TEE_IOC_OPEN_SESSION:
return tee_ioctl_open_session(ctx, uarg);
case TEE_IOC_INVOKE:
return tee_ioctl_invoke(ctx, uarg);
case TEE_IOC_OBJECT_INVOKE:
return tee_ioctl_object_invoke(ctx, uarg);
case TEE_IOC_CANCEL:
return tee_ioctl_cancel(ctx, uarg);
case TEE_IOC_CLOSE_SESSION:
return tee_ioctl_close_session(ctx, uarg);
case TEE_IOC_SUPPL_RECV:
return tee_ioctl_supp_recv(ctx, uarg);
case TEE_IOC_SUPPL_SEND:
return tee_ioctl_supp_send(ctx, uarg);
default:
return -EINVAL;
}
}
static const struct file_operations tee_fops = {
.owner = THIS_MODULE,
.open = tee_open,
.release = tee_release,
.unlocked_ioctl = tee_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
static void tee_release_device(struct device *dev)
{
struct tee_device *teedev = container_of(dev, struct tee_device, dev);
spin_lock(&driver_lock);
clear_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
mutex_destroy(&teedev->mutex);
idr_destroy(&teedev->idr);
kfree(teedev);
}
/**
* tee_device_alloc() - Allocate a new struct tee_device instance
* @teedesc: Descriptor for this driver
* @dev: Parent device for this device
* @pool: Shared memory pool, NULL if not used
* @driver_data: Private driver data for this device
*
* Allocates a new struct tee_device instance. The device is
* removed by tee_device_unregister().
*
* @returns a pointer to a 'struct tee_device' or an ERR_PTR on failure
*/
struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
struct device *dev,
struct tee_shm_pool *pool,
void *driver_data)
{
struct tee_device *teedev;
void *ret;
int rc, max_id;
int offs = 0;
if (!teedesc || !teedesc->name || !teedesc->ops ||
!teedesc->ops->get_version || !teedesc->ops->open ||
!teedesc->ops->release)
return ERR_PTR(-EINVAL);
teedev = kzalloc(sizeof(*teedev), GFP_KERNEL);
if (!teedev) {
ret = ERR_PTR(-ENOMEM);
goto err;
}
max_id = TEE_NUM_DEVICES / 2;
if (teedesc->flags & TEE_DESC_PRIVILEGED) {
offs = TEE_NUM_DEVICES / 2;
max_id = TEE_NUM_DEVICES;
}
spin_lock(&driver_lock);
teedev->id = find_next_zero_bit(dev_mask, max_id, offs);
if (teedev->id < max_id)
set_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
if (teedev->id >= max_id) {
ret = ERR_PTR(-ENOMEM);
goto err;
}
snprintf(teedev->name, sizeof(teedev->name), "tee%s%d",
teedesc->flags & TEE_DESC_PRIVILEGED ? "priv" : "",
teedev->id - offs);
teedev->dev.class = &tee_class;
teedev->dev.release = tee_release_device;
teedev->dev.parent = dev;
teedev->dev.devt = MKDEV(MAJOR(tee_devt), teedev->id);
rc = dev_set_name(&teedev->dev, "%s", teedev->name);
if (rc) {
ret = ERR_PTR(rc);
goto err_devt;
}
cdev_init(&teedev->cdev, &tee_fops);
teedev->cdev.owner = teedesc->owner;
dev_set_drvdata(&teedev->dev, driver_data);
device_initialize(&teedev->dev);
/* 1 as tee_device_unregister() does one final tee_device_put() */
teedev->num_users = 1;
init_completion(&teedev->c_no_users);
mutex_init(&teedev->mutex);
idr_init(&teedev->idr);
teedev->desc = teedesc;
teedev->pool = pool;
return teedev;
err_devt:
unregister_chrdev_region(teedev->dev.devt, 1);
err:
pr_err("could not register %s driver\n",
teedesc->flags & TEE_DESC_PRIVILEGED ? "privileged" : "client");
if (teedev && teedev->id < TEE_NUM_DEVICES) {
spin_lock(&driver_lock);
clear_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
}
kfree(teedev);
return ret;
}
EXPORT_SYMBOL_GPL(tee_device_alloc);
void tee_device_set_dev_groups(struct tee_device *teedev,
const struct attribute_group **dev_groups)
{
teedev->dev.groups = dev_groups;
}
EXPORT_SYMBOL_GPL(tee_device_set_dev_groups);
static ssize_t implementation_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tee_device *teedev = container_of(dev, struct tee_device, dev);
struct tee_ioctl_version_data vers;
teedev->desc->ops->get_version(teedev, &vers);
return sysfs_emit(buf, "%d\n", vers.impl_id);
}
static DEVICE_ATTR_RO(implementation_id);
static struct attribute *tee_dev_attrs[] = {
&dev_attr_implementation_id.attr,
NULL
};
ATTRIBUTE_GROUPS(tee_dev);
static const struct class tee_class = {
.name = "tee",
.dev_groups = tee_dev_groups,
};
/**
* tee_device_register() - Registers a TEE device
* @teedev: Device to register
*
* tee_device_unregister() need to be called to remove the @teedev if
* this function fails.
*
* @returns < 0 on failure
*/
int tee_device_register(struct tee_device *teedev)
{
int rc;
if (teedev->flags & TEE_DEVICE_FLAG_REGISTERED) {
dev_err(&teedev->dev, "attempt to register twice\n");
return -EINVAL;
}
rc = cdev_device_add(&teedev->cdev, &teedev->dev);
if (rc) {
dev_err(&teedev->dev,
"unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
teedev->name, MAJOR(teedev->dev.devt),
MINOR(teedev->dev.devt), rc);
return rc;
}
teedev->flags |= TEE_DEVICE_FLAG_REGISTERED;
return 0;
}
EXPORT_SYMBOL_GPL(tee_device_register);
void tee_device_put(struct tee_device *teedev)
{
mutex_lock(&teedev->mutex);
/* Shouldn't put in this state */
if (!WARN_ON(!teedev->desc)) {
teedev->num_users--;
if (!teedev->num_users) {
teedev->desc = NULL;
complete(&teedev->c_no_users);
}
}
mutex_unlock(&teedev->mutex);
}
EXPORT_SYMBOL_GPL(tee_device_put);
bool tee_device_get(struct tee_device *teedev)
{
mutex_lock(&teedev->mutex);
if (!teedev->desc) {
mutex_unlock(&teedev->mutex);
return false;
}
teedev->num_users++;
mutex_unlock(&teedev->mutex);
return true;
}
EXPORT_SYMBOL_GPL(tee_device_get);
/**
* tee_device_unregister() - Removes a TEE device
* @teedev: Device to unregister
*
* This function should be called to remove the @teedev even if
* tee_device_register() hasn't been called yet. Does nothing if
* @teedev is NULL.
*/
void tee_device_unregister(struct tee_device *teedev)
{
if (!teedev)
return;
tee_device_put_all_dma_heaps(teedev);
if (teedev->flags & TEE_DEVICE_FLAG_REGISTERED)
cdev_device_del(&teedev->cdev, &teedev->dev);
tee_device_put(teedev);
wait_for_completion(&teedev->c_no_users);
/*
* No need to take a mutex any longer now since teedev->desc was
* set to NULL before teedev->c_no_users was completed.
*/
teedev->pool = NULL;
put_device(&teedev->dev);
}
EXPORT_SYMBOL_GPL(tee_device_unregister);
/**
* tee_get_drvdata() - Return driver_data pointer
* @teedev: Device containing the driver_data pointer
* @returns the driver_data pointer supplied to tee_device_alloc().
*/
void *tee_get_drvdata(struct tee_device *teedev)
{
return dev_get_drvdata(&teedev->dev);
}
EXPORT_SYMBOL_GPL(tee_get_drvdata);
struct match_dev_data {
struct tee_ioctl_version_data *vers;
const void *data;
int (*match)(struct tee_ioctl_version_data *, const void *);
};
static int match_dev(struct device *dev, const void *data)
{
const struct match_dev_data *match_data = data;
struct tee_device *teedev = container_of(dev, struct tee_device, dev);
teedev->desc->ops->get_version(teedev, match_data->vers);
return match_data->match(match_data->vers, match_data->data);
}
struct tee_context *
tee_client_open_context(struct tee_context *start,
int (*match)(struct tee_ioctl_version_data *,
const void *),
const void *data, struct tee_ioctl_version_data *vers)
{
struct device *dev = NULL;
struct device *put_dev = NULL;
struct tee_context *ctx = NULL;
struct tee_ioctl_version_data v;
struct match_dev_data match_data = { vers ? vers : &v, data, match };
if (start)
dev = &start->teedev->dev;
do {
dev = class_find_device(&tee_class, dev, &match_data, match_dev);
if (!dev) {
ctx = ERR_PTR(-ENOENT);
break;
}
put_device(put_dev);
put_dev = dev;
ctx = teedev_open(container_of(dev, struct tee_device, dev));
} while (IS_ERR(ctx) && PTR_ERR(ctx) != -ENOMEM);
put_device(put_dev);
/*
* Default behaviour for in kernel client is to not wait for
* tee-supplicant if not present for any requests in this context.
* Also this flag could be configured again before call to
* tee_client_open_session() if any in kernel client requires
* different behaviour.
*/
if (!IS_ERR(ctx))
ctx->supp_nowait = true;
return ctx;
}
EXPORT_SYMBOL_GPL(tee_client_open_context);
void tee_client_close_context(struct tee_context *ctx)
{
teedev_close_context(ctx);
}
EXPORT_SYMBOL_GPL(tee_client_close_context);
void tee_client_get_version(struct tee_context *ctx,
struct tee_ioctl_version_data *vers)
{
ctx->teedev->desc->ops->get_version(ctx->teedev, vers);
}
EXPORT_SYMBOL_GPL(tee_client_get_version);
int tee_client_open_session(struct tee_context *ctx,
struct tee_ioctl_open_session_arg *arg,
struct tee_param *param)
{
if (!ctx->teedev->desc->ops->open_session)
return -EINVAL;
return ctx->teedev->desc->ops->open_session(ctx, arg, param);
}
EXPORT_SYMBOL_GPL(tee_client_open_session);
int tee_client_close_session(struct tee_context *ctx, u32 session)
{
if (!ctx->teedev->desc->ops->close_session)
return -EINVAL;
return ctx->teedev->desc->ops->close_session(ctx, session);
}
EXPORT_SYMBOL_GPL(tee_client_close_session);
int tee_client_system_session(struct tee_context *ctx, u32 session)
{
if (!ctx->teedev->desc->ops->system_session)
return -EINVAL;
return ctx->teedev->desc->ops->system_session(ctx, session);
}
EXPORT_SYMBOL_GPL(tee_client_system_session);
int tee_client_invoke_func(struct tee_context *ctx,
struct tee_ioctl_invoke_arg *arg,
struct tee_param *param)
{
if (!ctx->teedev->desc->ops->invoke_func)
return -EINVAL;
return ctx->teedev->desc->ops->invoke_func(ctx, arg, param);
}
EXPORT_SYMBOL_GPL(tee_client_invoke_func);
int tee_client_cancel_req(struct tee_context *ctx,
struct tee_ioctl_cancel_arg *arg)
{
if (!ctx->teedev->desc->ops->cancel_req)
return -EINVAL;
return ctx->teedev->desc->ops->cancel_req(ctx, arg->cancel_id,
arg->session);
}
static int tee_client_device_match(struct device *dev,
const struct device_driver *drv)
{
const struct tee_client_device_id *id_table;
struct tee_client_device *tee_device;
id_table = to_tee_client_driver(drv)->id_table;
tee_device = to_tee_client_device(dev);
while (!uuid_is_null(&id_table->uuid)) {
if (uuid_equal(&tee_device->id.uuid, &id_table->uuid))
return 1;
id_table++;
}
return 0;
}
static int tee_client_device_uevent(const struct device *dev,
struct kobj_uevent_env *env)
{
uuid_t *dev_id = &to_tee_client_device(dev)->id.uuid;
return add_uevent_var(env, "MODALIAS=tee:%pUb", dev_id);
}
const struct bus_type tee_bus_type = {
.name = "tee",
.match = tee_client_device_match,
.uevent = tee_client_device_uevent,
};
EXPORT_SYMBOL_GPL(tee_bus_type);
static int __init tee_init(void)
{
int rc;
rc = class_register(&tee_class);
if (rc) {
pr_err("couldn't create class\n");
return rc;
}
rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee");
if (rc) {
pr_err("failed to allocate char dev region\n");
goto out_unreg_class;
}
rc = bus_register(&tee_bus_type);
if (rc) {
pr_err("failed to register tee bus\n");
goto out_unreg_chrdev;
}
return 0;
out_unreg_chrdev:
unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES);
out_unreg_class:
class_unregister(&tee_class);
return rc;
}
static void __exit tee_exit(void)
{
bus_unregister(&tee_bus_type);
unregister_chrdev_region(tee_devt, TEE_NUM_DEVICES);
class_unregister(&tee_class);
}
subsys_initcall(tee_init);
module_exit(tee_exit);
MODULE_AUTHOR("Linaro");
MODULE_DESCRIPTION("TEE Driver");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("DMA_BUF");
MODULE_IMPORT_NS("DMA_BUF_HEAP");