This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
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.
Note: This patch series focuses on QTEE objects and userspace services.
Linux already provides a TEE subsystem, which is described in [1]. The tee subsystem provides a generic ioctl interface, TEE_IOC_INVOKE, which can be used by userspace to talk to a TEE backend driver. We extend the Linux 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.
The details of QTEE Transport Message to communicate with QTEE is available in [PATCH 10/10] Documentation: tee: Add Qualcomm TEE driver.
This patch series has been tested for basic QTEE object invocations and callback requests, including loading a TA and requesting services form the TA. However, the test platform is currently being prepared for upstream availability and will soon be accessible to the community for further validation. I will share updates as the platform become available.
[1] https://www.kernel.org/doc/Documentation/tee.txt
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- Amirreza Zarrabi (10): tee: allow a driver to allocate a tee_device without a pool tee: add TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF tee: add TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF firmware: qcom: scm: add support for object invocation qcomtee: implement object invoke support qcomtee: add primordial object dt-bindings: arm: qcomtee: add QTEE driver devicetree binding for TEE subsystem tee: add Qualcomm TEE driver arm64: dts: qcom: sm8650: add support for QTEE Documentation: tee: Add Qualcomm TEE driver
.../devicetree/bindings/arm/firmware/qcom,tee.yaml | 34 + Documentation/tee/index.rst | 1 + Documentation/tee/qtee.rst | 143 ++++ arch/arm64/boot/dts/qcom/sm8650.dtsi | 4 + drivers/firmware/qcom/qcom_scm.c | 60 ++ drivers/firmware/qcom/qcom_scm.h | 7 + drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 9 + drivers/tee/qcomtee/async.c | 153 ++++ drivers/tee/qcomtee/call.c | 707 ++++++++++++++++ drivers/tee/qcomtee/core.c | 942 +++++++++++++++++++++ drivers/tee/qcomtee/primordial_obj.c | 63 ++ drivers/tee/qcomtee/qcom_scm.c | 36 + drivers/tee/qcomtee/qcomtee_msg.h | 217 +++++ drivers/tee/qcomtee/qcomtee_private.h | 218 +++++ drivers/tee/qcomtee/release.c | 66 ++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++ drivers/tee/tee_core.c | 113 ++- include/linux/firmware/qcom/qcom_scm.h | 9 + include/linux/firmware/qcom/qcom_tee.h | 284 +++++++ include/linux/tee_core.h | 4 + include/linux/tee_drv.h | 12 + include/uapi/linux/tee.h | 54 +- 25 files changed, 3765 insertions(+), 8 deletions(-) --- base-commit: f486c8aa16b8172f63bddc70116a0c897a7f3f02 change-id: 20241202-qcom-tee-using-tee-ss-without-mem-obj-362c66340527
Best regards,
A TEE driver doesn't always need to provide a pool if it doesn't support memory sharing ioctls and can allocate memory for TEE messages in some other way. Although this is mentioned in the documentation for tee_device_alloc(), it is not handled correctly.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/tee_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index d113679b1e2d..24edce4cdbaa 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -888,7 +888,7 @@ struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
if (!teedesc || !teedesc->name || !teedesc->ops || !teedesc->ops->get_version || !teedesc->ops->open || - !teedesc->ops->release || !pool) + !teedesc->ops->release) return ERR_PTR(-EINVAL);
teedev = kzalloc(sizeof(*teedev), GFP_KERNEL);
For drivers that can transfer data to the TEE without needing shared memory from client, it is necessary to receive the user address directly, bypassing any processing by the TEE subsystem. Introduce TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT/OUTPUT/INOUT to represent userspace buffers.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/tee_core.c | 26 ++++++++++++++++++++++++++ include/linux/tee_drv.h | 6 ++++++ include/uapi/linux/tee.h | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 24edce4cdbaa..942ff5b359b2 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -381,6 +381,16 @@ static int params_from_user(struct tee_context *ctx, struct tee_param *params, params[n].u.value.b = ip.b; params[n].u.value.c = ip.c; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT: + params[n].u.membuf.uaddr = u64_to_user_ptr(ip.a); + params[n].u.membuf.size = ip.b; + + if (!access_ok(params[n].u.membuf.uaddr, params[n].u.membuf.size)) + return -EFAULT; + + 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: @@ -449,6 +459,11 @@ static int params_to_user(struct tee_ioctl_param __user *uparams, put_user(p->u.value.c, &up->c)) return -EFAULT; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT: + if (put_user((u64)p->u.membuf.size, &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)) @@ -649,6 +664,12 @@ static int params_to_supp(struct tee_context *ctx, ip.b = p->u.value.b; ip.c = p->u.value.c; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT: + ip.a = (u64)p->u.membuf.uaddr; + ip.b = p->u.membuf.size; + 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: @@ -751,6 +772,11 @@ static int params_from_supp(struct tee_param *params, size_t num_params, p->u.value.b = ip.b; p->u.value.c = ip.c; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT: + p->u.membuf.uaddr = u64_to_user_ptr(ip.a); + p->u.membuf.size = ip.b; + break; case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: /* diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index a54c203000ed..b66e611fece4 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -82,6 +82,11 @@ struct tee_param_memref { struct tee_shm *shm; };
+struct tee_param_membuf { + void * __user uaddr; + size_t size; +}; + struct tee_param_value { u64 a; u64 b; @@ -92,6 +97,7 @@ struct tee_param { u64 attr; union { struct tee_param_memref memref; + struct tee_param_membuf membuf; struct tee_param_value value; } u; }; diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index d0430bee8292..fae68386968a 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -151,6 +151,13 @@ struct tee_ioctl_buf_data { #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT 6 #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT 7 /* input and output */
+/* + * These defines memory buffer parameters. + */ +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT 8 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT 9 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT 10 /* input and output */ + /* * Mask for the type part of the attribute, leaves room for more types */ @@ -186,14 +193,17 @@ struct tee_ioctl_buf_data { /** * struct tee_ioctl_param - parameter * @attr: attributes - * @a: if a memref, offset into the shared memory object, else a value parameter - * @b: if a memref, size of the buffer, else a value parameter + * @a: if a memref, offset into the shared memory object, + * else if a membuf, address into the user buffer, + * else a value parameter + * @b: if a memref or membuf, size of the buffer, else a value parameter * @c: if a memref, shared memory identifier, else a value parameter * - * @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref or value is used in - * the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value and - * TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref. TEE_PARAM_ATTR_TYPE_NONE - * indicates that none of the members are used. + * @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref, membuf, or value is + * used in the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value, + * TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref, and TEE_PARAM_ATTR_TYPE_MEMBUF_* + * indicates membuf. TEE_PARAM_ATTR_TYPE_NONE indicates that none of the members + * are used. * * Shared memory is allocated with TEE_IOC_SHM_ALLOC which returns an * identifier representing the shared memory object. A memref can reference
Hi Amirreza,
kernel test robot noticed the following build warnings:
[auto build test WARNING on f486c8aa16b8172f63bddc70116a0c897a7f3f02]
url: https://github.com/intel-lab-lkp/linux/commits/Amirreza-Zarrabi/tee-allow-a-... base: f486c8aa16b8172f63bddc70116a0c897a7f3f02 patch link: https://lore.kernel.org/r/20241202-qcom-tee-using-tee-ss-without-mem-obj-v1-... patch subject: [PATCH 02/10] tee: add TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF config: i386-buildonly-randconfig-002-20241203 (https://download.01.org/0day-ci/archive/20241203/202412031510.Oh9kNGeK-lkp@i...) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241203/202412031510.Oh9kNGeK-lkp@i...)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202412031510.Oh9kNGeK-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/tee/tee_core.c: In function 'params_to_supp':
drivers/tee/tee_core.c:669:32: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
669 | ip.a = (u64)p->u.membuf.uaddr; | ^
vim +669 drivers/tee/tee_core.c
648 649 static int params_to_supp(struct tee_context *ctx, 650 struct tee_ioctl_param __user *uparams, 651 size_t num_params, struct tee_param *params) 652 { 653 size_t n; 654 655 for (n = 0; n < num_params; n++) { 656 struct tee_ioctl_param ip; 657 struct tee_param *p = params + n; 658 659 ip.attr = p->attr; 660 switch (p->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) { 661 case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: 662 case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: 663 ip.a = p->u.value.a; 664 ip.b = p->u.value.b; 665 ip.c = p->u.value.c; 666 break; 667 case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT: 668 case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
669 ip.a = (u64)p->u.membuf.uaddr;
670 ip.b = p->u.membuf.size; 671 ip.c = 0; 672 break; 673 case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: 674 case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: 675 case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: 676 ip.b = p->u.memref.size; 677 if (!p->u.memref.shm) { 678 ip.a = 0; 679 ip.c = (u64)-1; /* invalid shm id */ 680 break; 681 } 682 ip.a = p->u.memref.shm_offs; 683 ip.c = p->u.memref.shm->id; 684 break; 685 default: 686 ip.a = 0; 687 ip.b = 0; 688 ip.c = 0; 689 break; 690 } 691 692 if (copy_to_user(uparams + n, &ip, sizeof(ip))) 693 return -EFAULT; 694 } 695 696 return 0; 697 } 698
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
For drivers that can transfer data to the TEE without needing shared memory from client, it is necessary to receive the user address directly, bypassing any processing by the TEE subsystem. Introduce TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT/OUTPUT/INOUT to represent userspace buffers.
Internally you allocate a bounce buffer from the pool of shared memory and copy the content of the user space buffer into that. Wouldn't it be fair to replace "without needing shared memory" with "without using shared memory"?
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/tee_core.c | 26 ++++++++++++++++++++++++++ include/linux/tee_drv.h | 6 ++++++ include/uapi/linux/tee.h | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 24edce4cdbaa..942ff5b359b2 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -381,6 +381,16 @@ static int params_from_user(struct tee_context *ctx, struct tee_param *params, params[n].u.value.b = ip.b; params[n].u.value.c = ip.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
params[n].u.membuf.uaddr = u64_to_user_ptr(ip.a);
params[n].u.membuf.size = ip.b;
if (!access_ok(params[n].u.membuf.uaddr, params[n].u.membuf.size))
return -EFAULT;
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:
@@ -449,6 +459,11 @@ static int params_to_user(struct tee_ioctl_param __user *uparams, put_user(p->u.value.c, &up->c)) return -EFAULT; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
if (put_user((u64)p->u.membuf.size, &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))
@@ -649,6 +664,12 @@ static int params_to_supp(struct tee_context *ctx, ip.b = p->u.value.b; ip.c = p->u.value.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
ip.a = (u64)p->u.membuf.uaddr;
ip.b = p->u.membuf.size;
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:
@@ -751,6 +772,11 @@ static int params_from_supp(struct tee_param *params, size_t num_params, p->u.value.b = ip.b; p->u.value.c = ip.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
p->u.membuf.uaddr = u64_to_user_ptr(ip.a);
p->u.membuf.size = ip.b;
break; case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: /*
diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index a54c203000ed..b66e611fece4 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -82,6 +82,11 @@ struct tee_param_memref { struct tee_shm *shm; };
+struct tee_param_membuf {
I would prefer tee_param_ubuf to better describe what it is.
void * __user uaddr;
size_t size;
+};
struct tee_param_value { u64 a; u64 b; @@ -92,6 +97,7 @@ struct tee_param { u64 attr; union { struct tee_param_memref memref;
struct tee_param_membuf membuf; struct tee_param_value value; } u;
}; diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index d0430bee8292..fae68386968a 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -151,6 +151,13 @@ struct tee_ioctl_buf_data { #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT 6 #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT 7 /* input and output */
+/*
- These defines memory buffer parameters.
user space buffer
- */
+#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT 8 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT 9 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT 10 /* input and output */
TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_*
/*
- Mask for the type part of the attribute, leaves room for more types
*/ @@ -186,14 +193,17 @@ struct tee_ioctl_buf_data { /**
- struct tee_ioctl_param - parameter
- @attr: attributes
- @a: if a memref, offset into the shared memory object, else a value parameter
- @b: if a memref, size of the buffer, else a value parameter
- @a: if a memref, offset into the shared memory object,
else if a membuf, address into the user buffer,
else a value parameter
- @b: if a memref or membuf, size of the buffer, else a value parameter
- @c: if a memref, shared memory identifier, else a value parameter
- @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref or value is used in
- the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value and
- TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref. TEE_PARAM_ATTR_TYPE_NONE
- indicates that none of the members are used.
- @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref, membuf, or value is
- used in the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value,
- TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref, and TEE_PARAM_ATTR_TYPE_MEMBUF_*
- indicates membuf. TEE_PARAM_ATTR_TYPE_NONE indicates that none of the members
- are used.
- Shared memory is allocated with TEE_IOC_SHM_ALLOC which returns an
- identifier representing the shared memory object. A memref can reference
Please update the comment above with UBUF and ubuf as needed.
Cheers, Jens
-- 2.34.1
Hi Jens.
On 12/10/2024 2:46 AM, Jens Wiklander wrote:
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
For drivers that can transfer data to the TEE without needing shared memory from client, it is necessary to receive the user address directly, bypassing any processing by the TEE subsystem. Introduce TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT/OUTPUT/INOUT to represent userspace buffers.
Internally you allocate a bounce buffer from the pool of shared memory and copy the content of the user space buffer into that. Wouldn't it be fair to replace "without needing shared memory" with "without using shared memory"?
You are right. I'll update it.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/tee_core.c | 26 ++++++++++++++++++++++++++ include/linux/tee_drv.h | 6 ++++++ include/uapi/linux/tee.h | 22 ++++++++++++++++------ 3 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 24edce4cdbaa..942ff5b359b2 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -381,6 +381,16 @@ static int params_from_user(struct tee_context *ctx, struct tee_param *params, params[n].u.value.b = ip.b; params[n].u.value.c = ip.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
params[n].u.membuf.uaddr = u64_to_user_ptr(ip.a);
params[n].u.membuf.size = ip.b;
if (!access_ok(params[n].u.membuf.uaddr, params[n].u.membuf.size))
return -EFAULT;
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:
@@ -449,6 +459,11 @@ static int params_to_user(struct tee_ioctl_param __user *uparams, put_user(p->u.value.c, &up->c)) return -EFAULT; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
if (put_user((u64)p->u.membuf.size, &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))
@@ -649,6 +664,12 @@ static int params_to_supp(struct tee_context *ctx, ip.b = p->u.value.b; ip.c = p->u.value.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
ip.a = (u64)p->u.membuf.uaddr;
ip.b = p->u.membuf.size;
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:
@@ -751,6 +772,11 @@ static int params_from_supp(struct tee_param *params, size_t num_params, p->u.value.b = ip.b; p->u.value.c = ip.c; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT:
p->u.membuf.uaddr = u64_to_user_ptr(ip.a);
p->u.membuf.size = ip.b;
break; case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: /*
diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index a54c203000ed..b66e611fece4 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -82,6 +82,11 @@ struct tee_param_memref { struct tee_shm *shm; };
+struct tee_param_membuf {
I would prefer tee_param_ubuf to better describe what it is.
Ack.
void * __user uaddr;
size_t size;
+};
struct tee_param_value { u64 a; u64 b; @@ -92,6 +97,7 @@ struct tee_param { u64 attr; union { struct tee_param_memref memref;
struct tee_param_membuf membuf; struct tee_param_value value; } u;
}; diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index d0430bee8292..fae68386968a 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -151,6 +151,13 @@ struct tee_ioctl_buf_data { #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT 6 #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT 7 /* input and output */
+/*
- These defines memory buffer parameters.
user space buffer
- */
+#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT 8 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT 9 +#define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT 10 /* input and output */
TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_*
Ack.
/*
- Mask for the type part of the attribute, leaves room for more types
*/ @@ -186,14 +193,17 @@ struct tee_ioctl_buf_data { /**
- struct tee_ioctl_param - parameter
- @attr: attributes
- @a: if a memref, offset into the shared memory object, else a value parameter
- @b: if a memref, size of the buffer, else a value parameter
- @a: if a memref, offset into the shared memory object,
else if a membuf, address into the user buffer,
else a value parameter
- @b: if a memref or membuf, size of the buffer, else a value parameter
- @c: if a memref, shared memory identifier, else a value parameter
- @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref or value is used in
- the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value and
- TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref. TEE_PARAM_ATTR_TYPE_NONE
- indicates that none of the members are used.
- @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref, membuf, or value is
- used in the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value,
- TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref, and TEE_PARAM_ATTR_TYPE_MEMBUF_*
- indicates membuf. TEE_PARAM_ATTR_TYPE_NONE indicates that none of the members
- are used.
- Shared memory is allocated with TEE_IOC_SHM_ALLOC which returns an
- identifier representing the shared memory object. A memref can reference
Please update the comment above with UBUF and ubuf as needed.
Ack.
Cheers, Jens
Best Regadrs, Amir
-- 2.34.1
The TEE subsystem allows session-based access to trusted services, requiring a session to be established to receive a service. This is not suitable for an environment that represents services as objects. An object supports various operations that a client can invoke, potentially generating a result or a new object that can be invoked independently of the original object.
Add TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT/OUTPUT/INOUT to represent an object. Objects may reside in either TEE or userspace. To invoke an object in TEE, introduce a new ioctl. Use the existing SUPPL_RECV and SUPPL_SEND to invoke an object in userspace.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/tee_core.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/tee_core.h | 4 +++ include/linux/tee_drv.h | 6 ++++ include/uapi/linux/tee.h | 41 +++++++++++++++++++---- 4 files changed, 130 insertions(+), 6 deletions(-)
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index 942ff5b359b2..037bd17159e2 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -374,6 +374,7 @@ static int params_from_user(struct tee_context *ctx, struct tee_param *params, 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: @@ -391,6 +392,11 @@ static int params_from_user(struct tee_context *ctx, struct tee_param *params, 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: @@ -464,6 +470,12 @@ static int params_to_user(struct tee_ioctl_param __user *uparams, if (put_user((u64)p->u.membuf.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)) @@ -616,6 +628,66 @@ static int tee_ioctl_invoke(struct tee_context *ctx, 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) { @@ -670,6 +742,12 @@ static int params_to_supp(struct tee_context *ctx, ip.b = p->u.membuf.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: @@ -777,6 +855,11 @@ static int params_from_supp(struct tee_param *params, size_t num_params, p->u.membuf.uaddr = u64_to_user_ptr(ip.a); p->u.membuf.size = ip.b; 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: /* @@ -857,6 +940,8 @@ static long tee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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: diff --git a/include/linux/tee_core.h b/include/linux/tee_core.h index a38494d6b5f4..6311dedd2b83 100644 --- a/include/linux/tee_core.h +++ b/include/linux/tee_core.h @@ -71,6 +71,7 @@ struct tee_device { * @close_session: close a session * @system_session: declare session as a system session * @invoke_func: invoke a trusted function + * @object_invoke_func: invoke an object * @cancel_req: request cancel of an ongoing invoke or open * @supp_recv: called for supplicant to get a command * @supp_send: called for supplicant to send a response @@ -90,6 +91,9 @@ struct tee_driver_ops { int (*invoke_func)(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param); + int (*object_invoke_func)(struct tee_context *ctx, + struct tee_ioctl_object_invoke_arg *arg, + struct tee_param *param); int (*cancel_req)(struct tee_context *ctx, u32 cancel_id, u32 session); int (*supp_recv)(struct tee_context *ctx, u32 *func, u32 *num_params, struct tee_param *param); diff --git a/include/linux/tee_drv.h b/include/linux/tee_drv.h index b66e611fece4..a556e6bc0c53 100644 --- a/include/linux/tee_drv.h +++ b/include/linux/tee_drv.h @@ -87,6 +87,11 @@ struct tee_param_membuf { size_t size; };
+struct tee_param_objref { + u64 id; + u64 flags; +}; + struct tee_param_value { u64 a; u64 b; @@ -97,6 +102,7 @@ struct tee_param { u64 attr; union { struct tee_param_memref memref; + struct tee_param_objref objref; struct tee_param_membuf membuf; struct tee_param_value value; } u; diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index fae68386968a..5d33a8009efb 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -48,8 +48,10 @@ #define TEE_GEN_CAP_PRIVILEGED (1 << 1)/* Privileged device (for supplicant) */ #define TEE_GEN_CAP_REG_MEM (1 << 2)/* Supports registering shared memory */ #define TEE_GEN_CAP_MEMREF_NULL (1 << 3)/* NULL MemRef support */ +#define TEE_GEN_CAP_OBJREF (1 << 4)/* Supports generic object reference */
-#define TEE_MEMREF_NULL (__u64)(-1) /* NULL MemRef Buffer */ +#define TEE_MEMREF_NULL ((__u64)(-1)) /* NULL MemRef Buffer */ +#define TEE_OBJREF_NULL ((__u64)(-1)) /* NULL ObjRef Object */
/* * TEE Implementation ID @@ -158,6 +160,13 @@ struct tee_ioctl_buf_data { #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT 9 #define TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INOUT 10 /* input and output */
+/* + * These defines object reference parameters. + */ +#define TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT 11 +#define TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT 12 +#define TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT 13 + /* * Mask for the type part of the attribute, leaves room for more types */ @@ -195,15 +204,16 @@ struct tee_ioctl_buf_data { * @attr: attributes * @a: if a memref, offset into the shared memory object, * else if a membuf, address into the user buffer, - * else a value parameter - * @b: if a memref or membuf, size of the buffer, else a value parameter + * else if an objref, object identifier, else a value parameter + * @b: if a memref or membuf, size of the buffer, + * else if objref, a flag for object, else a value parameter * @c: if a memref, shared memory identifier, else a value parameter * * @attr & TEE_PARAM_ATTR_TYPE_MASK indicates if memref, membuf, or value is * used in the union. TEE_PARAM_ATTR_TYPE_VALUE_* indicates value, - * TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref, and TEE_PARAM_ATTR_TYPE_MEMBUF_* - * indicates membuf. TEE_PARAM_ATTR_TYPE_NONE indicates that none of the members - * are used. + * TEE_PARAM_ATTR_TYPE_MEMREF_* indicates memref, TEE_PARAM_ATTR_TYPE_MEMBUF_* + * indicates membuf, and TEE_PARAM_ATTR_TYPE_OBJREF_* indicates objref. + * TEE_PARAM_ATTR_TYPE_NONE indicates that none of the members are used. * * Shared memory is allocated with TEE_IOC_SHM_ALLOC which returns an * identifier representing the shared memory object. A memref can reference @@ -411,4 +421,23 @@ struct tee_ioctl_shm_register_data { * munmap(): unmaps previously shared memory */
+/** + * struct tee_ioctl_invoke_func_arg - Invokes an object in a Trusted Application. + * @object: [in] Object id + * @op: [in] Object operation, specific to the object + * @ret: [out] return value + * @num_params [in] number of parameters following this struct + */ +struct tee_ioctl_object_invoke_arg { + __u64 object; + __u32 op; + __u32 ret; + __u32 num_params; + /* num_params tells the actual number of element in params */ + struct tee_ioctl_param params[]; +}; + +#define TEE_IOC_OBJECT_INVOKE _IOR(TEE_IOC_MAGIC, TEE_IOC_BASE + 10, \ + struct tee_ioctl_buf_data) + #endif /*__TEE_H*/
Qualcomm TEE (QTEE) hosts Trusted Applications (TAs) and services in the secure world, accessed via objects. A QTEE client can invoke these objects to request services. Similarly, QTEE can request services from the nonsecure world using objects exported to the secure world.
This introduces low-level primitives to facilitate the invocation of objects hosted in QTEE, as well as those hosted in the nonsecure world.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/firmware/qcom/qcom_scm.c | 60 ++++++++++++++++++++++++++++++++++ drivers/firmware/qcom/qcom_scm.h | 7 ++++ include/linux/firmware/qcom/qcom_scm.h | 9 +++++ 3 files changed, 76 insertions(+)
diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 72bf87ddcd96..8ac570df192b 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -1862,6 +1862,66 @@ static int qcom_scm_qseecom_init(struct qcom_scm *scm)
#endif /* CONFIG_QCOM_QSEECOM */
+#ifdef CONFIG_QCOMTEE + +int qcom_scm_qtee_invoke_smc(u64 inbuf, u64 inbuf_size, u64 outbuf, u64 outbuf_size, + u64 *result, u64 *response_type, u64 *data) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_SMCINVOKE, + .cmd = QCOM_SCM_SMCINVOKE_INVOKE, + .owner = ARM_SMCCC_OWNER_TRUSTED_OS, + .args[0] = inbuf, + .args[1] = inbuf_size, + .args[2] = outbuf, + .args[3] = outbuf_size, + .arginfo = QCOM_SCM_ARGS(4, QCOM_SCM_RW, QCOM_SCM_VAL, QCOM_SCM_RW, QCOM_SCM_VAL), + }; + struct qcom_scm_res res; + int ret; + + ret = qcom_scm_call(__scm->dev, &desc, &res); + if (ret) + return ret; + + *response_type = res.result[0]; + *result = res.result[1]; + if (data) + *data = res.result[2]; + + return 0; +} +EXPORT_SYMBOL(qcom_scm_qtee_invoke_smc); + +int qcom_scm_qtee_callback_response(u64 buf, u64 buf_size, u64 *result, u64 *response_type, + u64 *data) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_SMCINVOKE, + .cmd = QCOM_SCM_SMCINVOKE_CB_RSP, + .owner = ARM_SMCCC_OWNER_TRUSTED_OS, + .args[0] = buf, + .args[1] = buf_size, + .arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_RW, QCOM_SCM_VAL), + }; + struct qcom_scm_res res; + int ret; + + ret = qcom_scm_call(__scm->dev, &desc, &res); + if (ret) + return ret; + + *response_type = res.result[0]; + *result = res.result[1]; + if (data) + *data = res.result[2]; + + return 0; +} +EXPORT_SYMBOL(qcom_scm_qtee_callback_response); + +#endif /* CONFIG_QCOMTEE */ + /** * qcom_scm_is_available() - Checks if SCM is available */ diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h index e36b2f67607f..6b6bdee39236 100644 --- a/drivers/firmware/qcom/qcom_scm.h +++ b/drivers/firmware/qcom/qcom_scm.h @@ -148,6 +148,13 @@ struct qcom_tzmem_pool *qcom_scm_get_tzmem_pool(void); #define QCOM_SCM_SVC_GPU 0x28 #define QCOM_SCM_SVC_GPU_INIT_REGS 0x01
+/* ARM_SMCCC_OWNER_TRUSTED_OS calls */ + +#define QCOM_SCM_SVC_SMCINVOKE 0x06 +#define QCOM_SCM_SMCINVOKE_INVOKE_LEGACY 0x00 +#define QCOM_SCM_SMCINVOKE_CB_RSP 0x01 +#define QCOM_SCM_SMCINVOKE_INVOKE 0x02 + /* common error codes */ #define QCOM_SCM_V2_EBUSY -12 #define QCOM_SCM_ENOMEM -5 diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index 4621aec0328c..2e2a6abf9e34 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -168,4 +168,13 @@ static inline int qcom_scm_qseecom_app_send(u32 app_id,
#endif /* CONFIG_QCOM_QSEECOM */
+#ifdef CONFIG_QCOMTEE + +int qcom_scm_qtee_invoke_smc(u64 inbuf, u64 inbuf_size, u64 outbuf, u64 outbuf_size, + u64 *result, u64 *response_type, u64 *data); +int qcom_scm_qtee_callback_response(u64 buf, u64 buf_size, u64 *result, u64 *response_type, + u64 *data); + +#endif /* CONFIG_QCOMTEE */ + #endif
Introduce qcom_tee_object, which represents an object in both QTEE and the kernel. QTEE clients can invoke an instance of qcom_tee_object to access QTEE services. If this invocation produces a new object in QTEE, an instance of qcom_tee_object will be returned.
Similarly, QTEE can request services from the kernel by issuing a callback request, which invokes an instance of qcom_tee_object in the kernel. Any subsystem that exposes a service to QTEE should allocate and initialize an instance of qcom_tee_object with a dispatcher callback that is called when the object is invoked.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 6 + drivers/tee/qcomtee/async.c | 153 ++++++ drivers/tee/qcomtee/core.c | 928 +++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcom_scm.c | 36 ++ drivers/tee/qcomtee/qcomtee_msg.h | 217 ++++++++ drivers/tee/qcomtee/qcomtee_private.h | 47 ++ drivers/tee/qcomtee/release.c | 66 +++ include/linux/firmware/qcom/qcom_tee.h | 284 ++++++++++ 11 files changed, 1749 insertions(+)
diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 61b507c18780..3a995d7f0d74 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -16,5 +16,6 @@ if TEE source "drivers/tee/optee/Kconfig" source "drivers/tee/amdtee/Kconfig" source "drivers/tee/tstee/Kconfig" +source "drivers/tee/qcomtee/Kconfig"
endif diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile index 5488cba30bd2..74e987f8f7ea 100644 --- a/drivers/tee/Makefile +++ b/drivers/tee/Makefile @@ -6,3 +6,4 @@ tee-objs += tee_shm_pool.o obj-$(CONFIG_OPTEE) += optee/ obj-$(CONFIG_AMDTEE) += amdtee/ obj-$(CONFIG_ARM_TSTEE) += tstee/ +obj-$(CONFIG_QCOMTEE) += qcomtee/ diff --git a/drivers/tee/qcomtee/Kconfig b/drivers/tee/qcomtee/Kconfig new file mode 100644 index 000000000000..d180a6d07d33 --- /dev/null +++ b/drivers/tee/qcomtee/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Qualcomm Trusted Execution Environment Configuration +config QCOMTEE + tristate "Qualcomm TEE Support" + select QCOM_SCM + help + This option enables the Qualcomm Trusted Execution Environment (QTEE) + driver. It provides an API to access services offered by QTEE and any + loaded Trusted Applications (TAs), as well as exporting kernel + services to QTEE. diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile new file mode 100644 index 000000000000..7dc5e6373042 --- /dev/null +++ b/drivers/tee/qcomtee/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_QCOMTEE) += qcomtee.o +qcomtee-objs += async.o +qcomtee-objs += core.o +qcomtee-objs += qcom_scm.o +qcomtee-objs += release.o diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c new file mode 100644 index 000000000000..218ec0209722 --- /dev/null +++ b/drivers/tee/qcomtee/async.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/mutex.h> +#include <linux/slab.h> + +#include "qcomtee_private.h" +#include "qcomtee_msg.h" + +#define QCOM_TEE_ASYNC_VERSION_1_0 0x00010000U /* Major: 0x0001, Minor: 0x0000. */ +#define QCOM_TEE_ASYNC_VERSION_1_1 0x00010001U /* Major: 0x0001, Minor: 0x0001. */ +#define QCOM_TEE_ASYNC_VERSION_1_2 0x00010002U /* Major: 0x0001, Minor: 0x0002. */ +#define QCOM_TEE_ASYNC_VERSION QCOM_TEE_ASYNC_VERSION_1_2 /* Current Version. */ + +#define QCOM_TEE_ASYNC_VERSION_MAJOR(n) upper_16_bits(n) +#define QCOM_TEE_ASYNC_VERSION_MINOR(n) lower_16_bits(n) + +/** + * struct qcom_tee_async_msg_hdr - Asynchronous message header format. + * @version: current async protocol version of remote endpoint + * @op: async operation + * + * @version specifies the endpoints (QTEE or driver) supported async protocol, e.g. + * if QTEE set @version to %QCOM_TEE_ASYNC_VERSION_1_1, QTEE handles operations + * supported in %QCOM_TEE_ASYNC_VERSION_1_1 or %QCOM_TEE_ASYNC_VERSION_1_0. + * @op determins the message format. + */ +struct qcom_tee_async_msg_hdr { + u32 version; + u32 op; +}; + +/** + * struct qcom_tee_async_release_msg - Release asynchronous message. + * @hdr: message header as &struct qcom_tee_async_msg_hdr + * @counts: number of objects in @object_ids + * @object_ids: array of object ids should be released + * + * Available in Major = 0x0001, Minor >= 0x0000. + */ +struct qcom_tee_async_release_msg { + struct qcom_tee_async_msg_hdr hdr; + u32 counts; + u32 object_ids[] __counted_by(counts); +}; + +/** + * qcom_tee_get_async_buffer() - Get start of the asynchronous message in outbound buffer. + * @oic: context used for current invocation + * @async_buffer: return buffer to extract from or fill in async messages + * + * If @oic is used for direct object invocation, whole outbound buffer is available for + * async message. If @oic is used for callback request, the tail of outbound buffer (after + * the callback request message) is available for async message. + */ +static void qcom_tee_get_async_buffer(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_buffer *async_buffer) +{ + struct qcom_tee_msg_callback *msg; + unsigned int offset; + int i; + + if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) { + /* The outbound buffer is empty. Using the whole buffer. */ + offset = 0; + } else { + msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr; + + /* Start offset in a message for buffer arguments. */ + offset = qcom_tee_msg_buffer_args(struct qcom_tee_msg_callback, + qcom_tee_msg_args(msg)); + + /* Add size of IB arguments. */ + qcom_tee_msg_for_each_input_buffer(i, msg) + offset += qcom_tee_msg_offset_align(msg->args[i].b.size); + + /* Add size of OB arguments. */ + qcom_tee_msg_for_each_output_buffer(i, msg) + offset += qcom_tee_msg_offset_align(msg->args[i].b.size); + } + + async_buffer->addr = oic->out_msg.addr + offset; + async_buffer->size = oic->out_msg.size - offset; +} + +/** + * qcom_tee_async_release_handler() - Process QTEE async requests for releasing objects. + * @oic: context used for current invocation + * @msg: async message for object release + * @size: size of the async buffer available + * + * Return: Size of outbound buffer used when processing @msg. + */ +static size_t qcom_tee_async_release_handler(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_async_msg_hdr *async_msg, size_t size) +{ + struct qcom_tee_async_release_msg *msg = (struct qcom_tee_async_release_msg *)async_msg; + struct qcom_tee_object *object; + int i; + + for (i = 0; i < msg->counts; i++) { + object = qcom_tee_idx_erase(msg->object_ids[i]); + qcom_tee_object_put(object); + } + + return struct_size_t(struct qcom_tee_async_release_msg, object_ids, i); +} + +/** + * qcom_tee_fetch_async_reqs() - Fetch and process asynchronous messages. + * @oic: context used for current invocation + * + * It looks for handler to process the requested operations in the async message. + * Currently, only support async release requests. + */ +void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic) +{ + struct qcom_tee_async_msg_hdr *async_msg; + struct qcom_tee_buffer async_buffer; + size_t consumed, used = 0; + + qcom_tee_get_async_buffer(oic, &async_buffer); + + while (async_buffer.size - used > sizeof(struct qcom_tee_async_msg_hdr)) { + async_msg = (struct qcom_tee_async_msg_hdr *)(async_buffer.addr + used); + + if (QCOM_TEE_ASYNC_VERSION_MAJOR(async_msg->version) != + QCOM_TEE_ASYNC_VERSION_MAJOR(QCOM_TEE_ASYNC_VERSION)) + goto out; + + switch (async_msg->op) { + case QCOM_TEE_MSG_OBJECT_OP_RELEASE: + consumed = qcom_tee_async_release_handler(oic, async_msg, + async_buffer.size - used); + break; + default: + /* Unsupported operations. */ + goto out; + } + + /* Supported operation but unable to parse the message. */ + if (!consumed) + goto out; + + used += qcom_tee_msg_offset_align(consumed); + } + + out: + /* Reset the async messages buffer so async requests do not loopback to QTEE. */ + memzero_explicit(async_buffer.addr, async_buffer.size); +} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c new file mode 100644 index 000000000000..a949ef4cceee --- /dev/null +++ b/drivers/tee/qcomtee/core.c @@ -0,0 +1,928 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/init.h> +#include <linux/kobject.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/xarray.h> + +#include "qcomtee_msg.h" +#include "qcomtee_private.h" + +/* Static instance of object represents QTEE root object. */ +struct qcom_tee_object qcom_tee_object_root = { + .name = "root", + .object_type = QCOM_TEE_OBJECT_TYPE_ROOT, + .info.qtee_id = QCOM_TEE_MSG_OBJECT_ROOT, +}; +EXPORT_SYMBOL_GPL(qcom_tee_object_root); + +/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type) +{ + while (u[i].type != QCOM_TEE_ARG_TYPE_INV && u[i].type != type) + i++; + return i; +} + +/* QTEE expects IDs with QCOM_TEE_MSG_OBJECT_NS_BIT set for object of + * QCOM_TEE_OBJECT_TYPE_CB_OBJECT type. + */ +#define QCOM_TEE_OBJECT_ID_START (QCOM_TEE_MSG_OBJECT_NS_BIT + 1) +#define QCOM_TEE_OBJECT_ID_END (UINT_MAX) + +#define QCOM_TEE_OBJECT_SET(p, type, ...) __QCOM_TEE_OBJECT_SET(p, type, ##__VA_ARGS__, 0UL) +#define __QCOM_TEE_OBJECT_SET(p, type, optr, ...) do { \ + (p)->object_type = (type); \ + (p)->info.qtee_id = (unsigned long)(optr); \ + } while (0) + +static struct qcom_tee_object *qcom_tee_object_alloc(void) +{ + struct qcom_tee_object *object; + + object = kzalloc(sizeof(*object), GFP_KERNEL); + if (object) { + QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL); + kref_init(&object->refcount); + } + + return object; +} + +void qcom_tee_object_free(struct qcom_tee_object *object) +{ + kfree(object->name); + kfree(object); +} + +static void qcom_tee_object_release(struct kref *refcount) +{ + struct qcom_tee_object *object; + struct module *owner; + const char *name; + + object = container_of(refcount, struct qcom_tee_object, refcount); + + synchronize_rcu(); + + switch (typeof_qcom_tee_object(object)) { + case QCOM_TEE_OBJECT_TYPE_TEE: + qcom_tee_release_tee_object(object); + + break; + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + /* Copy, as after release we should not access object. */ + name = object->name; + owner = object->owner; + + if (object->ops->release) + object->ops->release(object); + + module_put(owner); + kfree_const(name); + + break; + case QCOM_TEE_OBJECT_TYPE_ROOT: + case QCOM_TEE_OBJECT_TYPE_NULL: + default: + break; + } +} + +/** + * qcom_tee_object_get() - Increase object's refcount. + * @object: object to increase the refcount + */ +int qcom_tee_object_get(struct qcom_tee_object *object) +{ + if (object != NULL_QCOM_TEE_OBJECT && + object != ROOT_QCOM_TEE_OBJECT) + return kref_get_unless_zero(&object->refcount); + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_tee_object_get); + +/** + * qcom_tee_object_put() - Decrease object's refcount + * @object: object to decrease the refcount + */ +void qcom_tee_object_put(struct qcom_tee_object *object) +{ + if (object != NULL_QCOM_TEE_OBJECT && + object != ROOT_QCOM_TEE_OBJECT) + kref_put(&object->refcount, qcom_tee_object_release); +} +EXPORT_SYMBOL_GPL(qcom_tee_object_put); + +/* ''Local Object Table''. */ +/* Object from kernel that are exported to QTEE are assigned an id and stored in + * xa_qcom_local_objects (kernel object table). QTEE uses this id to reference the + * object using qcom_tee_local_object_get. + */ +static DEFINE_XARRAY_ALLOC(xa_qcom_local_objects); + +static int qcom_tee_idx_alloc(u32 *idx, struct qcom_tee_object *object) +{ + static u32 xa_last_id = QCOM_TEE_OBJECT_ID_START; + + /* Every id allocated here, has QCOM_TEE_MSG_OBJECT_NS_BIT set. */ + return xa_alloc_cyclic(&xa_qcom_local_objects, idx, object, + XA_LIMIT(QCOM_TEE_OBJECT_ID_START, QCOM_TEE_OBJECT_ID_END), + &xa_last_id, GFP_KERNEL); +} + +struct qcom_tee_object *qcom_tee_idx_erase(u32 idx) +{ + if (idx < QCOM_TEE_OBJECT_ID_START || idx > QCOM_TEE_OBJECT_ID_END) + return NULL_QCOM_TEE_OBJECT; + + return xa_erase(&xa_qcom_local_objects, idx); +} + +/** + * qcom_tee_object_id_get() - Get an id for an object to sent to QTEE. + * @object: object to get its id. + * @object_id: object id. + * + * For object hosted in REE, they are added to object table, and the idx in the + * object table is used as id. For object hosted in QTEE, use the QTEE id stored in + * @object. This is called on a path to QTEE to construct a message, see + * qcom_tee_prepare_msg() and qcom_tee_update_msg(). + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_object_id_get(struct qcom_tee_object *object, unsigned int *object_id) +{ + u32 idx; + + switch (typeof_qcom_tee_object(object)) { + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + if (qcom_tee_idx_alloc(&idx, object) < 0) + return -ENOSPC; + + *object_id = idx; + + break; + case QCOM_TEE_OBJECT_TYPE_ROOT: + case QCOM_TEE_OBJECT_TYPE_TEE: + *object_id = object->info.qtee_id; + + break; + case QCOM_TEE_OBJECT_TYPE_NULL: + *object_id = QCOM_TEE_MSG_OBJECT_NULL; + + break; + } + + return 0; +} + +/* Release object id assigned in qcom_tee_object_id_get. */ +static void qcom_tee_object_id_put(unsigned int object_id) +{ + qcom_tee_idx_erase(object_id); +} + +/** + * qcom_tee_local_object_get() - Get an object in REE referenced by the id. + * @object_id: object id. + * + * It is called on behalf of QTEE to obtain instance of object for an id. It is + * called on a path from QTEE to construct an argument of &struct qcom_tee_arg, + * see qcom_tee_update_args() and qcom_tee_prepare_args(). + * + * It increases the object's refcount on success. + * + * Return: On error returns %NULL_QCOM_TEE_OBJECT. On success, the object. + */ +static struct qcom_tee_object *qcom_tee_local_object_get(unsigned int object_id) +{ + struct qcom_tee_object *object; + + /* We trust QTEE does not mess the refcounts. + * It does not issue RELEASE request and qcom_tee_object_get(), simultaneously. + */ + + object = xa_load(&xa_qcom_local_objects, object_id); + + qcom_tee_object_get(object); + + return object; +} + +/** + * __qcom_tee_object_user_init() - Initialize an object for user. + * @object: object to initialize. + * @ot: type of object as &enum qcom_tee_object_type. + * @ops: instance of callbacks. + * @fmt: name assigned to the object. + * + * Return: On success return 0 or <0 on failure. + */ +int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot, + struct qcom_tee_object_operations *ops, struct module *owner, + const char *fmt, ...) +{ + va_list ap; + int ret; + + kref_init(&object->refcount); + QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL); + + va_start(ap, fmt); + switch (ot) { + case QCOM_TEE_OBJECT_TYPE_NULL: + ret = 0; + + break; + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + object->ops = ops; + if (!object->ops->dispatch) + return -EINVAL; + + object->owner = owner; + if (!try_module_get(object->owner)) + return -EINVAL; + + /* If failed, "no-name"; it is not really a reason to fail here. */ + object->name = kvasprintf_const(GFP_KERNEL, fmt, ap); + QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT); + + ret = 0; + break; + case QCOM_TEE_OBJECT_TYPE_ROOT: + case QCOM_TEE_OBJECT_TYPE_TEE: + default: + ret = -EINVAL; + } + va_end(ap); + + return ret; +} +EXPORT_SYMBOL_GPL(__qcom_tee_object_user_init); + +/** + * qcom_tee_object_type() - Returns type of object represented by an object id. + * @object_id: object id for the object. + * + * This is similar to typeof_qcom_tee_object() but instead of receiving object + * as argument it receives object id. It is used internally on return path + * from QTEE. + * + * Return: Returns type of object referenced by @object_id. + */ +static enum qcom_tee_object_type qcom_tee_object_type(unsigned int object_id) +{ + if (object_id == QCOM_TEE_MSG_OBJECT_NULL) + return QCOM_TEE_OBJECT_TYPE_NULL; + + if (object_id & QCOM_TEE_MSG_OBJECT_NS_BIT) + return QCOM_TEE_OBJECT_TYPE_CB_OBJECT; + + return QCOM_TEE_OBJECT_TYPE_TEE; +} + +/** + * qcom_tee_object_init() - Initialize an object for QTEE. + * @object: return object + * @object_id: object id received form QTEE + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_object_init(struct qcom_tee_object **object, unsigned int object_id) +{ + struct qcom_tee_object *qto; + int ret = 0; + + switch (qcom_tee_object_type(object_id)) { + case QCOM_TEE_OBJECT_TYPE_NULL: + *object = NULL_QCOM_TEE_OBJECT; + + break; + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + qto = qcom_tee_local_object_get(object_id); + if (qto != NULL_QCOM_TEE_OBJECT) + *object = qto; + else + ret = -EINVAL; + + break; + case QCOM_TEE_OBJECT_TYPE_TEE: + qto = qcom_tee_object_alloc(); + if (qto) { + /* If failed, "no-name"; it is not really a reason to fail here. */ + qto->name = kasprintf(GFP_KERNEL, "qcom_tee-%u", object_id); + QCOM_TEE_OBJECT_SET(qto, QCOM_TEE_OBJECT_TYPE_TEE, object_id); + + *object = qto; + } else { + ret = -ENOMEM; + } + + break; + default: + + break; + } + + if (ret) + *object = NULL_QCOM_TEE_OBJECT; + + return ret; +} + +/* Marshaling API. */ +/* qcom_tee_prepare_msg - Prepares inbound buffer for sending to QTEE + * qcom_tee_update_args - Parses QTEE response in inbound buffer + * qcom_tee_prepare_args - Parses QTEE request from outbound buffer + * qcom_tee_update_msg - Updates outbound buffer with response for QTEE request + */ + +static int qcom_tee_prepare_msg(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u) +{ + struct qcom_tee_msg_object_invoke *msg; + unsigned int object_id; + int ib, ob, io, oo; + size_t off; + int i; + + /* Use input message buffer in 'oic'. */ + msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr; + + /* Start offset in a message for buffer arguments. */ + off = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u)); + + /* Get id of object being invoked. */ + if (qcom_tee_object_id_get(object, &object_id)) + return -ENOSPC; + + ib = 0; + qcom_tee_arg_for_each_input_buffer(i, u) { + void *ptr; + + /* qcom_tee_msg_buffers_alloc() already checked overflow in message! */ + msg->args[ib].b.offset = off; + msg->args[ib].b.size = u[i].b.size; + + ptr = qcom_tee_msg_offset_to_ptr(msg, off); + if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR)) + memcpy(ptr, u[i].b.addr, u[i].b.size); + else if (copy_from_user(ptr, u[i].b.uaddr, u[i].b.size)) + return -EINVAL; + + off += qcom_tee_msg_offset_align(u[i].b.size); + ib++; + } + + ob = ib; + qcom_tee_arg_for_each_output_buffer(i, u) { + /* qcom_tee_msg_buffers_alloc() already checked overflow in message! */ + msg->args[ob].b.offset = off; + msg->args[ob].b.size = u[i].b.size; + + off += qcom_tee_msg_offset_align(u[i].b.size); + ob++; + } + + io = ob; + qcom_tee_arg_for_each_input_object(i, u) { + if (qcom_tee_object_id_get(u[i].o, &msg->args[io].o)) { + /* Unable to qcom_tee_object_id_get; put whatever we got. */ + qcom_tee_object_id_put(object_id); + for (--io; io >= ob; io--) + qcom_tee_object_id_put(msg->args[io].o); + + return -ENOSPC; + } + + io++; + } + + oo = io; + qcom_tee_arg_for_each_output_object(i, u) + oo++; + + /* Set object, operation, and argument counts. */ + qcom_tee_msg_init(msg, object_id, op, ib, ob, io, oo); + + return 0; +} + +static int qcom_tee_update_args(struct qcom_tee_arg *u, struct qcom_tee_object_invoke_ctx *oic) +{ + struct qcom_tee_msg_object_invoke *msg; + int ib, ob, io, oo; + int i, ret = 0; + + /* Use input message buffer in 'oic'. */ + msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr; + + ib = 0; + qcom_tee_arg_for_each_input_buffer(i, u) + ib++; + + ob = ib; + qcom_tee_arg_for_each_output_buffer(i, u) { + void *ptr = qcom_tee_msg_offset_to_ptr(msg, msg->args[ob].b.offset); + + if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR)) { + memcpy(u[i].b.addr, ptr, msg->args[ob].b.size); + } else if (copy_to_user(u[i].b.uaddr, ptr, msg->args[ob].b.size)) { + /* On ERROR, continue to process arguments to get to output object. */ + ret = -EINVAL; + } + + u[i].b.size = msg->args[ob].b.size; + ob++; + } + + io = ob; + qcom_tee_arg_for_each_input_object(i, u) + io++; + + oo = io; + qcom_tee_arg_for_each_output_object(i, u) { + int err; + + /* On ERROR, continue to process arguments so that we can issue the RELEASE. */ + err = qcom_tee_object_init(&u[i].o, msg->args[oo].o); + if (err) + ret = err; + + oo++; + } + + return ret; +} + +static int qcom_tee_prepare_args(struct qcom_tee_object_invoke_ctx *oic) +{ + int i, ret = 0; + + /* Use output message buffer in 'oic'. */ + struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr; + + qcom_tee_msg_for_each_input_buffer(i, msg) { + oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset); + oic->u[i].b.size = msg->args[i].b.size; + oic->u[i].type = QCOM_TEE_ARG_TYPE_IB; + } + + qcom_tee_msg_for_each_output_buffer(i, msg) { + oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset); + oic->u[i].b.size = msg->args[i].b.size; + oic->u[i].type = QCOM_TEE_ARG_TYPE_OB; + } + + qcom_tee_msg_for_each_input_object(i, msg) { + int err; + + /* On ERROR, continue to process arguments so that we can issue the RELEASE. */ + err = qcom_tee_object_init(&oic->u[i].o, msg->args[i].o); + if (err) + ret = err; + + oic->u[i].type = QCOM_TEE_ARG_TYPE_IO; + } + + qcom_tee_msg_for_each_output_object(i, msg) + oic->u[i].type = QCOM_TEE_ARG_TYPE_OO; + + /* End of Arguments. */ + oic->u[i].type = QCOM_TEE_ARG_TYPE_INV; + + return ret; +} + +static int qcom_tee_update_msg(struct qcom_tee_object_invoke_ctx *oic) +{ + int ib, ob, io, oo; + int i; + + /* Use output message buffer in 'oic'. */ + struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr; + + ib = 0; + qcom_tee_arg_for_each_input_buffer(i, oic->u) + ib++; + + ob = ib; + qcom_tee_arg_for_each_output_buffer(i, oic->u) { + /* Only reduce size; never increase it. */ + if (msg->args[ob].b.size < oic->u[i].b.size) + return -EINVAL; + + msg->args[ob].b.size = oic->u[i].b.size; + ob++; + } + + io = ob; + qcom_tee_arg_for_each_input_object(i, oic->u) + io++; + + oo = io; + qcom_tee_arg_for_each_output_object(i, oic->u) { + if (qcom_tee_object_id_get(oic->u[i].o, &msg->args[oo].o)) { + /* Unable to qcom_tee_object_id_get; put whatever we got. */ + for (--oo; oo >= io; --oo) + qcom_tee_object_id_put(msg->args[oo].o); + + return -ENOSPC; + } + + oo++; + } + + return 0; +} + +/** + * define MAX_BUFFER_SIZE - Maximum size of inbound and outbound buffers. + * + * QTEE transport does not impose any restriction on these buffers. However, if size of + * buffers are larger then %MAX_BUFFER_SIZE, user should probably use some other + * form of shared memory with QTEE. + */ +#define MAX_BUFFER_SIZE SZ_8K + +/* Pool to allocate inbound and outbound buffers. */ +static struct qcom_tzmem_pool *tzmem_msg_pool; + +static int qcom_tee_msg_buffers_alloc(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_arg *u) +{ + size_t size; + int i; + + /* Start offset in a message for buffer arguments. */ + size = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u)); + if (size > MAX_BUFFER_SIZE) + return -EINVAL; + + /* Add size of IB arguments. */ + qcom_tee_arg_for_each_input_buffer(i, u) { + size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size)); + if (size > MAX_BUFFER_SIZE) + return -EINVAL; + } + + /* Add size of OB arguments. */ + qcom_tee_arg_for_each_output_buffer(i, u) { + size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size)); + if (size > MAX_BUFFER_SIZE) + return -EINVAL; + } + + /* QTEE requires inbound buffer size to be page aligned. */ + size = PAGE_ALIGN(size); + + /* Do allocations. */ + oic->in_msg.size = size; + oic->in_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, size, GFP_KERNEL); + if (!oic->in_msg.addr) + return -EINVAL; + + oic->out_msg.size = MAX_BUFFER_SIZE; + oic->out_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, MAX_BUFFER_SIZE, GFP_KERNEL); + if (!oic->out_msg.addr) { + qcom_tzmem_free(oic->in_msg.addr); + + return -EINVAL; + } + + oic->in_msg_paddr = qcom_tzmem_to_phys(oic->in_msg.addr); + oic->out_msg_paddr = qcom_tzmem_to_phys(oic->out_msg.addr); + + /* QTEE assume unused buffers are zeroed; Do it now! */ + memzero_explicit(oic->in_msg.addr, oic->in_msg.size); + memzero_explicit(oic->out_msg.addr, oic->out_msg.size); + + return 0; +} + +static void qcom_tee_msg_buffers_free(struct qcom_tee_object_invoke_ctx *oic) +{ + qcom_tzmem_free(oic->in_msg.addr); + qcom_tzmem_free(oic->out_msg.addr); +} + +static int qcom_tee_msg_buffers_init(void) +{ + struct qcom_tzmem_pool_config config = { + .policy = QCOM_TZMEM_POLICY_ON_DEMAND, + /* 4M seems enough, it is used for QTEE meg header and qcom_tee_msg_arg array. */ + .max_size = SZ_4M + }; + + tzmem_msg_pool = qcom_tzmem_pool_new(&config); + if (IS_ERR(tzmem_msg_pool)) + return PTR_ERR(tzmem_msg_pool); + + return 0; +} + +static void qcom_tee_msg_buffers_destroy(void) +{ + qcom_tzmem_pool_free(tzmem_msg_pool); +} + +/* Invoke a REE object. */ +static void qcom_tee_object_invoke(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_msg_callback *msg) +{ + int i, errno; + u32 op; + + /* Get object being invoked. */ + unsigned int object_id = msg->cxt; + struct qcom_tee_object *object; + + /* QTEE can not invoke NULL object or objects it hosts. */ + if (qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_NULL || + qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_TEE) { + errno = -EINVAL; + goto out; + } + + object = qcom_tee_local_object_get(object_id); + if (object == NULL_QCOM_TEE_OBJECT) { + errno = -EINVAL; + goto out; + } + + oic->object = object; + + /* Filter bits used by transport. */ + op = msg->op & QCOM_TEE_MSG_OBJECT_OP_MASK; + + switch (op) { + case QCOM_TEE_MSG_OBJECT_OP_RELEASE: + qcom_tee_object_id_put(object_id); + qcom_tee_object_put(object); + errno = 0; + + break; + case QCOM_TEE_MSG_OBJECT_OP_RETAIN: + qcom_tee_object_get(object); + errno = 0; + + break; + default: + errno = qcom_tee_prepare_args(oic); + if (errno) { + /* Unable to parse the message. Release any object arrived as input. */ + qcom_tee_arg_for_each_input_buffer(i, oic->u) + qcom_tee_object_put(oic->u[i].o); + + break; + } + + errno = object->ops->dispatch(oic, object, op, oic->u); + if (!errno) { + /* On SUCCESS, notify object at appropriate time. */ + oic->flags |= QCOM_TEE_OIC_FLAG_NOTIFY; + } + } + +out: + + oic->errno = errno; +} + +/** + * __qcom_tee_object_do_invoke() - Submit an invocation for an object. + * @oic: context to use for current invocation. + * @object: object being invoked. + * @op: requested operation on object. + * @u: array of argument for the current invocation. + * @result: result returned from QTEE. + * + * The caller is responsible to keep track of the refcount for each object, + * including @object. On return, the caller loses the ownership of all input + * object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT. + * + * Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate + * the invocation, -EAGAIN if invocation failed and user may retry the invocation. + * Otherwise, -ENODEV on fatal failure. + */ +int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, + int *result) +{ + struct qcom_tee_msg_callback *cb_msg; + u64 response_type; + int i, ret, errno; + + ret = qcom_tee_msg_buffers_alloc(oic, u); + if (ret) + return ret; + + ret = qcom_tee_prepare_msg(oic, object, op, u); + if (ret) + goto out; + + cb_msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr; + + while (1) { + if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) { + errno = oic->errno; + /* Update output buffer only if result is SUCCESS. */ + if (!errno) + errno = qcom_tee_update_msg(oic); + + qcom_tee_msg_translate_err(cb_msg, errno); + } + + /* Invoke remote object. */ + ret = qcom_tee_object_invoke_ctx_invoke(oic, result, &response_type); + + if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) { + struct qcom_tee_object *qto = oic->object; + + if (qto) { + if (oic->flags & QCOM_TEE_OIC_FLAG_NOTIFY) { + if (qto->ops->notify) + qto->ops->notify(oic, qto, errno || ret); + } + + /* Matching get is in qcom_tee_object_invoke. */ + qcom_tee_object_put(qto); + } + + oic->object = NULL_QCOM_TEE_OBJECT; + oic->flags &= ~(QCOM_TEE_OIC_FLAG_BUSY | QCOM_TEE_OIC_FLAG_NOTIFY); + } + + if (ret) { + if (!(oic->flags & QCOM_TEE_OIC_FLAG_SHARED)) { + /* Release QCOM_TEE_OBJECT_TYPE_CB_OBJECT input objects. */ + qcom_tee_arg_for_each_input_object(i, u) + if (typeof_qcom_tee_object(u[i].o) == + QCOM_TEE_OBJECT_TYPE_CB_OBJECT) + qcom_tee_object_put(u[i].o); + + ret = -EAGAIN; + } else { + /* On error, there is no clean way to exit. */ + /* For some reason we can not communicate with QTEE, so we can not + * notify QTEE about the failure and do further cleanup. + */ + ret = -ENODEV; + } + + goto out; + + } else { + /* QTEE obtained the ownership of QCOM_TEE_OBJECT_TYPE_CB_OBJECT + * input objects in 'u'. On further failure, QTEE is responsible + * to release them. + */ + oic->flags |= QCOM_TEE_OIC_FLAG_SHARED; + } + + /* Is it a callback request? */ +#define QCOM_TEE_RESULT_INBOUND_REQ_NEEDED 3 + if (response_type != QCOM_TEE_RESULT_INBOUND_REQ_NEEDED) { + if (!*result) { + ret = qcom_tee_update_args(u, oic); + if (ret) { + qcom_tee_arg_for_each_output_object(i, u) + qcom_tee_object_put(u[i].o); + + ret = -EAGAIN; + } + } + + break; + + } else { + oic->flags |= QCOM_TEE_OIC_FLAG_BUSY; + /* Before dispatching the request, handle any pending async requests. */ + qcom_tee_fetch_async_reqs(oic); + qcom_tee_object_invoke(oic, cb_msg); + } + } + + qcom_tee_fetch_async_reqs(oic); + +out: + qcom_tee_msg_buffers_free(oic); + + return ret; +} + +int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, + int *result) +{ + /* User can not set bits used by transport. */ + if (op & ~QCOM_TEE_MSG_OBJECT_OP_MASK) + return -EINVAL; + + /* User can only invoke QTEE hosted objects. */ + if (typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_TEE && + typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_ROOT) + return -EINVAL; + + /* User can not issue reserved operations to QTEE. */ + if (op == QCOM_TEE_MSG_OBJECT_OP_RELEASE || op == QCOM_TEE_MSG_OBJECT_OP_RETAIN) + return -EINVAL; + + return __qcom_tee_object_do_invoke(oic, object, op, u, result); +} +EXPORT_SYMBOL_GPL(qcom_tee_object_do_invoke); + +/* Dump object table. */ +static ssize_t qcom_tee_object_table_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct qcom_tee_object *object; + unsigned long idx; + size_t len = 0; + + xa_for_each_start(&xa_qcom_local_objects, idx, object, QCOM_TEE_OBJECT_ID_START) { + len += sysfs_emit_at(buf, len, "%4lx %4d %s\n", idx, + kref_read(&object->refcount), + qcom_tee_object_name(object)); + } + + return len; +} + +static struct kobj_attribute object_table = __ATTR_RO(qcom_tee_object_table); +static struct kobj_attribute release = __ATTR_RO(qcom_tee_release_wq); +static struct attribute *attrs[] = { + &object_table.attr, + &release.attr, + NULL +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static struct kobject *qcom_tee_object_invoke_kobj; +static int __init qcom_tee_object_invoke_init(void) +{ + int ret; + + ret = qcom_tee_release_init(); + if (ret) + return ret; + + ret = qcom_tee_msg_buffers_init(); + if (ret) + goto err_release_destroy; + + /* Create '/sys/firmware/qcom_tee'. */ + qcom_tee_object_invoke_kobj = kobject_create_and_add("qcom_tee", firmware_kobj); + if (!qcom_tee_object_invoke_kobj) { + ret = -ENOMEM; + + goto err_msg_buffers_destroy; + } + + /* Create 'qcom_tee_object_table' and 'qcom_tee_release_wq'. */ + ret = sysfs_create_group(qcom_tee_object_invoke_kobj, &attr_group); + if (ret) + goto err_kobject_put; + + return 0; + +err_kobject_put: + /* Remove '/sys/firmware/qcom_tee'. */ + kobject_put(qcom_tee_object_invoke_kobj); +err_msg_buffers_destroy: + qcom_tee_msg_buffers_destroy(); +err_release_destroy: + qcom_tee_release_destroy(); + + return ret; +} +module_init(qcom_tee_object_invoke_init); + +static void __exit qcom_tee_object_invoke_deinit(void) +{ + /* Wait for RELEASE operations for QTEE objects. */ + qcom_tee_release_destroy(); + qcom_tee_msg_buffers_destroy(); + sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group); + kobject_put(qcom_tee_object_invoke_kobj); +} +module_exit(qcom_tee_object_invoke_deinit); + +MODULE_AUTHOR("Qualcomm"); +MODULE_DESCRIPTION("QTEE driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/qcomtee/qcom_scm.c b/drivers/tee/qcomtee/qcom_scm.c new file mode 100644 index 000000000000..230faf249095 --- /dev/null +++ b/drivers/tee/qcomtee/qcom_scm.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/firmware/qcom/qcom_scm.h> + +#include "qcomtee_private.h" + +int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic, + int *result, u64 *response_type) +{ + int ret; + u64 res; + + if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) { + /* Direct QTEE object invocation. */ + ret = qcom_scm_qtee_invoke_smc(oic->in_msg_paddr, + oic->in_msg.size, + oic->out_msg_paddr, + oic->out_msg.size, + &res, response_type, NULL); + } else { + /* Submit callback response. */ + ret = qcom_scm_qtee_callback_response(oic->out_msg_paddr, + oic->out_msg.size, + &res, response_type, NULL); + } + + if (ret) + pr_err("QTEE returned with %d.\n", ret); + else + *result = (int)res; + + return ret; +} diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h new file mode 100644 index 000000000000..7c968834ec9d --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef QCOMTEE_MSG_H +#define QCOMTEE_MSG_H + +#include <linux/firmware/qcom/qcom_tee.h> + +/** + * DOC: ''Qualcomm TEE'' (QTEE) Transport Message + * + * There are two buffers shared with QTEE, inbound and outbound buffers. + * The inbound buffer is used for direct object invocation and the outbound buffer is + * used to make a request from QTEE to kernel, i.e. callback request. + * + * The unused tail of the outbound buffer is also used for sending and receiving + * asynchronous messages. An asynchronous message is independent from the current + * object invocation (i.e. contents of the inbound buffer) or callback request + * (i.e. the head of the outbound buffer), see qcom_tee_get_async_buffer(). It is + * used by endpoints (QTEE or kernel) as an optimization to reduce number of context + * switches between secure and non-secure world. + * + * For instance, QTEE never sends an explicit callback request to release an object in + * kernel. Instead, it sends asynchronous release messages in outbound buffer when QTEE + * returns from previous direct object invocation, or append asynchronous release + * messages after the current callback request. + * + * QTEE supports two types of arguments in a message: buffer and object arguments. + * Depending on the direction of data flow, they could be input buffer (IO) to QTEE, + * output buffer (OB) from QTEE, input object (IO) to QTEE, or output object (OO) from + * QTEE. Object arguments hold object ids. Buffer arguments hold (offset, size) pairs + * into the inbound or outbound buffers. + * + * QTEE holds an object table for objects, it hosts and exposes to kernel. An object id + * is an index to the object table in QTEE. + * + * For direct object invocation message format in inbound buffer see + * &struct qcom_tee_msg_object_invoke. For callback request message format in outbound + * buffer see &struct qcom_tee_msg_callback. For the message format for asynchronous message + * in outbound buffer see &struct qcom_tee_async_msg_hdr. + */ + +/** + * define QCOM_TEE_MSG_OBJECT_NS_BIT - Non-secure bit + * + * Object id is a globally unique 32-bit number. Ids referencing objects in kernel should + * have %QCOM_TEE_MSG_OBJECT_NS_BIT set. + */ +#define QCOM_TEE_MSG_OBJECT_NS_BIT BIT(31) + +/* Static object ids recognized by QTEE. */ +#define QCOM_TEE_MSG_OBJECT_NULL (0U) +#define QCOM_TEE_MSG_OBJECT_ROOT (1U) + +/* Definitions from QTEE as part of the transport protocol. */ + +/* qcom_tee_msg_arg is argument as recognized by QTEE. */ +union qcom_tee_msg_arg { + struct { + u32 offset; + u32 size; + } b; + u32 o; +}; + +/* BI and BO payloads in a QTEE messages should be at 64-bit boundaries. */ +#define qcom_tee_msg_offset_align(o) ALIGN((o), sizeof(u64)) + +/* Operation for objects is 32-bit. Transport uses upper 16-bits internally. */ +#define QCOM_TEE_MSG_OBJECT_OP_MASK 0x0000FFFFU + +/* Reserved Operation IDs sent to QTEE: */ +/* QCOM_TEE_MSG_OBJECT_OP_RELEASE - Reduces the refcount and releases the object. + * QCOM_TEE_MSG_OBJECT_OP_RETAIN - Increases the refcount. + * + * These operation id are valid for all objects. They are not available outside of this + * driver. Developers should use qcom_tee_object_get() and qcom_tee_object_put(), to + * achieve the same. + */ + +#define QCOM_TEE_MSG_OBJECT_OP_RELEASE (QCOM_TEE_MSG_OBJECT_OP_MASK - 0) +#define QCOM_TEE_MSG_OBJECT_OP_RETAIN (QCOM_TEE_MSG_OBJECT_OP_MASK - 1) + +/** + * struct qcom_tee_msg_object_invoke - Direct object invocation message + * @ctx: object id hosted in QTEE + * @op: operation for the object + * @counts: number of different type of arguments in @args + * @args: array of arguments + * + * @counts consists of 4 * 4-bits felids. Bits 0 - 3, is number of input buffers, + * bits 4 - 7, is number of output buffers, bits 8 - 11, is number of input objects, + * and bits 12 - 15, is number of output objects. Remaining bits should be zero. + * + * Maximum number of arguments of each type is defined by %QCOM_TEE_ARGS_PER_TYPE. + */ +struct qcom_tee_msg_object_invoke { + u32 cxt; + u32 op; + u32 counts; + union qcom_tee_msg_arg args[]; +}; + +/** + * struct qcom_tee_msg_callback - Callback request message + * @result: result of operation @op on object referenced by @cxt + * @cxt: object id hosted in kernel + * @op: operation for the object + * @counts: number of different type of arguments in @args + * @args: array of arguments + * + * For details of @counts, see &qcom_tee_msg_object_invoke.counts. + */ +struct qcom_tee_msg_callback { + u32 result; + u32 cxt; + u32 op; + u32 counts; + union qcom_tee_msg_arg args[]; +}; + +/* Offset in the message for the beginning of buffer argument's contents. */ +#define qcom_tee_msg_buffer_args(t, n) \ + qcom_tee_msg_offset_align(struct_size_t(t, args, n)) +/* Pointer to the beginning of a buffer argument's content at an offset in a message. */ +#define qcom_tee_msg_offset_to_ptr(m, off) ((void *)&((char *)(m))[(off)]) + +/* Some helpers to manage msg.counts. */ + +#define QCOM_TEE_MSG_NUM_IB(x) ((x) & 0xfU) +#define QCOM_TEE_MSG_NUM_OB(x) (((x) >> 4) & 0xfU) +#define QCOM_TEE_MSG_NUM_IO(x) (((x) >> 8) & 0xfU) +#define QCOM_TEE_MSG_NUM_OO(x) (((x) >> 12) & 0xfU) + +#define QCOM_TEE_MSG_IDX_IB(x) (0U) +#define QCOM_TEE_MSG_IDX_OB(x) (QCOM_TEE_MSG_IDX_IB(x) + QCOM_TEE_MSG_NUM_IB(x)) +#define QCOM_TEE_MSG_IDX_IO(x) (QCOM_TEE_MSG_IDX_OB(x) + QCOM_TEE_MSG_NUM_OB(x)) +#define QCOM_TEE_MSG_IDX_OO(x) (QCOM_TEE_MSG_IDX_IO(x) + QCOM_TEE_MSG_NUM_IO(x)) + +#define qcom_tee_msg_for_each(i, c, type) \ + for (i = QCOM_TEE_MSG_IDX_##type(c); \ + i < (QCOM_TEE_MSG_IDX_##type(c) + QCOM_TEE_MSG_NUM_##type(c)); \ + i++) + +#define qcom_tee_msg_for_each_input_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, IB) +#define qcom_tee_msg_for_each_output_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, OB) +#define qcom_tee_msg_for_each_input_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, IO) +#define qcom_tee_msg_for_each_output_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, OO) + +/* Sum of arguments in a message. */ +#define qcom_tee_msg_args(m) (QCOM_TEE_MSG_IDX_OO((m)->counts) + QCOM_TEE_MSG_NUM_OO((m)->counts)) + +static inline void qcom_tee_msg_init(struct qcom_tee_msg_object_invoke *msg, u32 cxt, u32 op, + int in_buffer, int out_buffer, int in_object, int out_object) +{ + msg->counts |= (in_buffer & 0xfU); + msg->counts |= ((out_buffer - in_buffer) & 0xfU) << 4; + msg->counts |= ((in_object - out_buffer) & 0xfU) << 8; + msg->counts |= ((out_object - in_object) & 0xfU) << 12; + msg->cxt = cxt; + msg->op = op; +} + +/* Generic error codes. */ +#define QCOM_TEE_MSG_OK 0 /* non-specific success code. */ +#define QCOM_TEE_MSG_ERROR 1 /* non-specific error. */ +#define QCOM_TEE_MSG_ERROR_INVALID 2 /* unsupported/unrecognized request. */ +#define QCOM_TEE_MSG_ERROR_SIZE_IN 3 /* supplied buffer/string too large. */ +#define QCOM_TEE_MSG_ERROR_SIZE_OUT 4 /* supplied output buffer too small. */ +#define QCOM_TEE_MSG_ERROR_USERBASE 10 /* start of user-defined error range. */ + +/* Transport layer error codes. */ +#define QCOM_TEE_MSG_ERROR_DEFUNCT -90 /* object no longer exists. */ +#define QCOM_TEE_MSG_ERROR_ABORT -91 /* calling thread must exit. */ +#define QCOM_TEE_MSG_ERROR_BADOBJ -92 /* invalid object context. */ +#define QCOM_TEE_MSG_ERROR_NOSLOTS -93 /* caller's object table full. */ +#define QCOM_TEE_MSG_ERROR_MAXARGS -94 /* too many args. */ +#define QCOM_TEE_MSG_ERROR_MAXDATA -95 /* buffers too large. */ +#define QCOM_TEE_MSG_ERROR_UNAVAIL -96 /* the request could not be processed. */ +#define QCOM_TEE_MSG_ERROR_KMEM -97 /* kernel out of memory. */ +#define QCOM_TEE_MSG_ERROR_REMOTE -98 /* local method sent to remote object. */ +#define QCOM_TEE_MSG_ERROR_BUSY -99 /* Object is busy. */ +#define QCOM_TEE_MSG_ERROR_TIMEOUT -103 /* Call Back Object invocation timed out. */ + +static inline void qcom_tee_msg_translate_err(struct qcom_tee_msg_callback *cb_msg, int err) +{ + if (!err) { + cb_msg->result = QCOM_TEE_MSG_OK; + } else if (err < 0) { + /* If err < 0, then it is a transport error. */ + switch (err) { + case -ENOMEM: + cb_msg->result = QCOM_TEE_MSG_ERROR_KMEM; + break; + case -ENODEV: + cb_msg->result = QCOM_TEE_MSG_ERROR_DEFUNCT; + break; + case -ENOSPC: + case -EBUSY: + cb_msg->result = QCOM_TEE_MSG_ERROR_BUSY; + break; + case -EBADF: + case -EINVAL: + cb_msg->result = QCOM_TEE_MSG_ERROR_UNAVAIL; + break; + default: + cb_msg->result = QCOM_TEE_MSG_ERROR; + } + } else { + /* If err > 0, then it is user defined error, pass it as is. */ + cb_msg->result = err; + } +} + +#endif /* QCOMTEE_MSG_H */ diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h new file mode 100644 index 000000000000..e3e4ef51c0b2 --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef QCOM_TEE_PRIVATE_H +#define QCOM_TEE_PRIVATE_H + +#include <linux/firmware/qcom/qcom_tee.h> +#include <linux/kobject.h> +#include <linux/tee_core.h> + +struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); +void qcom_tee_object_free(struct qcom_tee_object *object); + +/* Process async messages form QTEE. */ +void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic); + +int qcom_tee_release_init(void); +void qcom_tee_release_destroy(void); +void qcom_tee_release_tee_object(struct qcom_tee_object *object); +ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); + +/* SCM call. */ +int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic, + int *result, u64 *response_type); + +/** + * __qcom_tee_object_do_invoke() - Submit an invocation for an object. + * @oic: context to use for current invocation. + * @object: object being invoked. + * @op: requested operation on object. + * @u: array of argument for the current invocation. + * @result: result returned from QTEE. + * + * Same as qcom_tee_object_do_invoke() without @object and @op is 32-bit, + * upper 16-bits are for internal use. + * + * Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate + * the invocation, -EAGAIN if invocation failed and user can retry the invocation. + * Otherwise, -ENODEV on fatal failure. + */ +int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, + int *result); + +#endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/release.c b/drivers/tee/qcomtee/release.c new file mode 100644 index 000000000000..f2e048418e23 --- /dev/null +++ b/drivers/tee/qcomtee/release.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include "qcomtee_private.h" +#include "qcomtee_msg.h" + +static struct workqueue_struct *qcom_tee_release_wq; + +/* Number of all release requests pending for processing. */ +static atomic_t qcom_tee_pending_releases = ATOMIC_INIT(0); + +/* qcom_tee_object_do_release makes direct object invocation to release an object. */ +static void qcom_tee_destroy_user_object(struct work_struct *work) +{ + static struct qcom_tee_object_invoke_ctx oic; + static struct qcom_tee_arg args[1] = { 0 }; + struct qcom_tee_object *object; + int ret, result; + + object = container_of(work, struct qcom_tee_object, work); + + ret = __qcom_tee_object_do_invoke(&oic, object, QCOM_TEE_MSG_OBJECT_OP_RELEASE, args, + &result); + + /* Is it safe to retry the release? */ + if (ret == -EAGAIN) { + queue_work(qcom_tee_release_wq, &object->work); + } else { + if (ret || result) + pr_err("%s: %s release failed, ret = %d (%x).\n", + __func__, qcom_tee_object_name(object), ret, result); + + atomic_dec(&qcom_tee_pending_releases); + qcom_tee_object_free(object); + } +} + +/* qcom_tee_release_tee_object puts object in release work queue. */ +void qcom_tee_release_tee_object(struct qcom_tee_object *object) +{ + INIT_WORK(&object->work, qcom_tee_destroy_user_object); + atomic_inc(&qcom_tee_pending_releases); + queue_work(qcom_tee_release_wq, &object->work); +} + +ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", atomic_read(&qcom_tee_pending_releases)); +} + +int qcom_tee_release_init(void) +{ + qcom_tee_release_wq = alloc_ordered_workqueue("qcom_tee_release_wq", 0); + if (!qcom_tee_release_wq) + return -ENOMEM; + + return 0; +} + +void qcom_tee_release_destroy(void) +{ + /* It drains the wq. */ + destroy_workqueue(qcom_tee_release_wq); +} diff --git a/include/linux/firmware/qcom/qcom_tee.h b/include/linux/firmware/qcom/qcom_tee.h new file mode 100644 index 000000000000..90e5e10a0e62 --- /dev/null +++ b/include/linux/firmware/qcom/qcom_tee.h @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QCOM_TEE_H +#define __QCOM_TEE_H + +#include <linux/kref.h> +#include <linux/completion.h> +#include <linux/workqueue.h> + +struct qcom_tee_object; + +/** + * DOC: Overview + * + * qcom_tee_object provides object ref-counting, id allocation for objects hosted in + * REE, and necessary message marshaling for Qualcomm TEE (QTEE). + * + * To invoke an object in QTEE, user calls qcom_tee_object_do_invoke() while passing + * an instance of &struct qcom_tee_object and the requested operation + arguments. + * + * After the boot, QTEE provides a static object %ROOT_QCOM_TEE_OBJECT (type of + * %QCOM_TEE_OBJECT_TYPE_ROOT). The root object is invoked to pass user's credentials and + * obtain other instances of &struct qcom_tee_object (type of %QCOM_TEE_OBJECT_TYPE_TEE) + * that represents services and TAs in QTEE, see &enum qcom_tee_object_type. + * + * The object received from QTEE are refcounted. So the owner of these objects can + * issue qcom_tee_object_get(), to increase the refcount, and pass objects to other + * clients, or issue qcom_tee_object_put() to decrease the refcount, and releasing + * the resources in QTEE. + * + * REE can host services accessible to QTEE. A driver should embed an instance of + * &struct qcom_tee_object in the struct it wants to export to QTEE (it is called + * callback object). It issues qcom_tee_object_user_init() to set the dispatch() + * operation for the callback object and set its type to %QCOM_TEE_OBJECT_TYPE_CB_OBJECT. + * + * core.c holds an object table for callback objects. An object id is assigned + * to each callback object which is an index to the object table. QTEE uses these ids + * to reference or invoke callback objects. + * + * If QTEE invoke a callback object in REE, the dispatch() operation is called in the + * context of thread that called qcom_tee_object_do_invoke(), originally. + */ + +/** + * enum qcom_tee_object_typ - Object types. + * @QCOM_TEE_OBJECT_TYPE_TEE: object hosted on QTEE. + * @QCOM_TEE_OBJECT_TYPE_CB_OBJECT: object hosted on REE. + * @QCOM_TEE_OBJECT_TYPE_ROOT: 'primordial' object. + * @QCOM_TEE_OBJECT_TYPE_NULL: NULL object. + * + * Primordial object is used for bootstrapping the IPC connection between a REE + * and QTEE. It is invoked by REE when it wants to get a 'client env'. + */ +enum qcom_tee_object_type { + QCOM_TEE_OBJECT_TYPE_TEE, + QCOM_TEE_OBJECT_TYPE_CB_OBJECT, + QCOM_TEE_OBJECT_TYPE_ROOT, + QCOM_TEE_OBJECT_TYPE_NULL, +}; + +/** + * enum qcom_tee_arg_type - Type of QTEE argument. + * @QCOM_TEE_ARG_TYPE_INV: invalid type. + * @QCOM_TEE_ARG_TYPE_IB: input buffer (IO). + * @QCOM_TEE_ARG_TYPE_OO: output object (OO). + * @QCOM_TEE_ARG_TYPE_OB: output buffer (OB). + * @QCOM_TEE_ARG_TYPE_IO: input object (IO). + * + * Use invalid type to specify end of argument array. + */ +enum qcom_tee_arg_type { + QCOM_TEE_ARG_TYPE_INV = 0, + QCOM_TEE_ARG_TYPE_OB, + QCOM_TEE_ARG_TYPE_OO, + QCOM_TEE_ARG_TYPE_IB, + QCOM_TEE_ARG_TYPE_IO, + QCOM_TEE_ARG_TYPE_NR, +}; + +/** + * define QCOM_TEE_ARGS_PER_TYPE - Maximum arguments of specific type. + * + * QTEE transport protocol limits maximum number of argument of specific type + * (i.e. IB, OB, IO, and OO). + */ +#define QCOM_TEE_ARGS_PER_TYPE 16 + +/* Maximum arguments that can fit in a QTEE message, ignoring the type. */ +#define QCOM_TEE_ARGS_MAX (QCOM_TEE_ARGS_PER_TYPE * (QCOM_TEE_ARG_TYPE_NR - 1)) + +struct qcom_tee_buffer { + union { + void *addr; + void __user *uaddr; + }; + size_t size; +}; + +/** + * struct qcom_tee_arg - Argument for QTEE object invocation. + * @type: type of argument as &enum qcom_tee_arg_type. + * @flags: extra flags. + * @b: address and size if type of argument is buffer. + * @o: object instance if type of argument is object. + * + * &qcom_tee_arg.flags only accept %QCOM_TEE_ARG_FLAGS_UADDR for now which states + * that &qcom_tee_arg.b contains userspace address in uaddr. + */ +struct qcom_tee_arg { + enum qcom_tee_arg_type type; +/* 'b.uaddr' holds a __user address. */ +#define QCOM_TEE_ARG_FLAGS_UADDR 1 + unsigned int flags; + union { + struct qcom_tee_buffer b; + struct qcom_tee_object *o; + }; +}; + +static inline int qcom_tee_args_len(struct qcom_tee_arg *args) +{ + int i = 0; + + while (args[i].type != QCOM_TEE_ARG_TYPE_INV) + i++; + return i; +} + +#define QCOM_TEE_OIC_FLAG_BUSY BIT(1) /* Context is busy (callback is in progress). */ +#define QCOM_TEE_OIC_FLAG_NOTIFY BIT(2) /* Context needs to notify the current object. */ +#define QCOM_TEE_OIC_FLAG_SHARED BIT(3) /* Context has shared state with QTEE. */ + +struct qcom_tee_object_invoke_ctx { + unsigned long flags; + int errno; + + /* Current object invoked in this callback context. */ + struct qcom_tee_object *object; + + /* Arguments passed to dispatch callback (+1 for ending QCOM_TEE_ARG_TYPE_INV). */ + struct qcom_tee_arg u[QCOM_TEE_ARGS_MAX + 1]; + + /* Inbound and Outbound buffers shared with QTEE. */ + struct qcom_tee_buffer in_msg; /* Inbound Buffer. */ + phys_addr_t in_msg_paddr; /* Physical address of inbound buffer. */ + struct qcom_tee_buffer out_msg; /* Outbound Buffer. */ + phys_addr_t out_msg_paddr; /* Physical address of outbound buffer. */ + + /* Extra data attached to this context. */ + void *data; +}; + +/** + * qcom_tee_object_do_invoke() - Submit an invocation for an object. + * @oic: context to use for current invocation. + * @object: object being invoked. + * @op: requested operation on object. + * @u: array of argument for the current invocation. + * @result: result returned from QTEE. + * + * The caller is responsible to keep track of the refcount for each object, + * including @object. On return, the caller loses the ownership of all input object of + * type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT. + * + * @object can be of %QCOM_TEE_OBJECT_TYPE_ROOT or %QCOM_TEE_OBJECT_TYPE_TEE types. + * + * Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate + * the invocation, -EAGAIN if invocation failed and user may retry the invocation. + * Otherwise, -ENODEV on fatal failure. + */ +int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, + int *result); + +/** + * struct qcom_tee_object_operations - Callback object operations. + * @release: release object if QTEE is not using it. + * @dispatch: dispatch the operation requested by QTEE. + * @notify: report status of any pending response submitted by @dispatch. + * + * Transport may fail (e.g. object table is full) even after @dispatch successfully submitted + * the response. @notify is called to do the necessary cleanup. + */ +struct qcom_tee_object_operations { + void (*release)(struct qcom_tee_object *object); + int (*dispatch)(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *args); + void (*notify)(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, int err); +}; + +/** + * struct qcom_tee_object - QTEE or REE object. + * @name: object name. + * @refcount: reference counter. + * @object_type: object type as &enum qcom_tee_object_type. + * @info: extra information for object. + * @owner: owning module/driver. + * @ops: callback operations for object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT. + * @work: work for async operation on object. + * + * @work is currently only used for release object of %QCOM_TEE_OBJECT_TYPE_TEE type. + */ +struct qcom_tee_object { + const char *name; + struct kref refcount; + + enum qcom_tee_object_type object_type; + union object_info { + /* QTEE object id if object_type is %QCOM_TEE_OBJECT_TYPE_TEE. */ + unsigned long qtee_id; + } info; + + struct module *owner; + struct qcom_tee_object_operations *ops; + struct work_struct work; +}; + +/* Static instances of qcom_tee_object objects. */ +#define NULL_QCOM_TEE_OBJECT ((struct qcom_tee_object *)(0)) +extern struct qcom_tee_object qcom_tee_object_root; +#define ROOT_QCOM_TEE_OBJECT (&qcom_tee_object_root) + +static inline enum qcom_tee_object_type typeof_qcom_tee_object(struct qcom_tee_object *object) +{ + if (object == NULL_QCOM_TEE_OBJECT) + return QCOM_TEE_OBJECT_TYPE_NULL; + return object->object_type; +} + +static inline const char *qcom_tee_object_name(struct qcom_tee_object *object) +{ + if (object == NULL_QCOM_TEE_OBJECT) + return "null"; + + if (!object->name) + return "no-name"; + return object->name; +} + +/** + * __qcom_tee_object_user_init() - Initialize an object for user. + * @object: object to initialize. + * @object_type: type of object as &enum qcom_tee_object_type. + * @ops: instance of callbacks. + * @owner: owning module/driver. + * @fmt: name assigned to the object. + * + * Return: On success return 0 or <0 on failure. + */ +int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot, + struct qcom_tee_object_operations *ops, struct module *owner, + const char *fmt, ...); +#define qcom_tee_object_user_init(obj, ot, ops, fmt, ...) \ + __qcom_tee_object_user_init((obj), (ot), (ops), THIS_MODULE, (fmt), __VA_ARGS__) + +/* Object release is RCU protected. */ +int qcom_tee_object_get(struct qcom_tee_object *object); +void qcom_tee_object_put(struct qcom_tee_object *object); + +#define qcom_tee_arg_for_each(i, args) \ + for (i = 0; args[i].type != QCOM_TEE_ARG_TYPE_INV; i++) + +/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type); + +/* Iterate over argument of given type. */ +#define qcom_tee_arg_for_each_type(i, args, at) \ + for (i = 0, i = qcom_tee_next_arg_type(args, i, at); \ + args[i].type != QCOM_TEE_ARG_TYPE_INV; \ + i++, i = qcom_tee_next_arg_type(args, i, at)) + +#define qcom_tee_arg_for_each_input_buffer(i, args) \ + qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IB) +#define qcom_tee_arg_for_each_output_buffer(i, args) \ + qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OB) +#define qcom_tee_arg_for_each_input_object(i, args) \ + qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IO) +#define qcom_tee_arg_for_each_output_object(i, args) \ + qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OO) + +#endif /* __QCOM_TEE_H */
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce qcom_tee_object, which represents an object in both QTEE and the kernel. QTEE clients can invoke an instance of qcom_tee_object to access QTEE services. If this invocation produces a new object in QTEE, an instance of qcom_tee_object will be returned.
Similarly, QTEE can request services from the kernel by issuing a callback request, which invokes an instance of qcom_tee_object in the kernel. Any subsystem that exposes a service to QTEE should allocate and initialize an instance of qcom_tee_object with a dispatcher callback that is called when the object is invoked.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 6 + drivers/tee/qcomtee/async.c | 153 ++++++ drivers/tee/qcomtee/core.c | 928 +++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcom_scm.c | 36 ++ drivers/tee/qcomtee/qcomtee_msg.h | 217 ++++++++ drivers/tee/qcomtee/qcomtee_private.h | 47 ++ drivers/tee/qcomtee/release.c | 66 +++ include/linux/firmware/qcom/qcom_tee.h | 284 ++++++++++ 11 files changed, 1749 insertions(+)
diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 61b507c18780..3a995d7f0d74 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -16,5 +16,6 @@ if TEE source "drivers/tee/optee/Kconfig" source "drivers/tee/amdtee/Kconfig" source "drivers/tee/tstee/Kconfig" +source "drivers/tee/qcomtee/Kconfig"
endif diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile index 5488cba30bd2..74e987f8f7ea 100644 --- a/drivers/tee/Makefile +++ b/drivers/tee/Makefile @@ -6,3 +6,4 @@ tee-objs += tee_shm_pool.o obj-$(CONFIG_OPTEE) += optee/ obj-$(CONFIG_AMDTEE) += amdtee/ obj-$(CONFIG_ARM_TSTEE) += tstee/ +obj-$(CONFIG_QCOMTEE) += qcomtee/ diff --git a/drivers/tee/qcomtee/Kconfig b/drivers/tee/qcomtee/Kconfig new file mode 100644 index 000000000000..d180a6d07d33 --- /dev/null +++ b/drivers/tee/qcomtee/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Qualcomm Trusted Execution Environment Configuration +config QCOMTEE
tristate "Qualcomm TEE Support"
select QCOM_SCM
help
This option enables the Qualcomm Trusted Execution Environment (QTEE)
driver. It provides an API to access services offered by QTEE and any
loaded Trusted Applications (TAs), as well as exporting kernel
services to QTEE.
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile new file mode 100644 index 000000000000..7dc5e6373042 --- /dev/null +++ b/drivers/tee/qcomtee/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_QCOMTEE) += qcomtee.o +qcomtee-objs += async.o +qcomtee-objs += core.o +qcomtee-objs += qcom_scm.o +qcomtee-objs += release.o diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c new file mode 100644 index 000000000000..218ec0209722 --- /dev/null +++ b/drivers/tee/qcomtee/async.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include <linux/mutex.h> +#include <linux/slab.h>
+#include "qcomtee_private.h" +#include "qcomtee_msg.h"
+#define QCOM_TEE_ASYNC_VERSION_1_0 0x00010000U /* Major: 0x0001, Minor: 0x0000. */
Please keep the limit of 80 columns as described in the coding guidelines at https://docs.kernel.org/process/coding-style.html#breaking-long-lines-and-st... This comment applies to the entire patch set.
I think it's easier to interpret the QCOM_TEE prefix as QCOMTEE. I wouldn't mind QTEE as a prefix and the name of the driver if that's more convenient.
+#define QCOM_TEE_ASYNC_VERSION_1_1 0x00010001U /* Major: 0x0001, Minor: 0x0001. */ +#define QCOM_TEE_ASYNC_VERSION_1_2 0x00010002U /* Major: 0x0001, Minor: 0x0002. */ +#define QCOM_TEE_ASYNC_VERSION QCOM_TEE_ASYNC_VERSION_1_2 /* Current Version. */
+#define QCOM_TEE_ASYNC_VERSION_MAJOR(n) upper_16_bits(n) +#define QCOM_TEE_ASYNC_VERSION_MINOR(n) lower_16_bits(n)
+/**
- struct qcom_tee_async_msg_hdr - Asynchronous message header format.
- @version: current async protocol version of remote endpoint
- @op: async operation
- @version specifies the endpoints (QTEE or driver) supported async protocol, e.g.
- if QTEE set @version to %QCOM_TEE_ASYNC_VERSION_1_1, QTEE handles operations
- supported in %QCOM_TEE_ASYNC_VERSION_1_1 or %QCOM_TEE_ASYNC_VERSION_1_0.
- @op determins the message format.
- */
+struct qcom_tee_async_msg_hdr {
u32 version;
u32 op;
+};
+/**
- struct qcom_tee_async_release_msg - Release asynchronous message.
- @hdr: message header as &struct qcom_tee_async_msg_hdr
- @counts: number of objects in @object_ids
- @object_ids: array of object ids should be released
- Available in Major = 0x0001, Minor >= 0x0000.
- */
+struct qcom_tee_async_release_msg {
struct qcom_tee_async_msg_hdr hdr;
u32 counts;
u32 object_ids[] __counted_by(counts);
+};
+/**
- qcom_tee_get_async_buffer() - Get start of the asynchronous message in outbound buffer.
- @oic: context used for current invocation
- @async_buffer: return buffer to extract from or fill in async messages
- If @oic is used for direct object invocation, whole outbound buffer is available for
- async message. If @oic is used for callback request, the tail of outbound buffer (after
- the callback request message) is available for async message.
- */
+static void qcom_tee_get_async_buffer(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_buffer *async_buffer)
+{
struct qcom_tee_msg_callback *msg;
unsigned int offset;
int i;
if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) {
/* The outbound buffer is empty. Using the whole buffer. */
offset = 0;
} else {
msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
/* Start offset in a message for buffer arguments. */
offset = qcom_tee_msg_buffer_args(struct qcom_tee_msg_callback,
qcom_tee_msg_args(msg));
/* Add size of IB arguments. */
qcom_tee_msg_for_each_input_buffer(i, msg)
offset += qcom_tee_msg_offset_align(msg->args[i].b.size);
/* Add size of OB arguments. */
qcom_tee_msg_for_each_output_buffer(i, msg)
offset += qcom_tee_msg_offset_align(msg->args[i].b.size);
}
async_buffer->addr = oic->out_msg.addr + offset;
async_buffer->size = oic->out_msg.size - offset;
+}
+/**
- qcom_tee_async_release_handler() - Process QTEE async requests for releasing objects.
- @oic: context used for current invocation
- @msg: async message for object release
- @size: size of the async buffer available
- Return: Size of outbound buffer used when processing @msg.
- */
+static size_t qcom_tee_async_release_handler(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_async_msg_hdr *async_msg, size_t size)
+{
struct qcom_tee_async_release_msg *msg = (struct qcom_tee_async_release_msg *)async_msg;
struct qcom_tee_object *object;
int i;
for (i = 0; i < msg->counts; i++) {
object = qcom_tee_idx_erase(msg->object_ids[i]);
qcom_tee_object_put(object);
}
return struct_size_t(struct qcom_tee_async_release_msg, object_ids, i);
+}
+/**
- qcom_tee_fetch_async_reqs() - Fetch and process asynchronous messages.
- @oic: context used for current invocation
- It looks for handler to process the requested operations in the async message.
- Currently, only support async release requests.
- */
+void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic) +{
struct qcom_tee_async_msg_hdr *async_msg;
struct qcom_tee_buffer async_buffer;
size_t consumed, used = 0;
qcom_tee_get_async_buffer(oic, &async_buffer);
while (async_buffer.size - used > sizeof(struct qcom_tee_async_msg_hdr)) {
async_msg = (struct qcom_tee_async_msg_hdr *)(async_buffer.addr + used);
if (QCOM_TEE_ASYNC_VERSION_MAJOR(async_msg->version) !=
QCOM_TEE_ASYNC_VERSION_MAJOR(QCOM_TEE_ASYNC_VERSION))
goto out;
switch (async_msg->op) {
case QCOM_TEE_MSG_OBJECT_OP_RELEASE:
consumed = qcom_tee_async_release_handler(oic, async_msg,
async_buffer.size - used);
break;
default:
/* Unsupported operations. */
goto out;
}
/* Supported operation but unable to parse the message. */
if (!consumed)
goto out;
used += qcom_tee_msg_offset_align(consumed);
}
- out:
/* Reset the async messages buffer so async requests do not loopback to QTEE. */
memzero_explicit(async_buffer.addr, async_buffer.size);
+} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c new file mode 100644 index 000000000000..a949ef4cceee --- /dev/null +++ b/drivers/tee/qcomtee/core.c @@ -0,0 +1,928 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/init.h> +#include <linux/kobject.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/xarray.h>
+#include "qcomtee_msg.h" +#include "qcomtee_private.h"
+/* Static instance of object represents QTEE root object. */ +struct qcom_tee_object qcom_tee_object_root = {
.name = "root",
.object_type = QCOM_TEE_OBJECT_TYPE_ROOT,
.info.qtee_id = QCOM_TEE_MSG_OBJECT_ROOT,
+}; +EXPORT_SYMBOL_GPL(qcom_tee_object_root);
+/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type) +{
while (u[i].type != QCOM_TEE_ARG_TYPE_INV && u[i].type != type)
i++;
return i;
+}
+/* QTEE expects IDs with QCOM_TEE_MSG_OBJECT_NS_BIT set for object of
- QCOM_TEE_OBJECT_TYPE_CB_OBJECT type.
- */
+#define QCOM_TEE_OBJECT_ID_START (QCOM_TEE_MSG_OBJECT_NS_BIT + 1) +#define QCOM_TEE_OBJECT_ID_END (UINT_MAX)
+#define QCOM_TEE_OBJECT_SET(p, type, ...) __QCOM_TEE_OBJECT_SET(p, type, ##__VA_ARGS__, 0UL) +#define __QCOM_TEE_OBJECT_SET(p, type, optr, ...) do { \
(p)->object_type = (type); \
(p)->info.qtee_id = (unsigned long)(optr); \
} while (0)
+static struct qcom_tee_object *qcom_tee_object_alloc(void) +{
struct qcom_tee_object *object;
object = kzalloc(sizeof(*object), GFP_KERNEL);
if (object) {
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL);
kref_init(&object->refcount);
}
return object;
+}
+void qcom_tee_object_free(struct qcom_tee_object *object) +{
kfree(object->name);
kfree(object);
+}
+static void qcom_tee_object_release(struct kref *refcount) +{
struct qcom_tee_object *object;
struct module *owner;
const char *name;
object = container_of(refcount, struct qcom_tee_object, refcount);
synchronize_rcu();
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_TEE:
qcom_tee_release_tee_object(object);
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
/* Copy, as after release we should not access object. */
name = object->name;
owner = object->owner;
if (object->ops->release)
object->ops->release(object);
module_put(owner);
kfree_const(name);
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_NULL:
default:
break;
}
+}
+/**
- qcom_tee_object_get() - Increase object's refcount.
- @object: object to increase the refcount
- */
+int qcom_tee_object_get(struct qcom_tee_object *object) +{
if (object != NULL_QCOM_TEE_OBJECT &&
object != ROOT_QCOM_TEE_OBJECT)
return kref_get_unless_zero(&object->refcount);
return 0;
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_get);
+/**
- qcom_tee_object_put() - Decrease object's refcount
- @object: object to decrease the refcount
- */
+void qcom_tee_object_put(struct qcom_tee_object *object) +{
if (object != NULL_QCOM_TEE_OBJECT &&
object != ROOT_QCOM_TEE_OBJECT)
kref_put(&object->refcount, qcom_tee_object_release);
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_put);
+/* ''Local Object Table''. */ +/* Object from kernel that are exported to QTEE are assigned an id and stored in
- xa_qcom_local_objects (kernel object table). QTEE uses this id to reference the
- object using qcom_tee_local_object_get.
- */
+static DEFINE_XARRAY_ALLOC(xa_qcom_local_objects);
We would store this in struct optee in the OP-TEE backend driver. It's not clear to me how this variable is freed when the driver is unloaded. Would it make sense to introduce a struct qcomtee to hold the driver state?
+static int qcom_tee_idx_alloc(u32 *idx, struct qcom_tee_object *object) +{
static u32 xa_last_id = QCOM_TEE_OBJECT_ID_START;
/* Every id allocated here, has QCOM_TEE_MSG_OBJECT_NS_BIT set. */
return xa_alloc_cyclic(&xa_qcom_local_objects, idx, object,
XA_LIMIT(QCOM_TEE_OBJECT_ID_START, QCOM_TEE_OBJECT_ID_END),
&xa_last_id, GFP_KERNEL);
+}
+struct qcom_tee_object *qcom_tee_idx_erase(u32 idx) +{
if (idx < QCOM_TEE_OBJECT_ID_START || idx > QCOM_TEE_OBJECT_ID_END)
return NULL_QCOM_TEE_OBJECT;
return xa_erase(&xa_qcom_local_objects, idx);
+}
+/**
- qcom_tee_object_id_get() - Get an id for an object to sent to QTEE.
- @object: object to get its id.
- @object_id: object id.
- For object hosted in REE, they are added to object table, and the idx in the
- object table is used as id. For object hosted in QTEE, use the QTEE id stored in
- @object. This is called on a path to QTEE to construct a message, see
- qcom_tee_prepare_msg() and qcom_tee_update_msg().
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_id_get(struct qcom_tee_object *object, unsigned int *object_id) +{
u32 idx;
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
if (qcom_tee_idx_alloc(&idx, object) < 0)
return -ENOSPC;
*object_id = idx;
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_TEE:
*object_id = object->info.qtee_id;
break;
case QCOM_TEE_OBJECT_TYPE_NULL:
*object_id = QCOM_TEE_MSG_OBJECT_NULL;
break;
}
return 0;
+}
+/* Release object id assigned in qcom_tee_object_id_get. */ +static void qcom_tee_object_id_put(unsigned int object_id) +{
qcom_tee_idx_erase(object_id);
+}
+/**
- qcom_tee_local_object_get() - Get an object in REE referenced by the id.
- @object_id: object id.
- It is called on behalf of QTEE to obtain instance of object for an id. It is
- called on a path from QTEE to construct an argument of &struct qcom_tee_arg,
- see qcom_tee_update_args() and qcom_tee_prepare_args().
- It increases the object's refcount on success.
- Return: On error returns %NULL_QCOM_TEE_OBJECT. On success, the object.
- */
+static struct qcom_tee_object *qcom_tee_local_object_get(unsigned int object_id) +{
struct qcom_tee_object *object;
/* We trust QTEE does not mess the refcounts.
* It does not issue RELEASE request and qcom_tee_object_get(), simultaneously.
*/
What does this comment mean? That QTEE shouldn't get and release an object in one request or that QTEE is trusted to keep the get and release in balance?
Please use the preferred style for multi-line comments https://docs.kernel.org/process/coding-style.html#commenting This comment applies to the entire patch set.
object = xa_load(&xa_qcom_local_objects, object_id);
qcom_tee_object_get(object);
return object;
+}
+/**
- __qcom_tee_object_user_init() - Initialize an object for user.
- @object: object to initialize.
- @ot: type of object as &enum qcom_tee_object_type.
- @ops: instance of callbacks.
- @fmt: name assigned to the object.
- Return: On success return 0 or <0 on failure.
- */
+int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot,
struct qcom_tee_object_operations *ops, struct module *owner,
const char *fmt, ...)
+{
va_list ap;
int ret;
kref_init(&object->refcount);
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL);
va_start(ap, fmt);
switch (ot) {
case QCOM_TEE_OBJECT_TYPE_NULL:
ret = 0;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
object->ops = ops;
if (!object->ops->dispatch)
return -EINVAL;
object->owner = owner;
if (!try_module_get(object->owner))
return -EINVAL;
/* If failed, "no-name"; it is not really a reason to fail here. */
object->name = kvasprintf_const(GFP_KERNEL, fmt, ap);
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT);
ret = 0;
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_TEE:
default:
ret = -EINVAL;
}
va_end(ap);
return ret;
+} +EXPORT_SYMBOL_GPL(__qcom_tee_object_user_init);
+/**
- qcom_tee_object_type() - Returns type of object represented by an object id.
- @object_id: object id for the object.
- This is similar to typeof_qcom_tee_object() but instead of receiving object
- as argument it receives object id. It is used internally on return path
- from QTEE.
- Return: Returns type of object referenced by @object_id.
- */
+static enum qcom_tee_object_type qcom_tee_object_type(unsigned int object_id) +{
if (object_id == QCOM_TEE_MSG_OBJECT_NULL)
return QCOM_TEE_OBJECT_TYPE_NULL;
if (object_id & QCOM_TEE_MSG_OBJECT_NS_BIT)
return QCOM_TEE_OBJECT_TYPE_CB_OBJECT;
return QCOM_TEE_OBJECT_TYPE_TEE;
+}
+/**
- qcom_tee_object_init() - Initialize an object for QTEE.
- @object: return object
- @object_id: object id received form QTEE
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_init(struct qcom_tee_object **object, unsigned int object_id) +{
struct qcom_tee_object *qto;
int ret = 0;
switch (qcom_tee_object_type(object_id)) {
case QCOM_TEE_OBJECT_TYPE_NULL:
*object = NULL_QCOM_TEE_OBJECT;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
qto = qcom_tee_local_object_get(object_id);
if (qto != NULL_QCOM_TEE_OBJECT)
*object = qto;
else
ret = -EINVAL;
break;
case QCOM_TEE_OBJECT_TYPE_TEE:
qto = qcom_tee_object_alloc();
if (qto) {
/* If failed, "no-name"; it is not really a reason to fail here. */
qto->name = kasprintf(GFP_KERNEL, "qcom_tee-%u", object_id);
QCOM_TEE_OBJECT_SET(qto, QCOM_TEE_OBJECT_TYPE_TEE, object_id);
*object = qto;
} else {
ret = -ENOMEM;
}
break;
default:
break;
}
if (ret)
*object = NULL_QCOM_TEE_OBJECT;
return ret;
+}
+/* Marshaling API. */ +/* qcom_tee_prepare_msg - Prepares inbound buffer for sending to QTEE
- qcom_tee_update_args - Parses QTEE response in inbound buffer
- qcom_tee_prepare_args - Parses QTEE request from outbound buffer
- qcom_tee_update_msg - Updates outbound buffer with response for QTEE request
- */
Please combine the two comments into one.
+static int qcom_tee_prepare_msg(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u)
+{
struct qcom_tee_msg_object_invoke *msg;
unsigned int object_id;
int ib, ob, io, oo;
size_t off;
int i;
/* Use input message buffer in 'oic'. */
msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr;
/* Start offset in a message for buffer arguments. */
off = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u));
/* Get id of object being invoked. */
if (qcom_tee_object_id_get(object, &object_id))
return -ENOSPC;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, u) {
void *ptr;
/* qcom_tee_msg_buffers_alloc() already checked overflow in message! */
msg->args[ib].b.offset = off;
msg->args[ib].b.size = u[i].b.size;
ptr = qcom_tee_msg_offset_to_ptr(msg, off);
if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR))
memcpy(ptr, u[i].b.addr, u[i].b.size);
else if (copy_from_user(ptr, u[i].b.uaddr, u[i].b.size))
return -EINVAL;
off += qcom_tee_msg_offset_align(u[i].b.size);
ib++;
}
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, u) {
/* qcom_tee_msg_buffers_alloc() already checked overflow in message! */
msg->args[ob].b.offset = off;
msg->args[ob].b.size = u[i].b.size;
off += qcom_tee_msg_offset_align(u[i].b.size);
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, u) {
if (qcom_tee_object_id_get(u[i].o, &msg->args[io].o)) {
/* Unable to qcom_tee_object_id_get; put whatever we got. */
qcom_tee_object_id_put(object_id);
for (--io; io >= ob; io--)
Please avoid the prefix increment or decrement operator and use the postfix increment or decrement unless there's no other option (use io-- instead of --io). This comment applies to the entire patch set.
qcom_tee_object_id_put(msg->args[io].o);
return -ENOSPC;
}
io++;
}
oo = io;
qcom_tee_arg_for_each_output_object(i, u)
oo++;
/* Set object, operation, and argument counts. */
count
qcom_tee_msg_init(msg, object_id, op, ib, ob, io, oo);
return 0;
+}
+static int qcom_tee_update_args(struct qcom_tee_arg *u, struct qcom_tee_object_invoke_ctx *oic) +{
struct qcom_tee_msg_object_invoke *msg;
int ib, ob, io, oo;
int i, ret = 0;
/* Use input message buffer in 'oic'. */
msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, u)
ib++;
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, u) {
void *ptr = qcom_tee_msg_offset_to_ptr(msg, msg->args[ob].b.offset);
if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR)) {
memcpy(u[i].b.addr, ptr, msg->args[ob].b.size);
} else if (copy_to_user(u[i].b.uaddr, ptr, msg->args[ob].b.size)) {
/* On ERROR, continue to process arguments to get to output object. */
ret = -EINVAL;
}
u[i].b.size = msg->args[ob].b.size;
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, u)
io++;
oo = io;
qcom_tee_arg_for_each_output_object(i, u) {
int err;
/* On ERROR, continue to process arguments so that we can issue the RELEASE. */
err = qcom_tee_object_init(&u[i].o, msg->args[oo].o);
if (err)
ret = err;
oo++;
}
return ret;
+}
+static int qcom_tee_prepare_args(struct qcom_tee_object_invoke_ctx *oic) +{
int i, ret = 0;
/* Use output message buffer in 'oic'. */
struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
qcom_tee_msg_for_each_input_buffer(i, msg) {
oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset);
oic->u[i].b.size = msg->args[i].b.size;
oic->u[i].type = QCOM_TEE_ARG_TYPE_IB;
}
qcom_tee_msg_for_each_output_buffer(i, msg) {
oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset);
oic->u[i].b.size = msg->args[i].b.size;
oic->u[i].type = QCOM_TEE_ARG_TYPE_OB;
}
qcom_tee_msg_for_each_input_object(i, msg) {
int err;
/* On ERROR, continue to process arguments so that we can issue the RELEASE. */
err = qcom_tee_object_init(&oic->u[i].o, msg->args[i].o);
if (err)
ret = err;
oic->u[i].type = QCOM_TEE_ARG_TYPE_IO;
}
qcom_tee_msg_for_each_output_object(i, msg)
oic->u[i].type = QCOM_TEE_ARG_TYPE_OO;
/* End of Arguments. */
oic->u[i].type = QCOM_TEE_ARG_TYPE_INV;
return ret;
+}
+static int qcom_tee_update_msg(struct qcom_tee_object_invoke_ctx *oic) +{
int ib, ob, io, oo;
int i;
/* Use output message buffer in 'oic'. */
struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, oic->u)
ib++;
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, oic->u) {
/* Only reduce size; never increase it. */
if (msg->args[ob].b.size < oic->u[i].b.size)
return -EINVAL;
msg->args[ob].b.size = oic->u[i].b.size;
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, oic->u)
io++;
oo = io;
qcom_tee_arg_for_each_output_object(i, oic->u) {
if (qcom_tee_object_id_get(oic->u[i].o, &msg->args[oo].o)) {
/* Unable to qcom_tee_object_id_get; put whatever we got. */
for (--oo; oo >= io; --oo)
qcom_tee_object_id_put(msg->args[oo].o);
return -ENOSPC;
}
oo++;
}
return 0;
+}
+/**
- define MAX_BUFFER_SIZE - Maximum size of inbound and outbound buffers.
- QTEE transport does not impose any restriction on these buffers. However, if size of
- buffers are larger then %MAX_BUFFER_SIZE, user should probably use some other
- form of shared memory with QTEE.
- */
+#define MAX_BUFFER_SIZE SZ_8K
+/* Pool to allocate inbound and outbound buffers. */ +static struct qcom_tzmem_pool */;
+static int qcom_tee_msg_buffers_alloc(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_arg *u)
+{
size_t size;
int i;
/* Start offset in a message for buffer arguments. */
size = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
/* Add size of IB arguments. */
qcom_tee_arg_for_each_input_buffer(i, u) {
size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
}
/* Add size of OB arguments. */
qcom_tee_arg_for_each_output_buffer(i, u) {
size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
}
/* QTEE requires inbound buffer size to be page aligned. */
size = PAGE_ALIGN(size);
/* Do allocations. */
oic->in_msg.size = size;
oic->in_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, size, GFP_KERNEL);
if (!oic->in_msg.addr)
return -EINVAL;
oic->out_msg.size = MAX_BUFFER_SIZE;
oic->out_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, MAX_BUFFER_SIZE, GFP_KERNEL);
if (!oic->out_msg.addr) {
qcom_tzmem_free(oic->in_msg.addr);
return -EINVAL;
}
oic->in_msg_paddr = qcom_tzmem_to_phys(oic->in_msg.addr);
oic->out_msg_paddr = qcom_tzmem_to_phys(oic->out_msg.addr);
/* QTEE assume unused buffers are zeroed; Do it now! */
memzero_explicit(oic->in_msg.addr, oic->in_msg.size);
memzero_explicit(oic->out_msg.addr, oic->out_msg.size);
return 0;
+}
+static void qcom_tee_msg_buffers_free(struct qcom_tee_object_invoke_ctx *oic) +{
qcom_tzmem_free(oic->in_msg.addr);
qcom_tzmem_free(oic->out_msg.addr);
+}
+static int qcom_tee_msg_buffers_init(void) +{
struct qcom_tzmem_pool_config config = {
.policy = QCOM_TZMEM_POLICY_ON_DEMAND,
/* 4M seems enough, it is used for QTEE meg header and qcom_tee_msg_arg array. */
.max_size = SZ_4M
};
tzmem_msg_pool = qcom_tzmem_pool_new(&config);
if (IS_ERR(tzmem_msg_pool))
return PTR_ERR(tzmem_msg_pool);
return 0;
+}
+static void qcom_tee_msg_buffers_destroy(void) +{
qcom_tzmem_pool_free(tzmem_msg_pool);
Can tzmem_msg_pool become a dangling pointer if the driver is unbound?
+}
+/* Invoke a REE object. */ +static void qcom_tee_object_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_msg_callback *msg)
+{
int i, errno;
u32 op;
/* Get object being invoked. */
unsigned int object_id = msg->cxt;
struct qcom_tee_object *object;
/* QTEE can not invoke NULL object or objects it hosts. */
if (qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_NULL ||
qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_TEE) {
errno = -EINVAL;
goto out;
}
object = qcom_tee_local_object_get(object_id);
if (object == NULL_QCOM_TEE_OBJECT) {
errno = -EINVAL;
goto out;
}
oic->object = object;
/* Filter bits used by transport. */
op = msg->op & QCOM_TEE_MSG_OBJECT_OP_MASK;
switch (op) {
case QCOM_TEE_MSG_OBJECT_OP_RELEASE:
qcom_tee_object_id_put(object_id);
qcom_tee_object_put(object);
errno = 0;
break;
case QCOM_TEE_MSG_OBJECT_OP_RETAIN:
qcom_tee_object_get(object);
errno = 0;
break;
default:
errno = qcom_tee_prepare_args(oic);
if (errno) {
/* Unable to parse the message. Release any object arrived as input. */
that arrived
qcom_tee_arg_for_each_input_buffer(i, oic->u)
qcom_tee_object_put(oic->u[i].o);
break;
}
errno = object->ops->dispatch(oic, object, op, oic->u);
if (!errno) {
/* On SUCCESS, notify object at appropriate time. */
oic->flags |= QCOM_TEE_OIC_FLAG_NOTIFY;
}
}
+out:
oic->errno = errno;
+}
+/**
- __qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
arguments
- @result: result returned from QTEE.
- The caller is responsible to keep track of the refcount for each object,
- including @object. On return, the caller loses the ownership of all input
- object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user may retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result)
+{
struct qcom_tee_msg_callback *cb_msg;
u64 response_type;
int i, ret, errno;
ret = qcom_tee_msg_buffers_alloc(oic, u);
if (ret)
return ret;
ret = qcom_tee_prepare_msg(oic, object, op, u);
if (ret)
goto out;
cb_msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
while (1) {
if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) {
errno = oic->errno;
/* Update output buffer only if result is SUCCESS. */
if (!errno)
errno = qcom_tee_update_msg(oic);
qcom_tee_msg_translate_err(cb_msg, errno);
}
/* Invoke remote object. */
ret = qcom_tee_object_invoke_ctx_invoke(oic, result, &response_type);
if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) {
struct qcom_tee_object *qto = oic->object;
if (qto) {
if (oic->flags & QCOM_TEE_OIC_FLAG_NOTIFY) {
if (qto->ops->notify)
qto->ops->notify(oic, qto, errno || ret);
}
/* Matching get is in qcom_tee_object_invoke. */
qcom_tee_object_put(qto);
}
oic->object = NULL_QCOM_TEE_OBJECT;
oic->flags &= ~(QCOM_TEE_OIC_FLAG_BUSY | QCOM_TEE_OIC_FLAG_NOTIFY);
}
if (ret) {
if (!(oic->flags & QCOM_TEE_OIC_FLAG_SHARED)) {
/* Release QCOM_TEE_OBJECT_TYPE_CB_OBJECT input objects. */
qcom_tee_arg_for_each_input_object(i, u)
if (typeof_qcom_tee_object(u[i].o) ==
QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(u[i].o);
ret = -EAGAIN;
} else {
/* On error, there is no clean way to exit. */
/* For some reason we can not communicate with QTEE, so we can not
* notify QTEE about the failure and do further cleanup.
*/
ret = -ENODEV;
}
goto out;
} else {
/* QTEE obtained the ownership of QCOM_TEE_OBJECT_TYPE_CB_OBJECT
* input objects in 'u'. On further failure, QTEE is responsible
* to release them.
*/
oic->flags |= QCOM_TEE_OIC_FLAG_SHARED;
}
/* Is it a callback request? */
+#define QCOM_TEE_RESULT_INBOUND_REQ_NEEDED 3
This must be part of the ABI to the secure world. How about keeping all those defines in a central place?
if (response_type != QCOM_TEE_RESULT_INBOUND_REQ_NEEDED) {
if (!*result) {
ret = qcom_tee_update_args(u, oic);
if (ret) {
qcom_tee_arg_for_each_output_object(i, u)
qcom_tee_object_put(u[i].o);
ret = -EAGAIN;
}
}
break;
} else {
oic->flags |= QCOM_TEE_OIC_FLAG_BUSY;
/* Before dispatching the request, handle any pending async requests. */
qcom_tee_fetch_async_reqs(oic);
qcom_tee_object_invoke(oic, cb_msg);
This looks like a recursion. Is there a limit on how deep it can become?
}
}
qcom_tee_fetch_async_reqs(oic);
+out:
qcom_tee_msg_buffers_free(oic);
return ret;
+}
+int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result)
+{
/* User can not set bits used by transport. */
if (op & ~QCOM_TEE_MSG_OBJECT_OP_MASK)
return -EINVAL;
/* User can only invoke QTEE hosted objects. */
if (typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_TEE &&
typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_ROOT)
return -EINVAL;
/* User can not issue reserved operations to QTEE. */
if (op == QCOM_TEE_MSG_OBJECT_OP_RELEASE || op == QCOM_TEE_MSG_OBJECT_OP_RETAIN)
return -EINVAL;
return __qcom_tee_object_do_invoke(oic, object, op, u, result);
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_do_invoke);
+/* Dump object table. */ +static ssize_t qcom_tee_object_table_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
+{
struct qcom_tee_object *object;
unsigned long idx;
size_t len = 0;
xa_for_each_start(&xa_qcom_local_objects, idx, object, QCOM_TEE_OBJECT_ID_START) {
len += sysfs_emit_at(buf, len, "%4lx %4d %s\n", idx,
kref_read(&object->refcount),
qcom_tee_object_name(object));
}
return len;
+}
+static struct kobj_attribute object_table = __ATTR_RO(qcom_tee_object_table); +static struct kobj_attribute release = __ATTR_RO(qcom_tee_release_wq); +static struct attribute *attrs[] = {
&object_table.attr,
&release.attr,
NULL
+};
+static struct attribute_group attr_group = {
.attrs = attrs,
+};
+static struct kobject *qcom_tee_object_invoke_kobj; +static int __init qcom_tee_object_invoke_init(void) +{
int ret;
ret = qcom_tee_release_init();
if (ret)
return ret;
ret = qcom_tee_msg_buffers_init();
if (ret)
goto err_release_destroy;
/* Create '/sys/firmware/qcom_tee'. */
qcom_tee_object_invoke_kobj = kobject_create_and_add("qcom_tee", firmware_kobj);
I'd expect TEE drivers to keep their stuff under /sys/class/tee/tee*/
if (!qcom_tee_object_invoke_kobj) {
ret = -ENOMEM;
goto err_msg_buffers_destroy;
}
/* Create 'qcom_tee_object_table' and 'qcom_tee_release_wq'. */
ret = sysfs_create_group(qcom_tee_object_invoke_kobj, &attr_group);
if (ret)
goto err_kobject_put;
return 0;
+err_kobject_put:
/* Remove '/sys/firmware/qcom_tee'. */
kobject_put(qcom_tee_object_invoke_kobj);
+err_msg_buffers_destroy:
qcom_tee_msg_buffers_destroy();
+err_release_destroy:
qcom_tee_release_destroy();
return ret;
+} +module_init(qcom_tee_object_invoke_init);
The other TEE drivers do most of the initialization work in the probe function and only the bare minimum for the probe function to be called before. Wouldn't it make sense to try to use the same pattern in this driver? It helps when trying to understand the driver, and perhaps a few bugs can be avoided too when following an established pattern.
+static void __exit qcom_tee_object_invoke_deinit(void) +{
/* Wait for RELEASE operations for QTEE objects. */
qcom_tee_release_destroy();
qcom_tee_msg_buffers_destroy();
sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group);
kobject_put(qcom_tee_object_invoke_kobj);
+} +module_exit(qcom_tee_object_invoke_deinit);
+MODULE_AUTHOR("Qualcomm"); +MODULE_DESCRIPTION("QTEE driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/qcomtee/qcom_scm.c b/drivers/tee/qcomtee/qcom_scm.c new file mode 100644 index 000000000000..230faf249095 --- /dev/null +++ b/drivers/tee/qcomtee/qcom_scm.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include <linux/firmware/qcom/qcom_scm.h>
+#include "qcomtee_private.h"
+int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic,
int *result, u64 *response_type)
+{
int ret;
u64 res;
if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) {
/* Direct QTEE object invocation. */
ret = qcom_scm_qtee_invoke_smc(oic->in_msg_paddr,
oic->in_msg.size,
oic->out_msg_paddr,
oic->out_msg.size,
&res, response_type, NULL);
} else {
/* Submit callback response. */
ret = qcom_scm_qtee_callback_response(oic->out_msg_paddr,
oic->out_msg.size,
&res, response_type, NULL);
}
if (ret)
pr_err("QTEE returned with %d.\n", ret);
else
*result = (int)res;
return ret;
+} diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h new file mode 100644 index 000000000000..7c968834ec9d --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef QCOMTEE_MSG_H +#define QCOMTEE_MSG_H
+#include <linux/firmware/qcom/qcom_tee.h>
+/**
- DOC: ''Qualcomm TEE'' (QTEE) Transport Message
- There are two buffers shared with QTEE, inbound and outbound buffers.
- The inbound buffer is used for direct object invocation and the outbound buffer is
- used to make a request from QTEE to kernel, i.e. callback request.
- The unused tail of the outbound buffer is also used for sending and receiving
- asynchronous messages. An asynchronous message is independent from the current
- object invocation (i.e. contents of the inbound buffer) or callback request
- (i.e. the head of the outbound buffer), see qcom_tee_get_async_buffer(). It is
- used by endpoints (QTEE or kernel) as an optimization to reduce number of context
- switches between secure and non-secure world.
- For instance, QTEE never sends an explicit callback request to release an object in
- kernel. Instead, it sends asynchronous release messages in outbound buffer when QTEE
- returns from previous direct object invocation, or append asynchronous release
- messages after the current callback request.
- QTEE supports two types of arguments in a message: buffer and object arguments.
- Depending on the direction of data flow, they could be input buffer (IO) to QTEE,
- output buffer (OB) from QTEE, input object (IO) to QTEE, or output object (OO) from
- QTEE. Object arguments hold object ids. Buffer arguments hold (offset, size) pairs
- into the inbound or outbound buffers.
- QTEE holds an object table for objects, it hosts and exposes to kernel. An object id
- is an index to the object table in QTEE.
- For direct object invocation message format in inbound buffer see
- &struct qcom_tee_msg_object_invoke. For callback request message format in outbound
- buffer see &struct qcom_tee_msg_callback. For the message format for asynchronous message
- in outbound buffer see &struct qcom_tee_async_msg_hdr.
- */
+/**
- define QCOM_TEE_MSG_OBJECT_NS_BIT - Non-secure bit
- Object id is a globally unique 32-bit number. Ids referencing objects in kernel should
- have %QCOM_TEE_MSG_OBJECT_NS_BIT set.
- */
+#define QCOM_TEE_MSG_OBJECT_NS_BIT BIT(31)
+/* Static object ids recognized by QTEE. */ +#define QCOM_TEE_MSG_OBJECT_NULL (0U) +#define QCOM_TEE_MSG_OBJECT_ROOT (1U)
+/* Definitions from QTEE as part of the transport protocol. */
+/* qcom_tee_msg_arg is argument as recognized by QTEE. */ +union qcom_tee_msg_arg {
struct {
u32 offset;
u32 size;
} b;
u32 o;
+};
+/* BI and BO payloads in a QTEE messages should be at 64-bit boundaries. */ +#define qcom_tee_msg_offset_align(o) ALIGN((o), sizeof(u64))
+/* Operation for objects is 32-bit. Transport uses upper 16-bits internally. */ +#define QCOM_TEE_MSG_OBJECT_OP_MASK 0x0000FFFFU
GENMASK()?
Cheers, Jens
+/* Reserved Operation IDs sent to QTEE: */ +/* QCOM_TEE_MSG_OBJECT_OP_RELEASE - Reduces the refcount and releases the object.
- QCOM_TEE_MSG_OBJECT_OP_RETAIN - Increases the refcount.
- These operation id are valid for all objects. They are not available outside of this
- driver. Developers should use qcom_tee_object_get() and qcom_tee_object_put(), to
- achieve the same.
- */
+#define QCOM_TEE_MSG_OBJECT_OP_RELEASE (QCOM_TEE_MSG_OBJECT_OP_MASK - 0) +#define QCOM_TEE_MSG_OBJECT_OP_RETAIN (QCOM_TEE_MSG_OBJECT_OP_MASK - 1)
+/**
- struct qcom_tee_msg_object_invoke - Direct object invocation message
- @ctx: object id hosted in QTEE
- @op: operation for the object
- @counts: number of different type of arguments in @args
- @args: array of arguments
- @counts consists of 4 * 4-bits felids. Bits 0 - 3, is number of input buffers,
- bits 4 - 7, is number of output buffers, bits 8 - 11, is number of input objects,
- and bits 12 - 15, is number of output objects. Remaining bits should be zero.
- Maximum number of arguments of each type is defined by %QCOM_TEE_ARGS_PER_TYPE.
- */
+struct qcom_tee_msg_object_invoke {
u32 cxt;
u32 op;
u32 counts;
union qcom_tee_msg_arg args[];
+};
+/**
- struct qcom_tee_msg_callback - Callback request message
- @result: result of operation @op on object referenced by @cxt
- @cxt: object id hosted in kernel
- @op: operation for the object
- @counts: number of different type of arguments in @args
- @args: array of arguments
- For details of @counts, see &qcom_tee_msg_object_invoke.counts.
- */
+struct qcom_tee_msg_callback {
u32 result;
u32 cxt;
u32 op;
u32 counts;
union qcom_tee_msg_arg args[];
+};
+/* Offset in the message for the beginning of buffer argument's contents. */ +#define qcom_tee_msg_buffer_args(t, n) \
qcom_tee_msg_offset_align(struct_size_t(t, args, n))
+/* Pointer to the beginning of a buffer argument's content at an offset in a message. */ +#define qcom_tee_msg_offset_to_ptr(m, off) ((void *)&((char *)(m))[(off)])
+/* Some helpers to manage msg.counts. */
+#define QCOM_TEE_MSG_NUM_IB(x) ((x) & 0xfU) +#define QCOM_TEE_MSG_NUM_OB(x) (((x) >> 4) & 0xfU) +#define QCOM_TEE_MSG_NUM_IO(x) (((x) >> 8) & 0xfU) +#define QCOM_TEE_MSG_NUM_OO(x) (((x) >> 12) & 0xfU)
+#define QCOM_TEE_MSG_IDX_IB(x) (0U) +#define QCOM_TEE_MSG_IDX_OB(x) (QCOM_TEE_MSG_IDX_IB(x) + QCOM_TEE_MSG_NUM_IB(x)) +#define QCOM_TEE_MSG_IDX_IO(x) (QCOM_TEE_MSG_IDX_OB(x) + QCOM_TEE_MSG_NUM_OB(x)) +#define QCOM_TEE_MSG_IDX_OO(x) (QCOM_TEE_MSG_IDX_IO(x) + QCOM_TEE_MSG_NUM_IO(x))
+#define qcom_tee_msg_for_each(i, c, type) \
for (i = QCOM_TEE_MSG_IDX_##type(c); \
i < (QCOM_TEE_MSG_IDX_##type(c) + QCOM_TEE_MSG_NUM_##type(c)); \
i++)
+#define qcom_tee_msg_for_each_input_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, IB) +#define qcom_tee_msg_for_each_output_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, OB) +#define qcom_tee_msg_for_each_input_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, IO) +#define qcom_tee_msg_for_each_output_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, OO)
+/* Sum of arguments in a message. */ +#define qcom_tee_msg_args(m) (QCOM_TEE_MSG_IDX_OO((m)->counts) + QCOM_TEE_MSG_NUM_OO((m)->counts))
+static inline void qcom_tee_msg_init(struct qcom_tee_msg_object_invoke *msg, u32 cxt, u32 op,
int in_buffer, int out_buffer, int in_object, int out_object)
+{
msg->counts |= (in_buffer & 0xfU);
msg->counts |= ((out_buffer - in_buffer) & 0xfU) << 4;
msg->counts |= ((in_object - out_buffer) & 0xfU) << 8;
msg->counts |= ((out_object - in_object) & 0xfU) << 12;
msg->cxt = cxt;
msg->op = op;
+}
+/* Generic error codes. */ +#define QCOM_TEE_MSG_OK 0 /* non-specific success code. */ +#define QCOM_TEE_MSG_ERROR 1 /* non-specific error. */ +#define QCOM_TEE_MSG_ERROR_INVALID 2 /* unsupported/unrecognized request. */ +#define QCOM_TEE_MSG_ERROR_SIZE_IN 3 /* supplied buffer/string too large. */ +#define QCOM_TEE_MSG_ERROR_SIZE_OUT 4 /* supplied output buffer too small. */ +#define QCOM_TEE_MSG_ERROR_USERBASE 10 /* start of user-defined error range. */
+/* Transport layer error codes. */ +#define QCOM_TEE_MSG_ERROR_DEFUNCT -90 /* object no longer exists. */ +#define QCOM_TEE_MSG_ERROR_ABORT -91 /* calling thread must exit. */ +#define QCOM_TEE_MSG_ERROR_BADOBJ -92 /* invalid object context. */ +#define QCOM_TEE_MSG_ERROR_NOSLOTS -93 /* caller's object table full. */ +#define QCOM_TEE_MSG_ERROR_MAXARGS -94 /* too many args. */ +#define QCOM_TEE_MSG_ERROR_MAXDATA -95 /* buffers too large. */ +#define QCOM_TEE_MSG_ERROR_UNAVAIL -96 /* the request could not be processed. */ +#define QCOM_TEE_MSG_ERROR_KMEM -97 /* kernel out of memory. */ +#define QCOM_TEE_MSG_ERROR_REMOTE -98 /* local method sent to remote object. */ +#define QCOM_TEE_MSG_ERROR_BUSY -99 /* Object is busy. */ +#define QCOM_TEE_MSG_ERROR_TIMEOUT -103 /* Call Back Object invocation timed out. */
+static inline void qcom_tee_msg_translate_err(struct qcom_tee_msg_callback *cb_msg, int err) +{
if (!err) {
cb_msg->result = QCOM_TEE_MSG_OK;
} else if (err < 0) {
/* If err < 0, then it is a transport error. */
switch (err) {
case -ENOMEM:
cb_msg->result = QCOM_TEE_MSG_ERROR_KMEM;
break;
case -ENODEV:
cb_msg->result = QCOM_TEE_MSG_ERROR_DEFUNCT;
break;
case -ENOSPC:
case -EBUSY:
cb_msg->result = QCOM_TEE_MSG_ERROR_BUSY;
break;
case -EBADF:
case -EINVAL:
cb_msg->result = QCOM_TEE_MSG_ERROR_UNAVAIL;
break;
default:
cb_msg->result = QCOM_TEE_MSG_ERROR;
}
} else {
/* If err > 0, then it is user defined error, pass it as is. */
cb_msg->result = err;
}
+}
+#endif /* QCOMTEE_MSG_H */ diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h new file mode 100644 index 000000000000..e3e4ef51c0b2 --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef QCOM_TEE_PRIVATE_H +#define QCOM_TEE_PRIVATE_H
+#include <linux/firmware/qcom/qcom_tee.h> +#include <linux/kobject.h> +#include <linux/tee_core.h>
+struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); +void qcom_tee_object_free(struct qcom_tee_object *object);
+/* Process async messages form QTEE. */ +void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic);
+int qcom_tee_release_init(void); +void qcom_tee_release_destroy(void); +void qcom_tee_release_tee_object(struct qcom_tee_object *object); +ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+/* SCM call. */ +int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic,
int *result, u64 *response_type);
+/**
- __qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
- @result: result returned from QTEE.
- Same as qcom_tee_object_do_invoke() without @object and @op is 32-bit,
- upper 16-bits are for internal use.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user can retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result);
+#endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/release.c b/drivers/tee/qcomtee/release.c new file mode 100644 index 000000000000..f2e048418e23 --- /dev/null +++ b/drivers/tee/qcomtee/release.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include "qcomtee_private.h" +#include "qcomtee_msg.h"
+static struct workqueue_struct *qcom_tee_release_wq;
+/* Number of all release requests pending for processing. */ +static atomic_t qcom_tee_pending_releases = ATOMIC_INIT(0);
+/* qcom_tee_object_do_release makes direct object invocation to release an object. */ +static void qcom_tee_destroy_user_object(struct work_struct *work) +{
static struct qcom_tee_object_invoke_ctx oic;
static struct qcom_tee_arg args[1] = { 0 };
struct qcom_tee_object *object;
int ret, result;
object = container_of(work, struct qcom_tee_object, work);
ret = __qcom_tee_object_do_invoke(&oic, object, QCOM_TEE_MSG_OBJECT_OP_RELEASE, args,
&result);
/* Is it safe to retry the release? */
if (ret == -EAGAIN) {
queue_work(qcom_tee_release_wq, &object->work);
} else {
if (ret || result)
pr_err("%s: %s release failed, ret = %d (%x).\n",
__func__, qcom_tee_object_name(object), ret, result);
atomic_dec(&qcom_tee_pending_releases);
qcom_tee_object_free(object);
}
+}
+/* qcom_tee_release_tee_object puts object in release work queue. */ +void qcom_tee_release_tee_object(struct qcom_tee_object *object) +{
INIT_WORK(&object->work, qcom_tee_destroy_user_object);
atomic_inc(&qcom_tee_pending_releases);
queue_work(qcom_tee_release_wq, &object->work);
+}
+ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{
return sysfs_emit(buf, "%d\n", atomic_read(&qcom_tee_pending_releases));
+}
+int qcom_tee_release_init(void) +{
qcom_tee_release_wq = alloc_ordered_workqueue("qcom_tee_release_wq", 0);
if (!qcom_tee_release_wq)
return -ENOMEM;
return 0;
+}
+void qcom_tee_release_destroy(void) +{
/* It drains the wq. */
destroy_workqueue(qcom_tee_release_wq);
+} diff --git a/include/linux/firmware/qcom/qcom_tee.h b/include/linux/firmware/qcom/qcom_tee.h new file mode 100644 index 000000000000..90e5e10a0e62 --- /dev/null +++ b/include/linux/firmware/qcom/qcom_tee.h @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef __QCOM_TEE_H +#define __QCOM_TEE_H
+#include <linux/kref.h> +#include <linux/completion.h> +#include <linux/workqueue.h>
+struct qcom_tee_object;
+/**
- DOC: Overview
- qcom_tee_object provides object ref-counting, id allocation for objects hosted in
- REE, and necessary message marshaling for Qualcomm TEE (QTEE).
- To invoke an object in QTEE, user calls qcom_tee_object_do_invoke() while passing
- an instance of &struct qcom_tee_object and the requested operation + arguments.
- After the boot, QTEE provides a static object %ROOT_QCOM_TEE_OBJECT (type of
- %QCOM_TEE_OBJECT_TYPE_ROOT). The root object is invoked to pass user's credentials and
- obtain other instances of &struct qcom_tee_object (type of %QCOM_TEE_OBJECT_TYPE_TEE)
- that represents services and TAs in QTEE, see &enum qcom_tee_object_type.
- The object received from QTEE are refcounted. So the owner of these objects can
- issue qcom_tee_object_get(), to increase the refcount, and pass objects to other
- clients, or issue qcom_tee_object_put() to decrease the refcount, and releasing
- the resources in QTEE.
- REE can host services accessible to QTEE. A driver should embed an instance of
- &struct qcom_tee_object in the struct it wants to export to QTEE (it is called
- callback object). It issues qcom_tee_object_user_init() to set the dispatch()
- operation for the callback object and set its type to %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- core.c holds an object table for callback objects. An object id is assigned
- to each callback object which is an index to the object table. QTEE uses these ids
- to reference or invoke callback objects.
- If QTEE invoke a callback object in REE, the dispatch() operation is called in the
- context of thread that called qcom_tee_object_do_invoke(), originally.
- */
+/**
- enum qcom_tee_object_typ - Object types.
- @QCOM_TEE_OBJECT_TYPE_TEE: object hosted on QTEE.
- @QCOM_TEE_OBJECT_TYPE_CB_OBJECT: object hosted on REE.
- @QCOM_TEE_OBJECT_TYPE_ROOT: 'primordial' object.
- @QCOM_TEE_OBJECT_TYPE_NULL: NULL object.
- Primordial object is used for bootstrapping the IPC connection between a REE
- and QTEE. It is invoked by REE when it wants to get a 'client env'.
- */
+enum qcom_tee_object_type {
QCOM_TEE_OBJECT_TYPE_TEE,
QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
QCOM_TEE_OBJECT_TYPE_ROOT,
QCOM_TEE_OBJECT_TYPE_NULL,
+};
+/**
- enum qcom_tee_arg_type - Type of QTEE argument.
- @QCOM_TEE_ARG_TYPE_INV: invalid type.
- @QCOM_TEE_ARG_TYPE_IB: input buffer (IO).
- @QCOM_TEE_ARG_TYPE_OO: output object (OO).
- @QCOM_TEE_ARG_TYPE_OB: output buffer (OB).
- @QCOM_TEE_ARG_TYPE_IO: input object (IO).
- Use invalid type to specify end of argument array.
- */
+enum qcom_tee_arg_type {
QCOM_TEE_ARG_TYPE_INV = 0,
QCOM_TEE_ARG_TYPE_OB,
QCOM_TEE_ARG_TYPE_OO,
QCOM_TEE_ARG_TYPE_IB,
QCOM_TEE_ARG_TYPE_IO,
QCOM_TEE_ARG_TYPE_NR,
+};
+/**
- define QCOM_TEE_ARGS_PER_TYPE - Maximum arguments of specific type.
- QTEE transport protocol limits maximum number of argument of specific type
- (i.e. IB, OB, IO, and OO).
- */
+#define QCOM_TEE_ARGS_PER_TYPE 16
+/* Maximum arguments that can fit in a QTEE message, ignoring the type. */ +#define QCOM_TEE_ARGS_MAX (QCOM_TEE_ARGS_PER_TYPE * (QCOM_TEE_ARG_TYPE_NR - 1))
+struct qcom_tee_buffer {
union {
void *addr;
void __user *uaddr;
};
size_t size;
+};
+/**
- struct qcom_tee_arg - Argument for QTEE object invocation.
- @type: type of argument as &enum qcom_tee_arg_type.
- @flags: extra flags.
- @b: address and size if type of argument is buffer.
- @o: object instance if type of argument is object.
- &qcom_tee_arg.flags only accept %QCOM_TEE_ARG_FLAGS_UADDR for now which states
- that &qcom_tee_arg.b contains userspace address in uaddr.
- */
+struct qcom_tee_arg {
enum qcom_tee_arg_type type;
+/* 'b.uaddr' holds a __user address. */ +#define QCOM_TEE_ARG_FLAGS_UADDR 1
unsigned int flags;
union {
struct qcom_tee_buffer b;
struct qcom_tee_object *o;
};
+};
+static inline int qcom_tee_args_len(struct qcom_tee_arg *args) +{
int i = 0;
while (args[i].type != QCOM_TEE_ARG_TYPE_INV)
i++;
return i;
+}
+#define QCOM_TEE_OIC_FLAG_BUSY BIT(1) /* Context is busy (callback is in progress). */ +#define QCOM_TEE_OIC_FLAG_NOTIFY BIT(2) /* Context needs to notify the current object. */ +#define QCOM_TEE_OIC_FLAG_SHARED BIT(3) /* Context has shared state with QTEE. */
+struct qcom_tee_object_invoke_ctx {
unsigned long flags;
int errno;
/* Current object invoked in this callback context. */
struct qcom_tee_object *object;
/* Arguments passed to dispatch callback (+1 for ending QCOM_TEE_ARG_TYPE_INV). */
struct qcom_tee_arg u[QCOM_TEE_ARGS_MAX + 1];
/* Inbound and Outbound buffers shared with QTEE. */
struct qcom_tee_buffer in_msg; /* Inbound Buffer. */
phys_addr_t in_msg_paddr; /* Physical address of inbound buffer. */
struct qcom_tee_buffer out_msg; /* Outbound Buffer. */
phys_addr_t out_msg_paddr; /* Physical address of outbound buffer. */
/* Extra data attached to this context. */
void *data;
+};
+/**
- qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
- @result: result returned from QTEE.
- The caller is responsible to keep track of the refcount for each object,
- including @object. On return, the caller loses the ownership of all input object of
- type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- @object can be of %QCOM_TEE_OBJECT_TYPE_ROOT or %QCOM_TEE_OBJECT_TYPE_TEE types.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user may retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result);
+/**
- struct qcom_tee_object_operations - Callback object operations.
- @release: release object if QTEE is not using it.
- @dispatch: dispatch the operation requested by QTEE.
- @notify: report status of any pending response submitted by @dispatch.
- Transport may fail (e.g. object table is full) even after @dispatch successfully submitted
- the response. @notify is called to do the necessary cleanup.
- */
+struct qcom_tee_object_operations {
void (*release)(struct qcom_tee_object *object);
int (*dispatch)(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *args);
void (*notify)(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, int err);
+};
+/**
- struct qcom_tee_object - QTEE or REE object.
- @name: object name.
- @refcount: reference counter.
- @object_type: object type as &enum qcom_tee_object_type.
- @info: extra information for object.
- @owner: owning module/driver.
- @ops: callback operations for object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- @work: work for async operation on object.
- @work is currently only used for release object of %QCOM_TEE_OBJECT_TYPE_TEE type.
- */
+struct qcom_tee_object {
const char *name;
struct kref refcount;
enum qcom_tee_object_type object_type;
union object_info {
/* QTEE object id if object_type is %QCOM_TEE_OBJECT_TYPE_TEE. */
unsigned long qtee_id;
} info;
struct module *owner;
struct qcom_tee_object_operations *ops;
struct work_struct work;
+};
+/* Static instances of qcom_tee_object objects. */ +#define NULL_QCOM_TEE_OBJECT ((struct qcom_tee_object *)(0)) +extern struct qcom_tee_object qcom_tee_object_root; +#define ROOT_QCOM_TEE_OBJECT (&qcom_tee_object_root)
+static inline enum qcom_tee_object_type typeof_qcom_tee_object(struct qcom_tee_object *object) +{
if (object == NULL_QCOM_TEE_OBJECT)
return QCOM_TEE_OBJECT_TYPE_NULL;
return object->object_type;
+}
+static inline const char *qcom_tee_object_name(struct qcom_tee_object *object) +{
if (object == NULL_QCOM_TEE_OBJECT)
return "null";
if (!object->name)
return "no-name";
return object->name;
+}
+/**
- __qcom_tee_object_user_init() - Initialize an object for user.
- @object: object to initialize.
- @object_type: type of object as &enum qcom_tee_object_type.
- @ops: instance of callbacks.
- @owner: owning module/driver.
- @fmt: name assigned to the object.
- Return: On success return 0 or <0 on failure.
- */
+int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot,
struct qcom_tee_object_operations *ops, struct module *owner,
const char *fmt, ...);
+#define qcom_tee_object_user_init(obj, ot, ops, fmt, ...) \
__qcom_tee_object_user_init((obj), (ot), (ops), THIS_MODULE, (fmt), __VA_ARGS__)
+/* Object release is RCU protected. */ +int qcom_tee_object_get(struct qcom_tee_object *object); +void qcom_tee_object_put(struct qcom_tee_object *object);
+#define qcom_tee_arg_for_each(i, args) \
for (i = 0; args[i].type != QCOM_TEE_ARG_TYPE_INV; i++)
+/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type);
+/* Iterate over argument of given type. */ +#define qcom_tee_arg_for_each_type(i, args, at) \
for (i = 0, i = qcom_tee_next_arg_type(args, i, at); \
args[i].type != QCOM_TEE_ARG_TYPE_INV; \
i++, i = qcom_tee_next_arg_type(args, i, at))
+#define qcom_tee_arg_for_each_input_buffer(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IB)
+#define qcom_tee_arg_for_each_output_buffer(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OB)
+#define qcom_tee_arg_for_each_input_object(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IO)
+#define qcom_tee_arg_for_each_output_object(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OO)
+#endif /* __QCOM_TEE_H */
-- 2.34.1
Hi Jens,
On 12/10/2024 5:43 AM, Jens Wiklander wrote:
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce qcom_tee_object, which represents an object in both QTEE and the kernel. QTEE clients can invoke an instance of qcom_tee_object to access QTEE services. If this invocation produces a new object in QTEE, an instance of qcom_tee_object will be returned.
Similarly, QTEE can request services from the kernel by issuing a callback request, which invokes an instance of qcom_tee_object in the kernel. Any subsystem that exposes a service to QTEE should allocate and initialize an instance of qcom_tee_object with a dispatcher callback that is called when the object is invoked.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 6 + drivers/tee/qcomtee/async.c | 153 ++++++ drivers/tee/qcomtee/core.c | 928 +++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcom_scm.c | 36 ++ drivers/tee/qcomtee/qcomtee_msg.h | 217 ++++++++ drivers/tee/qcomtee/qcomtee_private.h | 47 ++ drivers/tee/qcomtee/release.c | 66 +++ include/linux/firmware/qcom/qcom_tee.h | 284 ++++++++++ 11 files changed, 1749 insertions(+)
diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 61b507c18780..3a995d7f0d74 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -16,5 +16,6 @@ if TEE source "drivers/tee/optee/Kconfig" source "drivers/tee/amdtee/Kconfig" source "drivers/tee/tstee/Kconfig" +source "drivers/tee/qcomtee/Kconfig"
endif diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile index 5488cba30bd2..74e987f8f7ea 100644 --- a/drivers/tee/Makefile +++ b/drivers/tee/Makefile @@ -6,3 +6,4 @@ tee-objs += tee_shm_pool.o obj-$(CONFIG_OPTEE) += optee/ obj-$(CONFIG_AMDTEE) += amdtee/ obj-$(CONFIG_ARM_TSTEE) += tstee/ +obj-$(CONFIG_QCOMTEE) += qcomtee/ diff --git a/drivers/tee/qcomtee/Kconfig b/drivers/tee/qcomtee/Kconfig new file mode 100644 index 000000000000..d180a6d07d33 --- /dev/null +++ b/drivers/tee/qcomtee/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Qualcomm Trusted Execution Environment Configuration +config QCOMTEE
tristate "Qualcomm TEE Support"
select QCOM_SCM
help
This option enables the Qualcomm Trusted Execution Environment (QTEE)
driver. It provides an API to access services offered by QTEE and any
loaded Trusted Applications (TAs), as well as exporting kernel
services to QTEE.
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile new file mode 100644 index 000000000000..7dc5e6373042 --- /dev/null +++ b/drivers/tee/qcomtee/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_QCOMTEE) += qcomtee.o +qcomtee-objs += async.o +qcomtee-objs += core.o +qcomtee-objs += qcom_scm.o +qcomtee-objs += release.o diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c new file mode 100644 index 000000000000..218ec0209722 --- /dev/null +++ b/drivers/tee/qcomtee/async.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include <linux/mutex.h> +#include <linux/slab.h>
+#include "qcomtee_private.h" +#include "qcomtee_msg.h"
+#define QCOM_TEE_ASYNC_VERSION_1_0 0x00010000U /* Major: 0x0001, Minor: 0x0000. */
Please keep the limit of 80 columns as described in the coding guidelines at https://docs.kernel.org/process/coding-style.html#breaking-long-lines-and-st... This comment applies to the entire patch set.
Ack.
I think it's easier to interpret the QCOM_TEE prefix as QCOMTEE. I wouldn't mind QTEE as a prefix and the name of the driver if that's more convenient.
Absolutely, I'll rename prefixes with QCOMTEE. I did not use QTEE as it refers to the TEE kernel :).
+#define QCOM_TEE_ASYNC_VERSION_1_1 0x00010001U /* Major: 0x0001, Minor: 0x0001. */ +#define QCOM_TEE_ASYNC_VERSION_1_2 0x00010002U /* Major: 0x0001, Minor: 0x0002. */ +#define QCOM_TEE_ASYNC_VERSION QCOM_TEE_ASYNC_VERSION_1_2 /* Current Version. */
+#define QCOM_TEE_ASYNC_VERSION_MAJOR(n) upper_16_bits(n) +#define QCOM_TEE_ASYNC_VERSION_MINOR(n) lower_16_bits(n)
+/**
- struct qcom_tee_async_msg_hdr - Asynchronous message header format.
- @version: current async protocol version of remote endpoint
- @op: async operation
- @version specifies the endpoints (QTEE or driver) supported async protocol, e.g.
- if QTEE set @version to %QCOM_TEE_ASYNC_VERSION_1_1, QTEE handles operations
- supported in %QCOM_TEE_ASYNC_VERSION_1_1 or %QCOM_TEE_ASYNC_VERSION_1_0.
- @op determins the message format.
- */
+struct qcom_tee_async_msg_hdr {
u32 version;
u32 op;
+};
+/**
- struct qcom_tee_async_release_msg - Release asynchronous message.
- @hdr: message header as &struct qcom_tee_async_msg_hdr
- @counts: number of objects in @object_ids
- @object_ids: array of object ids should be released
- Available in Major = 0x0001, Minor >= 0x0000.
- */
+struct qcom_tee_async_release_msg {
struct qcom_tee_async_msg_hdr hdr;
u32 counts;
u32 object_ids[] __counted_by(counts);
+};
+/**
- qcom_tee_get_async_buffer() - Get start of the asynchronous message in outbound buffer.
- @oic: context used for current invocation
- @async_buffer: return buffer to extract from or fill in async messages
- If @oic is used for direct object invocation, whole outbound buffer is available for
- async message. If @oic is used for callback request, the tail of outbound buffer (after
- the callback request message) is available for async message.
- */
+static void qcom_tee_get_async_buffer(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_buffer *async_buffer)
+{
struct qcom_tee_msg_callback *msg;
unsigned int offset;
int i;
if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) {
/* The outbound buffer is empty. Using the whole buffer. */
offset = 0;
} else {
msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
/* Start offset in a message for buffer arguments. */
offset = qcom_tee_msg_buffer_args(struct qcom_tee_msg_callback,
qcom_tee_msg_args(msg));
/* Add size of IB arguments. */
qcom_tee_msg_for_each_input_buffer(i, msg)
offset += qcom_tee_msg_offset_align(msg->args[i].b.size);
/* Add size of OB arguments. */
qcom_tee_msg_for_each_output_buffer(i, msg)
offset += qcom_tee_msg_offset_align(msg->args[i].b.size);
}
async_buffer->addr = oic->out_msg.addr + offset;
async_buffer->size = oic->out_msg.size - offset;
+}
+/**
- qcom_tee_async_release_handler() - Process QTEE async requests for releasing objects.
- @oic: context used for current invocation
- @msg: async message for object release
- @size: size of the async buffer available
- Return: Size of outbound buffer used when processing @msg.
- */
+static size_t qcom_tee_async_release_handler(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_async_msg_hdr *async_msg, size_t size)
+{
struct qcom_tee_async_release_msg *msg = (struct qcom_tee_async_release_msg *)async_msg;
struct qcom_tee_object *object;
int i;
for (i = 0; i < msg->counts; i++) {
object = qcom_tee_idx_erase(msg->object_ids[i]);
qcom_tee_object_put(object);
}
return struct_size_t(struct qcom_tee_async_release_msg, object_ids, i);
+}
+/**
- qcom_tee_fetch_async_reqs() - Fetch and process asynchronous messages.
- @oic: context used for current invocation
- It looks for handler to process the requested operations in the async message.
- Currently, only support async release requests.
- */
+void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic) +{
struct qcom_tee_async_msg_hdr *async_msg;
struct qcom_tee_buffer async_buffer;
size_t consumed, used = 0;
qcom_tee_get_async_buffer(oic, &async_buffer);
while (async_buffer.size - used > sizeof(struct qcom_tee_async_msg_hdr)) {
async_msg = (struct qcom_tee_async_msg_hdr *)(async_buffer.addr + used);
if (QCOM_TEE_ASYNC_VERSION_MAJOR(async_msg->version) !=
QCOM_TEE_ASYNC_VERSION_MAJOR(QCOM_TEE_ASYNC_VERSION))
goto out;
switch (async_msg->op) {
case QCOM_TEE_MSG_OBJECT_OP_RELEASE:
consumed = qcom_tee_async_release_handler(oic, async_msg,
async_buffer.size - used);
break;
default:
/* Unsupported operations. */
goto out;
}
/* Supported operation but unable to parse the message. */
if (!consumed)
goto out;
used += qcom_tee_msg_offset_align(consumed);
}
- out:
/* Reset the async messages buffer so async requests do not loopback to QTEE. */
memzero_explicit(async_buffer.addr, async_buffer.size);
+} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c new file mode 100644 index 000000000000..a949ef4cceee --- /dev/null +++ b/drivers/tee/qcomtee/core.c @@ -0,0 +1,928 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/init.h> +#include <linux/kobject.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/xarray.h>
+#include "qcomtee_msg.h" +#include "qcomtee_private.h"
+/* Static instance of object represents QTEE root object. */ +struct qcom_tee_object qcom_tee_object_root = {
.name = "root",
.object_type = QCOM_TEE_OBJECT_TYPE_ROOT,
.info.qtee_id = QCOM_TEE_MSG_OBJECT_ROOT,
+}; +EXPORT_SYMBOL_GPL(qcom_tee_object_root);
+/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type) +{
while (u[i].type != QCOM_TEE_ARG_TYPE_INV && u[i].type != type)
i++;
return i;
+}
+/* QTEE expects IDs with QCOM_TEE_MSG_OBJECT_NS_BIT set for object of
- QCOM_TEE_OBJECT_TYPE_CB_OBJECT type.
- */
+#define QCOM_TEE_OBJECT_ID_START (QCOM_TEE_MSG_OBJECT_NS_BIT + 1) +#define QCOM_TEE_OBJECT_ID_END (UINT_MAX)
+#define QCOM_TEE_OBJECT_SET(p, type, ...) __QCOM_TEE_OBJECT_SET(p, type, ##__VA_ARGS__, 0UL) +#define __QCOM_TEE_OBJECT_SET(p, type, optr, ...) do { \
(p)->object_type = (type); \
(p)->info.qtee_id = (unsigned long)(optr); \
} while (0)
+static struct qcom_tee_object *qcom_tee_object_alloc(void) +{
struct qcom_tee_object *object;
object = kzalloc(sizeof(*object), GFP_KERNEL);
if (object) {
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL);
kref_init(&object->refcount);
}
return object;
+}
+void qcom_tee_object_free(struct qcom_tee_object *object) +{
kfree(object->name);
kfree(object);
+}
+static void qcom_tee_object_release(struct kref *refcount) +{
struct qcom_tee_object *object;
struct module *owner;
const char *name;
object = container_of(refcount, struct qcom_tee_object, refcount);
synchronize_rcu();
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_TEE:
qcom_tee_release_tee_object(object);
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
/* Copy, as after release we should not access object. */
name = object->name;
owner = object->owner;
if (object->ops->release)
object->ops->release(object);
module_put(owner);
kfree_const(name);
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_NULL:
default:
break;
}
+}
+/**
- qcom_tee_object_get() - Increase object's refcount.
- @object: object to increase the refcount
- */
+int qcom_tee_object_get(struct qcom_tee_object *object) +{
if (object != NULL_QCOM_TEE_OBJECT &&
object != ROOT_QCOM_TEE_OBJECT)
return kref_get_unless_zero(&object->refcount);
return 0;
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_get);
+/**
- qcom_tee_object_put() - Decrease object's refcount
- @object: object to decrease the refcount
- */
+void qcom_tee_object_put(struct qcom_tee_object *object) +{
if (object != NULL_QCOM_TEE_OBJECT &&
object != ROOT_QCOM_TEE_OBJECT)
kref_put(&object->refcount, qcom_tee_object_release);
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_put);
+/* ''Local Object Table''. */ +/* Object from kernel that are exported to QTEE are assigned an id and stored in
- xa_qcom_local_objects (kernel object table). QTEE uses this id to reference the
- object using qcom_tee_local_object_get.
- */
+static DEFINE_XARRAY_ALLOC(xa_qcom_local_objects);
We would store this in struct optee in the OP-TEE backend driver. It's not clear to me how this variable is freed when the driver is unloaded. Would it make sense to introduce a struct qcomtee to hold the driver state?
I did not put it in a seperate struct as we did not have much to put in it, but for consistencey, I can do that :).
xa_qcom_local_objects holds callback objects sent to QTEE. After the driver unloads, there's no API to notify QTEE of this event. However, we rely on two mechanisms:
1. TEE subsystem tee_device_unregister: This ensures there are no active users. When user contexts terminate, all QTEE objects within those contexts are released. This is done by adding release requests to a workqueue. When QTEE receives a release request for an object, it will also release all callback objects associated with that object.
For instance, imagine an object represents a TA. If you release the TA object, it will unload the TA, which in turn will release all the callback objects that the TA owns.
2. Wait for the release workqueue to flush: If there are no QTEE objects in the kernel, there can't be any callback objects in QTEE.
Therefore, it's indirectly guaranteed that xa_qcom_local_objects will be empty when the unload process is complete.
I can add a WARN on unload to at least print something if anything goes wrong.
+static int qcom_tee_idx_alloc(u32 *idx, struct qcom_tee_object *object) +{
static u32 xa_last_id = QCOM_TEE_OBJECT_ID_START;
/* Every id allocated here, has QCOM_TEE_MSG_OBJECT_NS_BIT set. */
return xa_alloc_cyclic(&xa_qcom_local_objects, idx, object,
XA_LIMIT(QCOM_TEE_OBJECT_ID_START, QCOM_TEE_OBJECT_ID_END),
&xa_last_id, GFP_KERNEL);
+}
+struct qcom_tee_object *qcom_tee_idx_erase(u32 idx) +{
if (idx < QCOM_TEE_OBJECT_ID_START || idx > QCOM_TEE_OBJECT_ID_END)
return NULL_QCOM_TEE_OBJECT;
return xa_erase(&xa_qcom_local_objects, idx);
+}
+/**
- qcom_tee_object_id_get() - Get an id for an object to sent to QTEE.
- @object: object to get its id.
- @object_id: object id.
- For object hosted in REE, they are added to object table, and the idx in the
- object table is used as id. For object hosted in QTEE, use the QTEE id stored in
- @object. This is called on a path to QTEE to construct a message, see
- qcom_tee_prepare_msg() and qcom_tee_update_msg().
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_id_get(struct qcom_tee_object *object, unsigned int *object_id) +{
u32 idx;
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
if (qcom_tee_idx_alloc(&idx, object) < 0)
return -ENOSPC;
*object_id = idx;
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_TEE:
*object_id = object->info.qtee_id;
break;
case QCOM_TEE_OBJECT_TYPE_NULL:
*object_id = QCOM_TEE_MSG_OBJECT_NULL;
break;
}
return 0;
+}
+/* Release object id assigned in qcom_tee_object_id_get. */ +static void qcom_tee_object_id_put(unsigned int object_id) +{
qcom_tee_idx_erase(object_id);
+}
+/**
- qcom_tee_local_object_get() - Get an object in REE referenced by the id.
- @object_id: object id.
- It is called on behalf of QTEE to obtain instance of object for an id. It is
- called on a path from QTEE to construct an argument of &struct qcom_tee_arg,
- see qcom_tee_update_args() and qcom_tee_prepare_args().
- It increases the object's refcount on success.
- Return: On error returns %NULL_QCOM_TEE_OBJECT. On success, the object.
- */
+static struct qcom_tee_object *qcom_tee_local_object_get(unsigned int object_id) +{
struct qcom_tee_object *object;
/* We trust QTEE does not mess the refcounts.
* It does not issue RELEASE request and qcom_tee_object_get(), simultaneously.
*/
What does this comment mean? That QTEE shouldn't get and release an object in one request or that QTEE is trusted to keep the get and release in balance?
I should use an RCU lock to protect qcom_tee_object_get and check its return value, to manage the race condition where QTEE might try to get and release an object at the same time.
I assume it would not happen and droped the check.
Please use the preferred style for multi-line comments https://docs.kernel.org/process/coding-style.html#commenting This comment applies to the entire patch set.
Ack.
object = xa_load(&xa_qcom_local_objects, object_id);
qcom_tee_object_get(object);
return object;
+}
+/**
- __qcom_tee_object_user_init() - Initialize an object for user.
- @object: object to initialize.
- @ot: type of object as &enum qcom_tee_object_type.
- @ops: instance of callbacks.
- @fmt: name assigned to the object.
- Return: On success return 0 or <0 on failure.
- */
+int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot,
struct qcom_tee_object_operations *ops, struct module *owner,
const char *fmt, ...)
+{
va_list ap;
int ret;
kref_init(&object->refcount);
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_NULL);
va_start(ap, fmt);
switch (ot) {
case QCOM_TEE_OBJECT_TYPE_NULL:
ret = 0;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
object->ops = ops;
if (!object->ops->dispatch)
return -EINVAL;
object->owner = owner;
if (!try_module_get(object->owner))
return -EINVAL;
/* If failed, "no-name"; it is not really a reason to fail here. */
object->name = kvasprintf_const(GFP_KERNEL, fmt, ap);
QCOM_TEE_OBJECT_SET(object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT);
ret = 0;
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
case QCOM_TEE_OBJECT_TYPE_TEE:
default:
ret = -EINVAL;
}
va_end(ap);
return ret;
+} +EXPORT_SYMBOL_GPL(__qcom_tee_object_user_init);
+/**
- qcom_tee_object_type() - Returns type of object represented by an object id.
- @object_id: object id for the object.
- This is similar to typeof_qcom_tee_object() but instead of receiving object
- as argument it receives object id. It is used internally on return path
- from QTEE.
- Return: Returns type of object referenced by @object_id.
- */
+static enum qcom_tee_object_type qcom_tee_object_type(unsigned int object_id) +{
if (object_id == QCOM_TEE_MSG_OBJECT_NULL)
return QCOM_TEE_OBJECT_TYPE_NULL;
if (object_id & QCOM_TEE_MSG_OBJECT_NS_BIT)
return QCOM_TEE_OBJECT_TYPE_CB_OBJECT;
return QCOM_TEE_OBJECT_TYPE_TEE;
+}
+/**
- qcom_tee_object_init() - Initialize an object for QTEE.
- @object: return object
- @object_id: object id received form QTEE
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_init(struct qcom_tee_object **object, unsigned int object_id) +{
struct qcom_tee_object *qto;
int ret = 0;
switch (qcom_tee_object_type(object_id)) {
case QCOM_TEE_OBJECT_TYPE_NULL:
*object = NULL_QCOM_TEE_OBJECT;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
qto = qcom_tee_local_object_get(object_id);
if (qto != NULL_QCOM_TEE_OBJECT)
*object = qto;
else
ret = -EINVAL;
break;
case QCOM_TEE_OBJECT_TYPE_TEE:
qto = qcom_tee_object_alloc();
if (qto) {
/* If failed, "no-name"; it is not really a reason to fail here. */
qto->name = kasprintf(GFP_KERNEL, "qcom_tee-%u", object_id);
QCOM_TEE_OBJECT_SET(qto, QCOM_TEE_OBJECT_TYPE_TEE, object_id);
*object = qto;
} else {
ret = -ENOMEM;
}
break;
default:
break;
}
if (ret)
*object = NULL_QCOM_TEE_OBJECT;
return ret;
+}
+/* Marshaling API. */ +/* qcom_tee_prepare_msg - Prepares inbound buffer for sending to QTEE
- qcom_tee_update_args - Parses QTEE response in inbound buffer
- qcom_tee_prepare_args - Parses QTEE request from outbound buffer
- qcom_tee_update_msg - Updates outbound buffer with response for QTEE request
- */
Please combine the two comments into one.
Ack.
+static int qcom_tee_prepare_msg(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u)
+{
struct qcom_tee_msg_object_invoke *msg;
unsigned int object_id;
int ib, ob, io, oo;
size_t off;
int i;
/* Use input message buffer in 'oic'. */
msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr;
/* Start offset in a message for buffer arguments. */
off = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u));
/* Get id of object being invoked. */
if (qcom_tee_object_id_get(object, &object_id))
return -ENOSPC;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, u) {
void *ptr;
/* qcom_tee_msg_buffers_alloc() already checked overflow in message! */
msg->args[ib].b.offset = off;
msg->args[ib].b.size = u[i].b.size;
ptr = qcom_tee_msg_offset_to_ptr(msg, off);
if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR))
memcpy(ptr, u[i].b.addr, u[i].b.size);
else if (copy_from_user(ptr, u[i].b.uaddr, u[i].b.size))
return -EINVAL;
off += qcom_tee_msg_offset_align(u[i].b.size);
ib++;
}
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, u) {
/* qcom_tee_msg_buffers_alloc() already checked overflow in message! */
msg->args[ob].b.offset = off;
msg->args[ob].b.size = u[i].b.size;
off += qcom_tee_msg_offset_align(u[i].b.size);
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, u) {
if (qcom_tee_object_id_get(u[i].o, &msg->args[io].o)) {
/* Unable to qcom_tee_object_id_get; put whatever we got. */
qcom_tee_object_id_put(object_id);
for (--io; io >= ob; io--)
Please avoid the prefix increment or decrement operator and use the postfix increment or decrement unless there's no other option (use io-- instead of --io). This comment applies to the entire patch set.
Ack.
qcom_tee_object_id_put(msg->args[io].o);
return -ENOSPC;
}
io++;
}
oo = io;
qcom_tee_arg_for_each_output_object(i, u)
oo++;
/* Set object, operation, and argument counts. */
count
qcom_tee_msg_init(msg, object_id, op, ib, ob, io, oo);
return 0;
+}
+static int qcom_tee_update_args(struct qcom_tee_arg *u, struct qcom_tee_object_invoke_ctx *oic) +{
struct qcom_tee_msg_object_invoke *msg;
int ib, ob, io, oo;
int i, ret = 0;
/* Use input message buffer in 'oic'. */
msg = (struct qcom_tee_msg_object_invoke *)oic->in_msg.addr;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, u)
ib++;
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, u) {
void *ptr = qcom_tee_msg_offset_to_ptr(msg, msg->args[ob].b.offset);
if (!(u[i].flags & QCOM_TEE_ARG_FLAGS_UADDR)) {
memcpy(u[i].b.addr, ptr, msg->args[ob].b.size);
} else if (copy_to_user(u[i].b.uaddr, ptr, msg->args[ob].b.size)) {
/* On ERROR, continue to process arguments to get to output object. */
ret = -EINVAL;
}
u[i].b.size = msg->args[ob].b.size;
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, u)
io++;
oo = io;
qcom_tee_arg_for_each_output_object(i, u) {
int err;
/* On ERROR, continue to process arguments so that we can issue the RELEASE. */
err = qcom_tee_object_init(&u[i].o, msg->args[oo].o);
if (err)
ret = err;
oo++;
}
return ret;
+}
+static int qcom_tee_prepare_args(struct qcom_tee_object_invoke_ctx *oic) +{
int i, ret = 0;
/* Use output message buffer in 'oic'. */
struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
qcom_tee_msg_for_each_input_buffer(i, msg) {
oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset);
oic->u[i].b.size = msg->args[i].b.size;
oic->u[i].type = QCOM_TEE_ARG_TYPE_IB;
}
qcom_tee_msg_for_each_output_buffer(i, msg) {
oic->u[i].b.addr = qcom_tee_msg_offset_to_ptr(msg, msg->args[i].b.offset);
oic->u[i].b.size = msg->args[i].b.size;
oic->u[i].type = QCOM_TEE_ARG_TYPE_OB;
}
qcom_tee_msg_for_each_input_object(i, msg) {
int err;
/* On ERROR, continue to process arguments so that we can issue the RELEASE. */
err = qcom_tee_object_init(&oic->u[i].o, msg->args[i].o);
if (err)
ret = err;
oic->u[i].type = QCOM_TEE_ARG_TYPE_IO;
}
qcom_tee_msg_for_each_output_object(i, msg)
oic->u[i].type = QCOM_TEE_ARG_TYPE_OO;
/* End of Arguments. */
oic->u[i].type = QCOM_TEE_ARG_TYPE_INV;
return ret;
+}
+static int qcom_tee_update_msg(struct qcom_tee_object_invoke_ctx *oic) +{
int ib, ob, io, oo;
int i;
/* Use output message buffer in 'oic'. */
struct qcom_tee_msg_callback *msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
ib = 0;
qcom_tee_arg_for_each_input_buffer(i, oic->u)
ib++;
ob = ib;
qcom_tee_arg_for_each_output_buffer(i, oic->u) {
/* Only reduce size; never increase it. */
if (msg->args[ob].b.size < oic->u[i].b.size)
return -EINVAL;
msg->args[ob].b.size = oic->u[i].b.size;
ob++;
}
io = ob;
qcom_tee_arg_for_each_input_object(i, oic->u)
io++;
oo = io;
qcom_tee_arg_for_each_output_object(i, oic->u) {
if (qcom_tee_object_id_get(oic->u[i].o, &msg->args[oo].o)) {
/* Unable to qcom_tee_object_id_get; put whatever we got. */
for (--oo; oo >= io; --oo)
qcom_tee_object_id_put(msg->args[oo].o);
return -ENOSPC;
}
oo++;
}
return 0;
+}
+/**
- define MAX_BUFFER_SIZE - Maximum size of inbound and outbound buffers.
- QTEE transport does not impose any restriction on these buffers. However, if size of
- buffers are larger then %MAX_BUFFER_SIZE, user should probably use some other
- form of shared memory with QTEE.
- */
+#define MAX_BUFFER_SIZE SZ_8K
+/* Pool to allocate inbound and outbound buffers. */ +static struct qcom_tzmem_pool */;
+static int qcom_tee_msg_buffers_alloc(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_arg *u)
+{
size_t size;
int i;
/* Start offset in a message for buffer arguments. */
size = qcom_tee_msg_buffer_args(struct qcom_tee_msg_object_invoke, qcom_tee_args_len(u));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
/* Add size of IB arguments. */
qcom_tee_arg_for_each_input_buffer(i, u) {
size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
}
/* Add size of OB arguments. */
qcom_tee_arg_for_each_output_buffer(i, u) {
size = size_add(size, qcom_tee_msg_offset_align(u[i].b.size));
if (size > MAX_BUFFER_SIZE)
return -EINVAL;
}
/* QTEE requires inbound buffer size to be page aligned. */
size = PAGE_ALIGN(size);
/* Do allocations. */
oic->in_msg.size = size;
oic->in_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, size, GFP_KERNEL);
if (!oic->in_msg.addr)
return -EINVAL;
oic->out_msg.size = MAX_BUFFER_SIZE;
oic->out_msg.addr = qcom_tzmem_alloc(tzmem_msg_pool, MAX_BUFFER_SIZE, GFP_KERNEL);
if (!oic->out_msg.addr) {
qcom_tzmem_free(oic->in_msg.addr);
return -EINVAL;
}
oic->in_msg_paddr = qcom_tzmem_to_phys(oic->in_msg.addr);
oic->out_msg_paddr = qcom_tzmem_to_phys(oic->out_msg.addr);
/* QTEE assume unused buffers are zeroed; Do it now! */
memzero_explicit(oic->in_msg.addr, oic->in_msg.size);
memzero_explicit(oic->out_msg.addr, oic->out_msg.size);
return 0;
+}
+static void qcom_tee_msg_buffers_free(struct qcom_tee_object_invoke_ctx *oic) +{
qcom_tzmem_free(oic->in_msg.addr);
qcom_tzmem_free(oic->out_msg.addr);
+}
+static int qcom_tee_msg_buffers_init(void) +{
struct qcom_tzmem_pool_config config = {
.policy = QCOM_TZMEM_POLICY_ON_DEMAND,
/* 4M seems enough, it is used for QTEE meg header and qcom_tee_msg_arg array. */
.max_size = SZ_4M
};
tzmem_msg_pool = qcom_tzmem_pool_new(&config);
if (IS_ERR(tzmem_msg_pool))
return PTR_ERR(tzmem_msg_pool);
return 0;
+}
+static void qcom_tee_msg_buffers_destroy(void) +{
qcom_tzmem_pool_free(tzmem_msg_pool);
Can tzmem_msg_pool become a dangling pointer if the driver is unbound?
Yes, my original plan was to keep basic calls like qcom_tee_object_do_invoke (to invoke an object) available even when the driver is unbound in case it is needed.
I'll avoid it.
+}
+/* Invoke a REE object. */ +static void qcom_tee_object_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_msg_callback *msg)
+{
int i, errno;
u32 op;
/* Get object being invoked. */
unsigned int object_id = msg->cxt;
struct qcom_tee_object *object;
/* QTEE can not invoke NULL object or objects it hosts. */
if (qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_NULL ||
qcom_tee_object_type(object_id) == QCOM_TEE_OBJECT_TYPE_TEE) {
errno = -EINVAL;
goto out;
}
object = qcom_tee_local_object_get(object_id);
if (object == NULL_QCOM_TEE_OBJECT) {
errno = -EINVAL;
goto out;
}
oic->object = object;
/* Filter bits used by transport. */
op = msg->op & QCOM_TEE_MSG_OBJECT_OP_MASK;
switch (op) {
case QCOM_TEE_MSG_OBJECT_OP_RELEASE:
qcom_tee_object_id_put(object_id);
qcom_tee_object_put(object);
errno = 0;
break;
case QCOM_TEE_MSG_OBJECT_OP_RETAIN:
qcom_tee_object_get(object);
errno = 0;
break;
default:
errno = qcom_tee_prepare_args(oic);
if (errno) {
/* Unable to parse the message. Release any object arrived as input. */
that arrived
qcom_tee_arg_for_each_input_buffer(i, oic->u)
qcom_tee_object_put(oic->u[i].o);
break;
}
errno = object->ops->dispatch(oic, object, op, oic->u);
if (!errno) {
/* On SUCCESS, notify object at appropriate time. */
oic->flags |= QCOM_TEE_OIC_FLAG_NOTIFY;
}
}
+out:
oic->errno = errno;
+}
+/**
- __qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
arguments
- @result: result returned from QTEE.
- The caller is responsible to keep track of the refcount for each object,
- including @object. On return, the caller loses the ownership of all input
- object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user may retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result)
+{
struct qcom_tee_msg_callback *cb_msg;
u64 response_type;
int i, ret, errno;
ret = qcom_tee_msg_buffers_alloc(oic, u);
if (ret)
return ret;
ret = qcom_tee_prepare_msg(oic, object, op, u);
if (ret)
goto out;
cb_msg = (struct qcom_tee_msg_callback *)oic->out_msg.addr;
while (1) {
if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) {
errno = oic->errno;
/* Update output buffer only if result is SUCCESS. */
if (!errno)
errno = qcom_tee_update_msg(oic);
qcom_tee_msg_translate_err(cb_msg, errno);
}
/* Invoke remote object. */
ret = qcom_tee_object_invoke_ctx_invoke(oic, result, &response_type);
if (oic->flags & QCOM_TEE_OIC_FLAG_BUSY) {
struct qcom_tee_object *qto = oic->object;
if (qto) {
if (oic->flags & QCOM_TEE_OIC_FLAG_NOTIFY) {
if (qto->ops->notify)
qto->ops->notify(oic, qto, errno || ret);
}
/* Matching get is in qcom_tee_object_invoke. */
qcom_tee_object_put(qto);
}
oic->object = NULL_QCOM_TEE_OBJECT;
oic->flags &= ~(QCOM_TEE_OIC_FLAG_BUSY | QCOM_TEE_OIC_FLAG_NOTIFY);
}
if (ret) {
if (!(oic->flags & QCOM_TEE_OIC_FLAG_SHARED)) {
/* Release QCOM_TEE_OBJECT_TYPE_CB_OBJECT input objects. */
qcom_tee_arg_for_each_input_object(i, u)
if (typeof_qcom_tee_object(u[i].o) ==
QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(u[i].o);
ret = -EAGAIN;
} else {
/* On error, there is no clean way to exit. */
/* For some reason we can not communicate with QTEE, so we can not
* notify QTEE about the failure and do further cleanup.
*/
ret = -ENODEV;
}
goto out;
} else {
/* QTEE obtained the ownership of QCOM_TEE_OBJECT_TYPE_CB_OBJECT
* input objects in 'u'. On further failure, QTEE is responsible
* to release them.
*/
oic->flags |= QCOM_TEE_OIC_FLAG_SHARED;
}
/* Is it a callback request? */
+#define QCOM_TEE_RESULT_INBOUND_REQ_NEEDED 3
This must be part of the ABI to the secure world. How about keeping all those defines in a central place?
Ack I'll move it to the correct header.
if (response_type != QCOM_TEE_RESULT_INBOUND_REQ_NEEDED) {
if (!*result) {
ret = qcom_tee_update_args(u, oic);
if (ret) {
qcom_tee_arg_for_each_output_object(i, u)
qcom_tee_object_put(u[i].o);
ret = -EAGAIN;
}
}
break;
} else {
oic->flags |= QCOM_TEE_OIC_FLAG_BUSY;
/* Before dispatching the request, handle any pending async requests. */
qcom_tee_fetch_async_reqs(oic);
qcom_tee_object_invoke(oic, cb_msg);
This looks like a recursion. Is there a limit on how deep it can become?
It's not recursion :). I understand the confusion; the function names qcom_tee_object_invoke and qcom_tee_object_do_invoke are very similar. I'll fix this.
}
}
qcom_tee_fetch_async_reqs(oic);
+out:
qcom_tee_msg_buffers_free(oic);
return ret;
+}
+int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result)
+{
/* User can not set bits used by transport. */
if (op & ~QCOM_TEE_MSG_OBJECT_OP_MASK)
return -EINVAL;
/* User can only invoke QTEE hosted objects. */
if (typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_TEE &&
typeof_qcom_tee_object(object) != QCOM_TEE_OBJECT_TYPE_ROOT)
return -EINVAL;
/* User can not issue reserved operations to QTEE. */
if (op == QCOM_TEE_MSG_OBJECT_OP_RELEASE || op == QCOM_TEE_MSG_OBJECT_OP_RETAIN)
return -EINVAL;
return __qcom_tee_object_do_invoke(oic, object, op, u, result);
+} +EXPORT_SYMBOL_GPL(qcom_tee_object_do_invoke);
+/* Dump object table. */ +static ssize_t qcom_tee_object_table_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
+{
struct qcom_tee_object *object;
unsigned long idx;
size_t len = 0;
xa_for_each_start(&xa_qcom_local_objects, idx, object, QCOM_TEE_OBJECT_ID_START) {
len += sysfs_emit_at(buf, len, "%4lx %4d %s\n", idx,
kref_read(&object->refcount),
qcom_tee_object_name(object));
}
return len;
+}
+static struct kobj_attribute object_table = __ATTR_RO(qcom_tee_object_table); +static struct kobj_attribute release = __ATTR_RO(qcom_tee_release_wq); +static struct attribute *attrs[] = {
&object_table.attr,
&release.attr,
NULL
+};
+static struct attribute_group attr_group = {
.attrs = attrs,
+};
+static struct kobject *qcom_tee_object_invoke_kobj; +static int __init qcom_tee_object_invoke_init(void) +{
int ret;
ret = qcom_tee_release_init();
if (ret)
return ret;
ret = qcom_tee_msg_buffers_init();
if (ret)
goto err_release_destroy;
/* Create '/sys/firmware/qcom_tee'. */
qcom_tee_object_invoke_kobj = kobject_create_and_add("qcom_tee", firmware_kobj);
I'd expect TEE drivers to keep their stuff under /sys/class/tee/tee*/
Ack.
if (!qcom_tee_object_invoke_kobj) {
ret = -ENOMEM;
goto err_msg_buffers_destroy;
}
/* Create 'qcom_tee_object_table' and 'qcom_tee_release_wq'. */
ret = sysfs_create_group(qcom_tee_object_invoke_kobj, &attr_group);
if (ret)
goto err_kobject_put;
return 0;
+err_kobject_put:
/* Remove '/sys/firmware/qcom_tee'. */
kobject_put(qcom_tee_object_invoke_kobj);
+err_msg_buffers_destroy:
qcom_tee_msg_buffers_destroy();
+err_release_destroy:
qcom_tee_release_destroy();
return ret;
+} +module_init(qcom_tee_object_invoke_init);
The other TEE drivers do most of the initialization work in the probe function and only the bare minimum for the probe function to be called before. Wouldn't it make sense to try to use the same pattern in this driver? It helps when trying to understand the driver, and perhaps a few bugs can be avoided too when following an established pattern.
I understand. I'll do that.
I did it this way to keep basic calls like qcom_tee_object_do_invoke (to invoke an object) available even when the driver is unbound. I'll remove it.
+static void __exit qcom_tee_object_invoke_deinit(void) +{
/* Wait for RELEASE operations for QTEE objects. */
qcom_tee_release_destroy();
qcom_tee_msg_buffers_destroy();
sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group);
kobject_put(qcom_tee_object_invoke_kobj);
+} +module_exit(qcom_tee_object_invoke_deinit);
+MODULE_AUTHOR("Qualcomm"); +MODULE_DESCRIPTION("QTEE driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/qcomtee/qcom_scm.c b/drivers/tee/qcomtee/qcom_scm.c new file mode 100644 index 000000000000..230faf249095 --- /dev/null +++ b/drivers/tee/qcomtee/qcom_scm.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include <linux/firmware/qcom/qcom_scm.h>
+#include "qcomtee_private.h"
+int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic,
int *result, u64 *response_type)
+{
int ret;
u64 res;
if (!(oic->flags & QCOM_TEE_OIC_FLAG_BUSY)) {
/* Direct QTEE object invocation. */
ret = qcom_scm_qtee_invoke_smc(oic->in_msg_paddr,
oic->in_msg.size,
oic->out_msg_paddr,
oic->out_msg.size,
&res, response_type, NULL);
} else {
/* Submit callback response. */
ret = qcom_scm_qtee_callback_response(oic->out_msg_paddr,
oic->out_msg.size,
&res, response_type, NULL);
}
if (ret)
pr_err("QTEE returned with %d.\n", ret);
else
*result = (int)res;
return ret;
+} diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h new file mode 100644 index 000000000000..7c968834ec9d --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef QCOMTEE_MSG_H +#define QCOMTEE_MSG_H
+#include <linux/firmware/qcom/qcom_tee.h>
+/**
- DOC: ''Qualcomm TEE'' (QTEE) Transport Message
- There are two buffers shared with QTEE, inbound and outbound buffers.
- The inbound buffer is used for direct object invocation and the outbound buffer is
- used to make a request from QTEE to kernel, i.e. callback request.
- The unused tail of the outbound buffer is also used for sending and receiving
- asynchronous messages. An asynchronous message is independent from the current
- object invocation (i.e. contents of the inbound buffer) or callback request
- (i.e. the head of the outbound buffer), see qcom_tee_get_async_buffer(). It is
- used by endpoints (QTEE or kernel) as an optimization to reduce number of context
- switches between secure and non-secure world.
- For instance, QTEE never sends an explicit callback request to release an object in
- kernel. Instead, it sends asynchronous release messages in outbound buffer when QTEE
- returns from previous direct object invocation, or append asynchronous release
- messages after the current callback request.
- QTEE supports two types of arguments in a message: buffer and object arguments.
- Depending on the direction of data flow, they could be input buffer (IO) to QTEE,
- output buffer (OB) from QTEE, input object (IO) to QTEE, or output object (OO) from
- QTEE. Object arguments hold object ids. Buffer arguments hold (offset, size) pairs
- into the inbound or outbound buffers.
- QTEE holds an object table for objects, it hosts and exposes to kernel. An object id
- is an index to the object table in QTEE.
- For direct object invocation message format in inbound buffer see
- &struct qcom_tee_msg_object_invoke. For callback request message format in outbound
- buffer see &struct qcom_tee_msg_callback. For the message format for asynchronous message
- in outbound buffer see &struct qcom_tee_async_msg_hdr.
- */
+/**
- define QCOM_TEE_MSG_OBJECT_NS_BIT - Non-secure bit
- Object id is a globally unique 32-bit number. Ids referencing objects in kernel should
- have %QCOM_TEE_MSG_OBJECT_NS_BIT set.
- */
+#define QCOM_TEE_MSG_OBJECT_NS_BIT BIT(31)
+/* Static object ids recognized by QTEE. */ +#define QCOM_TEE_MSG_OBJECT_NULL (0U) +#define QCOM_TEE_MSG_OBJECT_ROOT (1U)
+/* Definitions from QTEE as part of the transport protocol. */
+/* qcom_tee_msg_arg is argument as recognized by QTEE. */ +union qcom_tee_msg_arg {
struct {
u32 offset;
u32 size;
} b;
u32 o;
+};
+/* BI and BO payloads in a QTEE messages should be at 64-bit boundaries. */ +#define qcom_tee_msg_offset_align(o) ALIGN((o), sizeof(u64))
+/* Operation for objects is 32-bit. Transport uses upper 16-bits internally. */ +#define QCOM_TEE_MSG_OBJECT_OP_MASK 0x0000FFFFU
GENMASK()?
Ack.
Cheers, Jens
Thank you so much Jens for your comments :).
Best Regards, Amir
+/* Reserved Operation IDs sent to QTEE: */ +/* QCOM_TEE_MSG_OBJECT_OP_RELEASE - Reduces the refcount and releases the object.
- QCOM_TEE_MSG_OBJECT_OP_RETAIN - Increases the refcount.
- These operation id are valid for all objects. They are not available outside of this
- driver. Developers should use qcom_tee_object_get() and qcom_tee_object_put(), to
- achieve the same.
- */
+#define QCOM_TEE_MSG_OBJECT_OP_RELEASE (QCOM_TEE_MSG_OBJECT_OP_MASK - 0) +#define QCOM_TEE_MSG_OBJECT_OP_RETAIN (QCOM_TEE_MSG_OBJECT_OP_MASK - 1)
+/**
- struct qcom_tee_msg_object_invoke - Direct object invocation message
- @ctx: object id hosted in QTEE
- @op: operation for the object
- @counts: number of different type of arguments in @args
- @args: array of arguments
- @counts consists of 4 * 4-bits felids. Bits 0 - 3, is number of input buffers,
- bits 4 - 7, is number of output buffers, bits 8 - 11, is number of input objects,
- and bits 12 - 15, is number of output objects. Remaining bits should be zero.
- Maximum number of arguments of each type is defined by %QCOM_TEE_ARGS_PER_TYPE.
- */
+struct qcom_tee_msg_object_invoke {
u32 cxt;
u32 op;
u32 counts;
union qcom_tee_msg_arg args[];
+};
+/**
- struct qcom_tee_msg_callback - Callback request message
- @result: result of operation @op on object referenced by @cxt
- @cxt: object id hosted in kernel
- @op: operation for the object
- @counts: number of different type of arguments in @args
- @args: array of arguments
- For details of @counts, see &qcom_tee_msg_object_invoke.counts.
- */
+struct qcom_tee_msg_callback {
u32 result;
u32 cxt;
u32 op;
u32 counts;
union qcom_tee_msg_arg args[];
+};
+/* Offset in the message for the beginning of buffer argument's contents. */ +#define qcom_tee_msg_buffer_args(t, n) \
qcom_tee_msg_offset_align(struct_size_t(t, args, n))
+/* Pointer to the beginning of a buffer argument's content at an offset in a message. */ +#define qcom_tee_msg_offset_to_ptr(m, off) ((void *)&((char *)(m))[(off)])
+/* Some helpers to manage msg.counts. */
+#define QCOM_TEE_MSG_NUM_IB(x) ((x) & 0xfU) +#define QCOM_TEE_MSG_NUM_OB(x) (((x) >> 4) & 0xfU) +#define QCOM_TEE_MSG_NUM_IO(x) (((x) >> 8) & 0xfU) +#define QCOM_TEE_MSG_NUM_OO(x) (((x) >> 12) & 0xfU)
+#define QCOM_TEE_MSG_IDX_IB(x) (0U) +#define QCOM_TEE_MSG_IDX_OB(x) (QCOM_TEE_MSG_IDX_IB(x) + QCOM_TEE_MSG_NUM_IB(x)) +#define QCOM_TEE_MSG_IDX_IO(x) (QCOM_TEE_MSG_IDX_OB(x) + QCOM_TEE_MSG_NUM_OB(x)) +#define QCOM_TEE_MSG_IDX_OO(x) (QCOM_TEE_MSG_IDX_IO(x) + QCOM_TEE_MSG_NUM_IO(x))
+#define qcom_tee_msg_for_each(i, c, type) \
for (i = QCOM_TEE_MSG_IDX_##type(c); \
i < (QCOM_TEE_MSG_IDX_##type(c) + QCOM_TEE_MSG_NUM_##type(c)); \
i++)
+#define qcom_tee_msg_for_each_input_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, IB) +#define qcom_tee_msg_for_each_output_buffer(i, m) qcom_tee_msg_for_each(i, (m)->counts, OB) +#define qcom_tee_msg_for_each_input_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, IO) +#define qcom_tee_msg_for_each_output_object(i, m) qcom_tee_msg_for_each(i, (m)->counts, OO)
+/* Sum of arguments in a message. */ +#define qcom_tee_msg_args(m) (QCOM_TEE_MSG_IDX_OO((m)->counts) + QCOM_TEE_MSG_NUM_OO((m)->counts))
+static inline void qcom_tee_msg_init(struct qcom_tee_msg_object_invoke *msg, u32 cxt, u32 op,
int in_buffer, int out_buffer, int in_object, int out_object)
+{
msg->counts |= (in_buffer & 0xfU);
msg->counts |= ((out_buffer - in_buffer) & 0xfU) << 4;
msg->counts |= ((in_object - out_buffer) & 0xfU) << 8;
msg->counts |= ((out_object - in_object) & 0xfU) << 12;
msg->cxt = cxt;
msg->op = op;
+}
+/* Generic error codes. */ +#define QCOM_TEE_MSG_OK 0 /* non-specific success code. */ +#define QCOM_TEE_MSG_ERROR 1 /* non-specific error. */ +#define QCOM_TEE_MSG_ERROR_INVALID 2 /* unsupported/unrecognized request. */ +#define QCOM_TEE_MSG_ERROR_SIZE_IN 3 /* supplied buffer/string too large. */ +#define QCOM_TEE_MSG_ERROR_SIZE_OUT 4 /* supplied output buffer too small. */ +#define QCOM_TEE_MSG_ERROR_USERBASE 10 /* start of user-defined error range. */
+/* Transport layer error codes. */ +#define QCOM_TEE_MSG_ERROR_DEFUNCT -90 /* object no longer exists. */ +#define QCOM_TEE_MSG_ERROR_ABORT -91 /* calling thread must exit. */ +#define QCOM_TEE_MSG_ERROR_BADOBJ -92 /* invalid object context. */ +#define QCOM_TEE_MSG_ERROR_NOSLOTS -93 /* caller's object table full. */ +#define QCOM_TEE_MSG_ERROR_MAXARGS -94 /* too many args. */ +#define QCOM_TEE_MSG_ERROR_MAXDATA -95 /* buffers too large. */ +#define QCOM_TEE_MSG_ERROR_UNAVAIL -96 /* the request could not be processed. */ +#define QCOM_TEE_MSG_ERROR_KMEM -97 /* kernel out of memory. */ +#define QCOM_TEE_MSG_ERROR_REMOTE -98 /* local method sent to remote object. */ +#define QCOM_TEE_MSG_ERROR_BUSY -99 /* Object is busy. */ +#define QCOM_TEE_MSG_ERROR_TIMEOUT -103 /* Call Back Object invocation timed out. */
+static inline void qcom_tee_msg_translate_err(struct qcom_tee_msg_callback *cb_msg, int err) +{
if (!err) {
cb_msg->result = QCOM_TEE_MSG_OK;
} else if (err < 0) {
/* If err < 0, then it is a transport error. */
switch (err) {
case -ENOMEM:
cb_msg->result = QCOM_TEE_MSG_ERROR_KMEM;
break;
case -ENODEV:
cb_msg->result = QCOM_TEE_MSG_ERROR_DEFUNCT;
break;
case -ENOSPC:
case -EBUSY:
cb_msg->result = QCOM_TEE_MSG_ERROR_BUSY;
break;
case -EBADF:
case -EINVAL:
cb_msg->result = QCOM_TEE_MSG_ERROR_UNAVAIL;
break;
default:
cb_msg->result = QCOM_TEE_MSG_ERROR;
}
} else {
/* If err > 0, then it is user defined error, pass it as is. */
cb_msg->result = err;
}
+}
+#endif /* QCOMTEE_MSG_H */ diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h new file mode 100644 index 000000000000..e3e4ef51c0b2 --- /dev/null +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef QCOM_TEE_PRIVATE_H +#define QCOM_TEE_PRIVATE_H
+#include <linux/firmware/qcom/qcom_tee.h> +#include <linux/kobject.h> +#include <linux/tee_core.h>
+struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); +void qcom_tee_object_free(struct qcom_tee_object *object);
+/* Process async messages form QTEE. */ +void qcom_tee_fetch_async_reqs(struct qcom_tee_object_invoke_ctx *oic);
+int qcom_tee_release_init(void); +void qcom_tee_release_destroy(void); +void qcom_tee_release_tee_object(struct qcom_tee_object *object); +ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+/* SCM call. */ +int qcom_tee_object_invoke_ctx_invoke(struct qcom_tee_object_invoke_ctx *oic,
int *result, u64 *response_type);
+/**
- __qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
- @result: result returned from QTEE.
- Same as qcom_tee_object_do_invoke() without @object and @op is 32-bit,
- upper 16-bits are for internal use.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user can retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result);
+#endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/release.c b/drivers/tee/qcomtee/release.c new file mode 100644 index 000000000000..f2e048418e23 --- /dev/null +++ b/drivers/tee/qcomtee/release.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#include "qcomtee_private.h" +#include "qcomtee_msg.h"
+static struct workqueue_struct *qcom_tee_release_wq;
+/* Number of all release requests pending for processing. */ +static atomic_t qcom_tee_pending_releases = ATOMIC_INIT(0);
+/* qcom_tee_object_do_release makes direct object invocation to release an object. */ +static void qcom_tee_destroy_user_object(struct work_struct *work) +{
static struct qcom_tee_object_invoke_ctx oic;
static struct qcom_tee_arg args[1] = { 0 };
struct qcom_tee_object *object;
int ret, result;
object = container_of(work, struct qcom_tee_object, work);
ret = __qcom_tee_object_do_invoke(&oic, object, QCOM_TEE_MSG_OBJECT_OP_RELEASE, args,
&result);
/* Is it safe to retry the release? */
if (ret == -EAGAIN) {
queue_work(qcom_tee_release_wq, &object->work);
} else {
if (ret || result)
pr_err("%s: %s release failed, ret = %d (%x).\n",
__func__, qcom_tee_object_name(object), ret, result);
atomic_dec(&qcom_tee_pending_releases);
qcom_tee_object_free(object);
}
+}
+/* qcom_tee_release_tee_object puts object in release work queue. */ +void qcom_tee_release_tee_object(struct qcom_tee_object *object) +{
INIT_WORK(&object->work, qcom_tee_destroy_user_object);
atomic_inc(&qcom_tee_pending_releases);
queue_work(qcom_tee_release_wq, &object->work);
+}
+ssize_t qcom_tee_release_wq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{
return sysfs_emit(buf, "%d\n", atomic_read(&qcom_tee_pending_releases));
+}
+int qcom_tee_release_init(void) +{
qcom_tee_release_wq = alloc_ordered_workqueue("qcom_tee_release_wq", 0);
if (!qcom_tee_release_wq)
return -ENOMEM;
return 0;
+}
+void qcom_tee_release_destroy(void) +{
/* It drains the wq. */
destroy_workqueue(qcom_tee_release_wq);
+} diff --git a/include/linux/firmware/qcom/qcom_tee.h b/include/linux/firmware/qcom/qcom_tee.h new file mode 100644 index 000000000000..90e5e10a0e62 --- /dev/null +++ b/include/linux/firmware/qcom/qcom_tee.h @@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#ifndef __QCOM_TEE_H +#define __QCOM_TEE_H
+#include <linux/kref.h> +#include <linux/completion.h> +#include <linux/workqueue.h>
+struct qcom_tee_object;
+/**
- DOC: Overview
- qcom_tee_object provides object ref-counting, id allocation for objects hosted in
- REE, and necessary message marshaling for Qualcomm TEE (QTEE).
- To invoke an object in QTEE, user calls qcom_tee_object_do_invoke() while passing
- an instance of &struct qcom_tee_object and the requested operation + arguments.
- After the boot, QTEE provides a static object %ROOT_QCOM_TEE_OBJECT (type of
- %QCOM_TEE_OBJECT_TYPE_ROOT). The root object is invoked to pass user's credentials and
- obtain other instances of &struct qcom_tee_object (type of %QCOM_TEE_OBJECT_TYPE_TEE)
- that represents services and TAs in QTEE, see &enum qcom_tee_object_type.
- The object received from QTEE are refcounted. So the owner of these objects can
- issue qcom_tee_object_get(), to increase the refcount, and pass objects to other
- clients, or issue qcom_tee_object_put() to decrease the refcount, and releasing
- the resources in QTEE.
- REE can host services accessible to QTEE. A driver should embed an instance of
- &struct qcom_tee_object in the struct it wants to export to QTEE (it is called
- callback object). It issues qcom_tee_object_user_init() to set the dispatch()
- operation for the callback object and set its type to %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- core.c holds an object table for callback objects. An object id is assigned
- to each callback object which is an index to the object table. QTEE uses these ids
- to reference or invoke callback objects.
- If QTEE invoke a callback object in REE, the dispatch() operation is called in the
- context of thread that called qcom_tee_object_do_invoke(), originally.
- */
+/**
- enum qcom_tee_object_typ - Object types.
- @QCOM_TEE_OBJECT_TYPE_TEE: object hosted on QTEE.
- @QCOM_TEE_OBJECT_TYPE_CB_OBJECT: object hosted on REE.
- @QCOM_TEE_OBJECT_TYPE_ROOT: 'primordial' object.
- @QCOM_TEE_OBJECT_TYPE_NULL: NULL object.
- Primordial object is used for bootstrapping the IPC connection between a REE
- and QTEE. It is invoked by REE when it wants to get a 'client env'.
- */
+enum qcom_tee_object_type {
QCOM_TEE_OBJECT_TYPE_TEE,
QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
QCOM_TEE_OBJECT_TYPE_ROOT,
QCOM_TEE_OBJECT_TYPE_NULL,
+};
+/**
- enum qcom_tee_arg_type - Type of QTEE argument.
- @QCOM_TEE_ARG_TYPE_INV: invalid type.
- @QCOM_TEE_ARG_TYPE_IB: input buffer (IO).
- @QCOM_TEE_ARG_TYPE_OO: output object (OO).
- @QCOM_TEE_ARG_TYPE_OB: output buffer (OB).
- @QCOM_TEE_ARG_TYPE_IO: input object (IO).
- Use invalid type to specify end of argument array.
- */
+enum qcom_tee_arg_type {
QCOM_TEE_ARG_TYPE_INV = 0,
QCOM_TEE_ARG_TYPE_OB,
QCOM_TEE_ARG_TYPE_OO,
QCOM_TEE_ARG_TYPE_IB,
QCOM_TEE_ARG_TYPE_IO,
QCOM_TEE_ARG_TYPE_NR,
+};
+/**
- define QCOM_TEE_ARGS_PER_TYPE - Maximum arguments of specific type.
- QTEE transport protocol limits maximum number of argument of specific type
- (i.e. IB, OB, IO, and OO).
- */
+#define QCOM_TEE_ARGS_PER_TYPE 16
+/* Maximum arguments that can fit in a QTEE message, ignoring the type. */ +#define QCOM_TEE_ARGS_MAX (QCOM_TEE_ARGS_PER_TYPE * (QCOM_TEE_ARG_TYPE_NR - 1))
+struct qcom_tee_buffer {
union {
void *addr;
void __user *uaddr;
};
size_t size;
+};
+/**
- struct qcom_tee_arg - Argument for QTEE object invocation.
- @type: type of argument as &enum qcom_tee_arg_type.
- @flags: extra flags.
- @b: address and size if type of argument is buffer.
- @o: object instance if type of argument is object.
- &qcom_tee_arg.flags only accept %QCOM_TEE_ARG_FLAGS_UADDR for now which states
- that &qcom_tee_arg.b contains userspace address in uaddr.
- */
+struct qcom_tee_arg {
enum qcom_tee_arg_type type;
+/* 'b.uaddr' holds a __user address. */ +#define QCOM_TEE_ARG_FLAGS_UADDR 1
unsigned int flags;
union {
struct qcom_tee_buffer b;
struct qcom_tee_object *o;
};
+};
+static inline int qcom_tee_args_len(struct qcom_tee_arg *args) +{
int i = 0;
while (args[i].type != QCOM_TEE_ARG_TYPE_INV)
i++;
return i;
+}
+#define QCOM_TEE_OIC_FLAG_BUSY BIT(1) /* Context is busy (callback is in progress). */ +#define QCOM_TEE_OIC_FLAG_NOTIFY BIT(2) /* Context needs to notify the current object. */ +#define QCOM_TEE_OIC_FLAG_SHARED BIT(3) /* Context has shared state with QTEE. */
+struct qcom_tee_object_invoke_ctx {
unsigned long flags;
int errno;
/* Current object invoked in this callback context. */
struct qcom_tee_object *object;
/* Arguments passed to dispatch callback (+1 for ending QCOM_TEE_ARG_TYPE_INV). */
struct qcom_tee_arg u[QCOM_TEE_ARGS_MAX + 1];
/* Inbound and Outbound buffers shared with QTEE. */
struct qcom_tee_buffer in_msg; /* Inbound Buffer. */
phys_addr_t in_msg_paddr; /* Physical address of inbound buffer. */
struct qcom_tee_buffer out_msg; /* Outbound Buffer. */
phys_addr_t out_msg_paddr; /* Physical address of outbound buffer. */
/* Extra data attached to this context. */
void *data;
+};
+/**
- qcom_tee_object_do_invoke() - Submit an invocation for an object.
- @oic: context to use for current invocation.
- @object: object being invoked.
- @op: requested operation on object.
- @u: array of argument for the current invocation.
- @result: result returned from QTEE.
- The caller is responsible to keep track of the refcount for each object,
- including @object. On return, the caller loses the ownership of all input object of
- type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- @object can be of %QCOM_TEE_OBJECT_TYPE_ROOT or %QCOM_TEE_OBJECT_TYPE_TEE types.
- Return: On success return 0. On error returns -EINVAL and -ENOSPC if unable to initiate
- the invocation, -EAGAIN if invocation failed and user may retry the invocation.
- Otherwise, -ENODEV on fatal failure.
- */
+int qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u,
int *result);
+/**
- struct qcom_tee_object_operations - Callback object operations.
- @release: release object if QTEE is not using it.
- @dispatch: dispatch the operation requested by QTEE.
- @notify: report status of any pending response submitted by @dispatch.
- Transport may fail (e.g. object table is full) even after @dispatch successfully submitted
- the response. @notify is called to do the necessary cleanup.
- */
+struct qcom_tee_object_operations {
void (*release)(struct qcom_tee_object *object);
int (*dispatch)(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *args);
void (*notify)(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, int err);
+};
+/**
- struct qcom_tee_object - QTEE or REE object.
- @name: object name.
- @refcount: reference counter.
- @object_type: object type as &enum qcom_tee_object_type.
- @info: extra information for object.
- @owner: owning module/driver.
- @ops: callback operations for object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- @work: work for async operation on object.
- @work is currently only used for release object of %QCOM_TEE_OBJECT_TYPE_TEE type.
- */
+struct qcom_tee_object {
const char *name;
struct kref refcount;
enum qcom_tee_object_type object_type;
union object_info {
/* QTEE object id if object_type is %QCOM_TEE_OBJECT_TYPE_TEE. */
unsigned long qtee_id;
} info;
struct module *owner;
struct qcom_tee_object_operations *ops;
struct work_struct work;
+};
+/* Static instances of qcom_tee_object objects. */ +#define NULL_QCOM_TEE_OBJECT ((struct qcom_tee_object *)(0)) +extern struct qcom_tee_object qcom_tee_object_root; +#define ROOT_QCOM_TEE_OBJECT (&qcom_tee_object_root)
+static inline enum qcom_tee_object_type typeof_qcom_tee_object(struct qcom_tee_object *object) +{
if (object == NULL_QCOM_TEE_OBJECT)
return QCOM_TEE_OBJECT_TYPE_NULL;
return object->object_type;
+}
+static inline const char *qcom_tee_object_name(struct qcom_tee_object *object) +{
if (object == NULL_QCOM_TEE_OBJECT)
return "null";
if (!object->name)
return "no-name";
return object->name;
+}
+/**
- __qcom_tee_object_user_init() - Initialize an object for user.
- @object: object to initialize.
- @object_type: type of object as &enum qcom_tee_object_type.
- @ops: instance of callbacks.
- @owner: owning module/driver.
- @fmt: name assigned to the object.
- Return: On success return 0 or <0 on failure.
- */
+int __qcom_tee_object_user_init(struct qcom_tee_object *object, enum qcom_tee_object_type ot,
struct qcom_tee_object_operations *ops, struct module *owner,
const char *fmt, ...);
+#define qcom_tee_object_user_init(obj, ot, ops, fmt, ...) \
__qcom_tee_object_user_init((obj), (ot), (ops), THIS_MODULE, (fmt), __VA_ARGS__)
+/* Object release is RCU protected. */ +int qcom_tee_object_get(struct qcom_tee_object *object); +void qcom_tee_object_put(struct qcom_tee_object *object);
+#define qcom_tee_arg_for_each(i, args) \
for (i = 0; args[i].type != QCOM_TEE_ARG_TYPE_INV; i++)
+/* Next argument of type @type after index @i. */ +int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type type);
+/* Iterate over argument of given type. */ +#define qcom_tee_arg_for_each_type(i, args, at) \
for (i = 0, i = qcom_tee_next_arg_type(args, i, at); \
args[i].type != QCOM_TEE_ARG_TYPE_INV; \
i++, i = qcom_tee_next_arg_type(args, i, at))
+#define qcom_tee_arg_for_each_input_buffer(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IB)
+#define qcom_tee_arg_for_each_output_buffer(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OB)
+#define qcom_tee_arg_for_each_input_object(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_IO)
+#define qcom_tee_arg_for_each_output_object(i, args) \
qcom_tee_arg_for_each_type(i, args, QCOM_TEE_ARG_TYPE_OO)
+#endif /* __QCOM_TEE_H */
-- 2.34.1
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce qcom_tee_object, which represents an object in both QTEE and the kernel. QTEE clients can invoke an instance of qcom_tee_object to access QTEE services. If this invocation produces a new object in QTEE, an instance of qcom_tee_object will be returned.
Similarly, QTEE can request services from the kernel by issuing a callback request, which invokes an instance of qcom_tee_object in the kernel. Any subsystem that exposes a service to QTEE should allocate and initialize an instance of qcom_tee_object with a dispatcher callback that is called when the object is invoked.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 6 + drivers/tee/qcomtee/async.c | 153 ++++++ drivers/tee/qcomtee/core.c | 928 +++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcom_scm.c | 36 ++ drivers/tee/qcomtee/qcomtee_msg.h | 217 ++++++++ drivers/tee/qcomtee/qcomtee_private.h | 47 ++ drivers/tee/qcomtee/release.c | 66 +++ include/linux/firmware/qcom/qcom_tee.h | 284 ++++++++++ 11 files changed, 1749 insertions(+)
[snip]
+/**
- DOC: Overview
- qcom_tee_object provides object ref-counting, id allocation for objects hosted in
- REE, and necessary message marshaling for Qualcomm TEE (QTEE).
- To invoke an object in QTEE, user calls qcom_tee_object_do_invoke() while passing
- an instance of &struct qcom_tee_object and the requested operation + arguments.
- After the boot, QTEE provides a static object %ROOT_QCOM_TEE_OBJECT (type of
- %QCOM_TEE_OBJECT_TYPE_ROOT). The root object is invoked to pass user's credentials and
- obtain other instances of &struct qcom_tee_object (type of %QCOM_TEE_OBJECT_TYPE_TEE)
- that represents services and TAs in QTEE, see &enum qcom_tee_object_type.
- The object received from QTEE are refcounted. So the owner of these objects can
- issue qcom_tee_object_get(), to increase the refcount, and pass objects to other
- clients, or issue qcom_tee_object_put() to decrease the refcount, and releasing
- the resources in QTEE.
- REE can host services accessible to QTEE. A driver should embed an instance of
- &struct qcom_tee_object in the struct it wants to export to QTEE (it is called
- callback object). It issues qcom_tee_object_user_init() to set the dispatch()
- operation for the callback object and set its type to %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- core.c holds an object table for callback objects. An object id is assigned
- to each callback object which is an index to the object table. QTEE uses these ids
- to reference or invoke callback objects.
- If QTEE invoke a callback object in REE, the dispatch() operation is called in the
- context of thread that called qcom_tee_object_do_invoke(), originally.
- */
+/**
- enum qcom_tee_object_typ - Object types.
- @QCOM_TEE_OBJECT_TYPE_TEE: object hosted on QTEE.
- @QCOM_TEE_OBJECT_TYPE_CB_OBJECT: object hosted on REE.
- @QCOM_TEE_OBJECT_TYPE_ROOT: 'primordial' object.
- @QCOM_TEE_OBJECT_TYPE_NULL: NULL object.
- Primordial object is used for bootstrapping the IPC connection between a REE
- and QTEE. It is invoked by REE when it wants to get a 'client env'.
- */
+enum qcom_tee_object_type {
QCOM_TEE_OBJECT_TYPE_TEE,
QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
The second OBJECT seems redundant. How about QCOM_TEE_OBJECT_TYPE_CB or QCOM_TEE_OBJECT_TYPE_CALLBACK instead?
QCOM_TEE_OBJECT_TYPE_ROOT,
QCOM_TEE_OBJECT_TYPE_NULL,
+};
[snip]
Cheers, Jens
On 12/10/2024 9:24 PM, Jens Wiklander wrote:
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce qcom_tee_object, which represents an object in both QTEE and the kernel. QTEE clients can invoke an instance of qcom_tee_object to access QTEE services. If this invocation produces a new object in QTEE, an instance of qcom_tee_object will be returned.
Similarly, QTEE can request services from the kernel by issuing a callback request, which invokes an instance of qcom_tee_object in the kernel. Any subsystem that exposes a service to QTEE should allocate and initialize an instance of qcom_tee_object with a dispatcher callback that is called when the object is invoked.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 6 + drivers/tee/qcomtee/async.c | 153 ++++++ drivers/tee/qcomtee/core.c | 928 +++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcom_scm.c | 36 ++ drivers/tee/qcomtee/qcomtee_msg.h | 217 ++++++++ drivers/tee/qcomtee/qcomtee_private.h | 47 ++ drivers/tee/qcomtee/release.c | 66 +++ include/linux/firmware/qcom/qcom_tee.h | 284 ++++++++++ 11 files changed, 1749 insertions(+)
[snip]
+/**
- DOC: Overview
- qcom_tee_object provides object ref-counting, id allocation for objects hosted in
- REE, and necessary message marshaling for Qualcomm TEE (QTEE).
- To invoke an object in QTEE, user calls qcom_tee_object_do_invoke() while passing
- an instance of &struct qcom_tee_object and the requested operation + arguments.
- After the boot, QTEE provides a static object %ROOT_QCOM_TEE_OBJECT (type of
- %QCOM_TEE_OBJECT_TYPE_ROOT). The root object is invoked to pass user's credentials and
- obtain other instances of &struct qcom_tee_object (type of %QCOM_TEE_OBJECT_TYPE_TEE)
- that represents services and TAs in QTEE, see &enum qcom_tee_object_type.
- The object received from QTEE are refcounted. So the owner of these objects can
- issue qcom_tee_object_get(), to increase the refcount, and pass objects to other
- clients, or issue qcom_tee_object_put() to decrease the refcount, and releasing
- the resources in QTEE.
- REE can host services accessible to QTEE. A driver should embed an instance of
- &struct qcom_tee_object in the struct it wants to export to QTEE (it is called
- callback object). It issues qcom_tee_object_user_init() to set the dispatch()
- operation for the callback object and set its type to %QCOM_TEE_OBJECT_TYPE_CB_OBJECT.
- core.c holds an object table for callback objects. An object id is assigned
- to each callback object which is an index to the object table. QTEE uses these ids
- to reference or invoke callback objects.
- If QTEE invoke a callback object in REE, the dispatch() operation is called in the
- context of thread that called qcom_tee_object_do_invoke(), originally.
- */
+/**
- enum qcom_tee_object_typ - Object types.
- @QCOM_TEE_OBJECT_TYPE_TEE: object hosted on QTEE.
- @QCOM_TEE_OBJECT_TYPE_CB_OBJECT: object hosted on REE.
- @QCOM_TEE_OBJECT_TYPE_ROOT: 'primordial' object.
- @QCOM_TEE_OBJECT_TYPE_NULL: NULL object.
- Primordial object is used for bootstrapping the IPC connection between a REE
- and QTEE. It is invoked by REE when it wants to get a 'client env'.
- */
+enum qcom_tee_object_type {
QCOM_TEE_OBJECT_TYPE_TEE,
QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
The second OBJECT seems redundant. How about QCOM_TEE_OBJECT_TYPE_CB or QCOM_TEE_OBJECT_TYPE_CALLBACK instead?
Sure. I'll rename to QCOM_TEE_OBJECT_TYPE_CALLBACK.
Best Regards, Amir
QCOM_TEE_OBJECT_TYPE_ROOT,
QCOM_TEE_OBJECT_TYPE_NULL,
+};
[snip]
Cheers, Jens
After booting, the kernel provides a static object known as the primordial object. This object is utilized by QTEE for native kernel services such as yield or privileged operations.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/qcomtee/Makefile | 1 + drivers/tee/qcomtee/core.c | 12 +++++-- drivers/tee/qcomtee/primordial_obj.c | 63 +++++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/qcomtee_private.h | 5 +++ 4 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile index 7dc5e6373042..108bc7fdabcb 100644 --- a/drivers/tee/qcomtee/Makefile +++ b/drivers/tee/qcomtee/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_QCOMTEE) += qcomtee.o qcomtee-objs += async.o qcomtee-objs += core.o +qcomtee-objs += primordial_obj.o qcomtee-objs += qcom_scm.o qcomtee-objs += release.o diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c index a949ef4cceee..79f1181cf676 100644 --- a/drivers/tee/qcomtee/core.c +++ b/drivers/tee/qcomtee/core.c @@ -34,9 +34,11 @@ int qcom_tee_next_arg_type(struct qcom_tee_arg *u, int i, enum qcom_tee_arg_type }
/* QTEE expects IDs with QCOM_TEE_MSG_OBJECT_NS_BIT set for object of - * QCOM_TEE_OBJECT_TYPE_CB_OBJECT type. + * QCOM_TEE_OBJECT_TYPE_CB_OBJECT type. The first ID with QCOM_TEE_MSG_OBJECT_NS_BIT set is + * reserved for primordial object. */ -#define QCOM_TEE_OBJECT_ID_START (QCOM_TEE_MSG_OBJECT_NS_BIT + 1) +#define QCOM_TEE_OBJECT_PRIMORDIAL (QCOM_TEE_MSG_OBJECT_NS_BIT) +#define QCOM_TEE_OBJECT_ID_START (QCOM_TEE_OBJECT_PRIMORDIAL + 1) #define QCOM_TEE_OBJECT_ID_END (UINT_MAX)
#define QCOM_TEE_OBJECT_SET(p, type, ...) __QCOM_TEE_OBJECT_SET(p, type, ##__VA_ARGS__, 0UL) @@ -118,7 +120,8 @@ EXPORT_SYMBOL_GPL(qcom_tee_object_get); */ void qcom_tee_object_put(struct qcom_tee_object *object) { - if (object != NULL_QCOM_TEE_OBJECT && + if (object != &qcom_tee_primordial_object && + object != NULL_QCOM_TEE_OBJECT && object != ROOT_QCOM_TEE_OBJECT) kref_put(&object->refcount, qcom_tee_object_release); } @@ -209,6 +212,9 @@ static struct qcom_tee_object *qcom_tee_local_object_get(unsigned int object_id) { struct qcom_tee_object *object;
+ if (object_id == QCOM_TEE_OBJECT_PRIMORDIAL) + return &qcom_tee_primordial_object; + /* We trust QTEE does not mess the refcounts. * It does not issue RELEASE request and qcom_tee_object_get(), simultaneously. */ diff --git a/drivers/tee/qcomtee/primordial_obj.c b/drivers/tee/qcomtee/primordial_obj.c new file mode 100644 index 000000000000..9065074b02e6 --- /dev/null +++ b/drivers/tee/qcomtee/primordial_obj.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/delay.h> +#include "qcomtee_private.h" + +/** + * DOC: Primordial Object + * + * After the boot, REE provides a static object of type %QCOM_TEE_OBJECT_TYPE_CB_OBJECT + * called primordial object. This object is used for native REE services or privileged operations. + * + * We support + * - %QCOM_TEE_OBJECT_OP_YIELD to yield by the thread running in QTEE. + * - %QCOM_TEE_OBJECT_OP_SLEEP to wait for period of time. + */ + +#define QCOM_TEE_OBJECT_OP_YIELD 1 +#define QCOM_TEE_OBJECT_OP_SLEEP 2 + +static int qcom_tee_primordial_object_dispatch(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *primordial_object_unused, + u32 op, struct qcom_tee_arg *args) +{ + int err = 0; + + switch (op) { + case QCOM_TEE_OBJECT_OP_YIELD: + cond_resched(); + /* No output object. */ + oic->data = NULL; + break; + case QCOM_TEE_OBJECT_OP_SLEEP: + /* Check message format matched QCOM_TEE_OBJECT_OP_SLEEP op. */ + if (qcom_tee_args_len(args) != 1 || /* Expect 1 argument. */ + args[0].type != QCOM_TEE_ARG_TYPE_IB || /* Time to sleep in ms. */ + args[0].b.size < sizeof(u32)) /* Buffer should hold a u32. */ + return -EINVAL; + + msleep(*(u32 *)(args[0].b.addr)); + /* No output object. */ + oic->data = NULL; + break; + default: + err = -EINVAL; + } + + return err; +} + +static struct qcom_tee_object_operations qcom_tee_primordial_object_ops = { + .dispatch = qcom_tee_primordial_object_dispatch, +}; + +struct qcom_tee_object qcom_tee_primordial_object = { + .name = "primordial", + .object_type = QCOM_TEE_OBJECT_TYPE_CB_OBJECT, + .ops = &qcom_tee_primordial_object_ops +}; diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h index e3e4ef51c0b2..c718cd2d8463 100644 --- a/drivers/tee/qcomtee/qcomtee_private.h +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -44,4 +44,9 @@ int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, int *result);
+/* OBJECTS: */ + +/* (1) Primordial Object. */ +extern struct qcom_tee_object qcom_tee_primordial_object; + #endif /* QCOM_TEE_PRIVATE_H */
Introduce qcom,tee compatible string.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- .../devicetree/bindings/arm/firmware/qcom,tee.yaml | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml b/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml new file mode 100644 index 000000000000..43b7e8ac944e --- /dev/null +++ b/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/firmware/qcom,tee.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm TEE + +maintainers: + - Amirreza Zarrabi quic_azarrabi@quicinc.com + +description: | + QTEE is a piece of software provide a Trusted Execution Environment using ARM + TrustZone for Qualcomm SoC. + +properties: + $nodename: + const: qcom_tee + + compatible: + const: qcom,tee + +required: + - compatible + +additionalProperties: false + +examples: + - | + firmware { + qcom_tee { + compatible = "qcom,tee"; + }; + };
On 03/12/2024 05:19, Amirreza Zarrabi wrote:
Introduce qcom,tee compatible string.
Why? What is it for? You have entire commit msg for this, instead of repeating subject.
A nit, subject: drop second/last, redundant "devicetree binding". The "dt-bindings" prefix is already stating that these are bindings. See also: https://elixir.bootlin.com/linux/v6.7-rc8/source/Documentation/devicetree/bi...
Also drop driver, bindings are not for drivers.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
.../devicetree/bindings/arm/firmware/qcom,tee.yaml | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml b/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml new file mode 100644 index 000000000000..43b7e8ac944e --- /dev/null +++ b/Documentation/devicetree/bindings/arm/firmware/qcom,tee.yaml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/firmware/qcom,tee.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Qualcomm TEE
+maintainers:
- Amirreza Zarrabi quic_azarrabi@quicinc.com
+description: |
Do not need '|' unless you need to preserve formatting.
- QTEE is a piece of software provide a Trusted Execution Environment using ARM
- TrustZone for Qualcomm SoC.
+properties:
- $nodename:
- const: qcom_tee
No, first it is not correct (see coding style), second is not even needed. Drop.
- compatible:
- const: qcom,tee
One, same interface for all devices? Nothing SoC specific? You are making now a contract, so please carefully analyze it internally what it means.
+required:
- compatible
+additionalProperties: false
+examples:
- |
- firmware {
Drop
qcom_tee {
See DTS coding style.
Node names should be generic. See also an explanation and list of examples (not exhaustive) in DT specification: https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetre...
compatible = "qcom,tee";
No resources? Nothing here? What is the point except of instantiating your driver?
};
- };
Best regards, Krzysztof
Introduce basic support for invoking objects hosted in QTEE and userspace through the TEE subsystem.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- drivers/tee/qcomtee/Makefile | 2 + drivers/tee/qcomtee/call.c | 707 ++++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/core.c | 8 + drivers/tee/qcomtee/qcomtee_private.h | 166 ++++++++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++++++++++++++++++ include/uapi/linux/tee.h | 1 + 6 files changed, 1509 insertions(+)
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile index 108bc7fdabcb..6bf91481fde3 100644 --- a/drivers/tee/qcomtee/Makefile +++ b/drivers/tee/qcomtee/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_QCOMTEE) += qcomtee.o qcomtee-objs += async.o +qcomtee-objs += call.o qcomtee-objs += core.o qcomtee-objs += primordial_obj.o qcomtee-objs += qcom_scm.o qcomtee-objs += release.o +qcomtee-objs += user_obj.o diff --git a/drivers/tee/qcomtee/call.c b/drivers/tee/qcomtee/call.c new file mode 100644 index 000000000000..11bb31836808 --- /dev/null +++ b/drivers/tee/qcomtee/call.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/tee.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/firmware/qcom/qcom_scm.h> + +#include "qcomtee_private.h" + +/** + * enum qcom_tee_object_host - Object host where it is managed. + * @QCOM_TEE_OBJECT_HOST_USER: objects in userspace. + * @QCOM_TEE_OBJECT_HOST_TEE: objects in QTEE. + * + * We refer to object hosted in userspace as 'Local Object' and objects hosted + * in QTEE as 'Remote Object'. + */ +enum qcom_tee_object_host { + QCOM_TEE_OBJECT_HOST_USER, /* Object that is managed in userspace. */ + QCOM_TEE_OBJECT_HOST_TEE, /* Object that is managed in QTEE. */ +}; + +/* Read object ID host information. */ +static enum qcom_tee_object_host qcom_tee_object_host(struct tee_param *param) +{ + if (param->u.objref.flags & QCOM_TEE_OBJREF_FLAG_USER) + return QCOM_TEE_OBJECT_HOST_USER; + return QCOM_TEE_OBJECT_HOST_TEE; +} + +/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context. + * @param: TEE parameter represents @object. + * @object: QTEE object. + * @ctx: context to add the object. + * + * It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued + * qcom_tee_object_get() for @object. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object, + struct qcom_tee_context *ctx) +{ + int ret; + + guard(mutex)(&ctx->lock); + ret = idr_alloc(&ctx->qtee_objects_idr, object, 0, 0, GFP_KERNEL); + if (ret < 0) + return ret; + + param->u.objref.id = ret; + /* QTEE Object: !QCOM_TEE_OBJREF_FLAG_USER. */ + param->u.objref.flags = 0; + + return 0; +} + +static int find_qtee_object(struct qcom_tee_object **object, unsigned long id, + struct qcom_tee_context *ctx) +{ + int err = 0; + + guard(rcu)(); + /* Object release is RCU protected. */ + *object = idr_find(&ctx->qtee_objects_idr, id); + if (!qcom_tee_object_get(*object)) + err = -EINVAL; + + return err; +} + +static void del_qtee_object(unsigned long id, struct qcom_tee_context *ctx) +{ + struct qcom_tee_object *object; + + scoped_guard(mutex, &ctx->lock) + object = idr_remove(&ctx->qtee_objects_idr, id); + qcom_tee_object_put(object); +} + +/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param, + struct qcom_tee_context *ctx) +{ + /* 'qtee_objects_idr' stores QTEE objects only. */ + if (qcom_tee_object_host(param) != QCOM_TEE_OBJECT_HOST_TEE) + return -EINVAL; + return find_qtee_object(object, param->u.objref.id, ctx); +} + +/** + * qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context. + * @param: TEE parameter represents @object. + * @ctx: context to delete the object. + * + * @param returned by qcom_tee_context_add_qtee_object(). + */ +void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx) +{ + /* 'qtee_objects_idr' stores QTEE objects only. */ + if (qcom_tee_object_host(param) == QCOM_TEE_OBJECT_HOST_TEE) + del_qtee_object(param->u.objref.id, ctx); +} + +/** + * qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context. + * @arg: QTEE argument. + * @param: TEE parameter. + * @ctx: context in which the conversion should happen. + * + * It assumes @param is OBJREF. + * It does not set @arg.type; caller should initialize it to a correct + * &enum qcom_tee_arg_type value. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_object *object; + int err; + + if (arg->type != QCOM_TEE_ARG_TYPE_IO && + arg->type != QCOM_TEE_ARG_TYPE_OO) + return -EINVAL; + + /* It is a NULL object?! */ + if (param->u.objref.id == TEE_OBJREF_NULL) { + arg->o = NULL_QCOM_TEE_OBJECT; + + return 0; + } + + switch (qcom_tee_object_host(param)) { + case QCOM_TEE_OBJECT_HOST_USER: + err = qcom_tee_user_param_to_object(&object, param, ctx); + if (err) + break; + + /* Keep a copy for driver as QTEE may release it (e.g. using async msg). */ + qcom_tee_object_get(object); + + break; + case QCOM_TEE_OBJECT_HOST_TEE: + err = qcom_tee_context_find_qtee_object(&object, param, ctx); + + break; + } + + arg->o = err ? NULL_QCOM_TEE_OBJECT : object; + + return err; +} + +/** + * qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context. + * @param: TEE parameter. + * @arg: QTEE argument. + * @ctx: context in which the conversion should happen. + * + * It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO. + * It does not set @param.attr; caller should initialize it to a correct OBJREF type. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_object *object; + int err; + + /* param should be of OBJREF. */ + if (param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT && + param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT && + param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT) + return -EINVAL; + + object = arg->o; + + switch (typeof_qcom_tee_object(object)) { + case QCOM_TEE_OBJECT_TYPE_NULL: + param->u.objref.id = TEE_OBJREF_NULL; + err = 0; + + break; + case QCOM_TEE_OBJECT_TYPE_CB_OBJECT: + err = qcom_tee_user_param_from_object(param, object, ctx); + + break; + case QCOM_TEE_OBJECT_TYPE_TEE: + err = qcom_tee_context_add_qtee_object(param, object, ctx); + + break; + case QCOM_TEE_OBJECT_TYPE_ROOT: + default: + return -EINVAL; + } + + return err; +} + +/** + * qcom_tee_params_to_args() - Convert TEE parameters to QTEE arguments in a context. + * @u: QTEE arguments. + * @params: TEE parameters. + * @num_params: number of elements in the parameter array. + * @ctx: context in which the conversion should happen. + * + * It assumes @u has at least @num_param + 1 entries and has been initialized + * with %QCOM_TEE_ARG_TYPE_INV as &struct qcom_tee_arg.type. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_params_to_args(struct qcom_tee_arg *u, + struct tee_param *params, int num_params, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_object *object; + int i; + + for (i = 0; i < num_params; i++) { + switch (params[i].attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT: + u[i].flags = QCOM_TEE_ARG_FLAGS_UADDR; + u[i].b.uaddr = params[i].u.membuf.uaddr; + u[i].b.size = params[i].u.membuf.size; + + if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT) + u[i].type = QCOM_TEE_ARG_TYPE_IB; + else /* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */ + u[i].type = QCOM_TEE_ARG_TYPE_OB; + + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT: + u[i].type = QCOM_TEE_ARG_TYPE_IO; + if (qcom_tee_objref_to_arg(&u[i], ¶ms[i], ctx)) + goto out_failed; + + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT: + u[i].type = QCOM_TEE_ARG_TYPE_OO; + u[i].o = NULL_QCOM_TEE_OBJECT; + break; + default: /* Unsupported TEE parameters. */ + goto out_failed; + } + } + + return 0; + +out_failed: + + /* On ERROR: */ + /* Put IO objects processed so far. */ + for (--i; i >= 0; i--) { + if (u[i].type != QCOM_TEE_ARG_TYPE_IO) + continue; + + object = u[i].o; + qcom_tee_user_object_set_notify(object, false); + /* For callback object, we hold a reference for the driver, put it. */ + if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT) + qcom_tee_object_put(object); + /* Put QTEE copy of object. */ + qcom_tee_object_put(object); + } + + return -EINVAL; +} + +/** + * qcom_tee_params_from_args() - Convert QTEE arguments to TEE parameters in a context. + * @params: TEE parameters. + * @u: QTEE arguments. + * @num_params: number of elements in the parameter array. + * @ctx: context in which the conversion should happen. + * + * @u should have been already initialized by qcom_tee_params_to_args(). + * This also represents end of a QTEE invocation that started with qcom_tee_params_to_args() + * by releasing %QCOM_TEE_ARG_TYPE_IO objects. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_params_from_args(struct tee_param *params, + struct qcom_tee_arg *u, int num_params, + struct qcom_tee_context *ctx) +{ + int i, np; + + for (np = 0; u[np].type; np++) { + enum qcom_tee_arg_type at = u[np].type; + + if (at == QCOM_TEE_ARG_TYPE_OB) { + /* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */ + params[np].u.value.b = u[np].b.size; + + } else if (at == QCOM_TEE_ARG_TYPE_IO) { + /* IEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT */ + qcom_tee_object_put(u[np].o); + + } else if (at == QCOM_TEE_ARG_TYPE_OO) { + /* TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT */ + if (qcom_tee_objref_from_arg(¶ms[np], &u[np], ctx)) + goto out_failed; + } + } + + return 0; + +out_failed: + + /* On ERROR: */ + /* - Release params associated to QTEE objects in this context so far. */ + for (i = 0; i < np; i++) { + if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) + qcom_tee_context_del_qtee_object(¶ms[i], ctx); + } + /* - Release any IO and OO objects not processed so far. */ + while (u[i].type) { + if (u[i].type == QCOM_TEE_ARG_TYPE_OO || + u[i].type == QCOM_TEE_ARG_TYPE_IO) + qcom_tee_object_put(u[i++].o); + } + + return -EINVAL; +} + +/* TEE Device Ops. */ + +static int qcom_tee_params_check(struct tee_param *params, int num_params) +{ + int io = 0, oo = 0, ib = 0, ob = 0; + int i; + + /* QTEE accepts 64 arguments. */ + if (num_params > QCOM_TEE_ARGS_MAX) + return -EINVAL; + + /* Supported parameter types. */ + for (i = 0; i < num_params; i++) { + switch (params[i].attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT: + ib++; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT: + ob++; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT: + io++; break; + case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT: + oo++; break; + default: + return -EINVAL; + } + } + + /* QTEE accepts 16 arguments of each supported types. */ + if (io > QCOM_TEE_ARGS_PER_TYPE || oo > QCOM_TEE_ARGS_PER_TYPE || + ib > QCOM_TEE_ARGS_PER_TYPE || ob > QCOM_TEE_ARGS_PER_TYPE) + return -EINVAL; + + return 0; +} + +/* Check if user issued a permitted operation on ROOT_QCOM_TEE_OBJECT from userspace. */ +static int qcom_tee_root_object_check(u32 op, struct tee_param *params, int num_params) +{ + /* Some privileged operations recognized by QTEE. */ + if (op == 4 || op == 8 || op == 9) + return -EINVAL; + + /* OP 5 is to register with QTEE by passing credential object as input OBJREF. */ + /* TEE_OBJREF_NULL as credential object represents a privileged client for QTEE, + * only kernel can pass TEE_OBJREF_NULL. + */ + if (op == 5) { + if (num_params != 2) + return -EINVAL; + + if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT && + params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) { + if (params[0].u.objref.id == TEE_OBJREF_NULL) + return -EINVAL; + + } else if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT && + params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT) { + if (params[1].u.objref.id == TEE_OBJREF_NULL) + return -EINVAL; + } + } + + return 0; +} + +/** + * qcom_tee_object_invoke() - Invoke a QTEE object. + * @tee_ctx: TEE context. + * @arg: ioctl arguments. + * @params: parameters for the object. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_object_invoke(struct tee_context *tee_ctx, + struct tee_ioctl_object_invoke_arg *arg, + struct tee_param *params) +{ + struct qcom_tee_object_invoke_ctx *oic __free(kfree) = NULL; + struct qcom_tee_context *ctx = tee_ctx->data; + struct qcom_tee_arg *u __free(kfree) = NULL; + struct qcom_tee_object *object; + int i, ret, result; + + if (qcom_tee_params_check(params, arg->num_params)) + return -EINVAL; + + /* Handle OBJREF reserved operations. */ + if (arg->op == QCOM_TEE_OBJREF_OP_RELEASE) { + del_qtee_object(arg->object, ctx); + + return 0; + } + + /* Unsupported reserved operation. */ + if (arg->op > QCOM_TEE_OBJREF_OP_MIN) + return -EINVAL; + + oic = kzalloc(sizeof(*oic), GFP_KERNEL); + if (!oic) + return -ENOMEM; + + /* +1 for ending QCOM_TEE_ARG_TYPE_INV. */ + u = kzalloc(sizeof(*u) * (arg->num_params + 1), GFP_KERNEL); + if (!u) + return -ENOMEM; + + if (arg->object == TEE_OBJREF_NULL && + !qcom_tee_root_object_check(arg->op, params, arg->num_params)) { + /* Use ROOT if NULL is invoked. */ + object = ROOT_QCOM_TEE_OBJECT; + } else { + /* Get object being invoked. */ + ret = find_qtee_object(&object, arg->object, ctx); + if (ret) + return ret; + } + + ret = qcom_tee_params_to_args(u, params, arg->num_params, ctx); + if (ret) + goto out; + + ret = qcom_tee_object_do_invoke(oic, object, arg->op, u, &result); + if (!ret) { + if (!result) { + /* Only parse QTEE response on SUCCESS. */ + ret = qcom_tee_params_from_args(params, u, arg->num_params, ctx); + } else { + /* Put driver's IO objects copy; get in qcom_tee_params_to_args(). */ + qcom_tee_arg_for_each_input_object(i, u) + qcom_tee_object_put(u[i++].o); + } + } else if (ret != -EAGAIN && ret != -ENODEV) { + /* Unable to initiate a QTEE invocation; cleanup qcom_tee_params_to_args(). */ + qcom_tee_arg_for_each_input_object(i, u) { + qcom_tee_user_object_set_notify(u[i].o, false); + if (typeof_qcom_tee_object(u[i].o) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT) + qcom_tee_object_put(u[i].o); + qcom_tee_object_put(u[i].o); + } + } + + arg->ret = result; +out: + qcom_tee_object_put(object); + + return ret; +} + +/** + * qcom_tee_supp_recv() - Pick a request for the supplicant. + * @tee_ctx: TEE context. + * @op: requested operation on object. + * @num_params: number of elements in the parameter array, updated with number used. + * @params: parameters for @op. + * + * The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT meta parameter. + * On input, it provides a user buffer. This buffer is used for parameters of type + * %TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT in qcom_tee_cb_params_from_args(). + * On output, object id and request id are stored in the meta parameter. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_supp_recv(struct tee_context *tee_ctx, u32 *op, u32 *num_params, + struct tee_param *params) +{ + struct qcom_tee_user_object_request_data data; + struct qcom_tee_context *ctx = tee_ctx->data; + void __user *uaddr; + size_t ubuf_size; + int i, ret; + + if (!*num_params) + return -EINVAL; + + /* We expect the first parameter to be an INOUT + meta parameter. */ + if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT | TEE_IOCTL_PARAM_ATTR_META)) + return -EINVAL; + + /* Others parameters are none. */ + for (i = 1; i < *num_params; i++) + if (params[i].attr) + return -EINVAL; + + if (IS_ALIGNED(params->u.value.a, 8)) + return -EINVAL; + + /* User buffer and size from meta parameter. */ + uaddr = u64_to_user_ptr(params->u.value.a); + ubuf_size = params->u.value.b; + /* Process TEE parameters. +/-1 to ignore meta parameter. */ + ret = qcom_tee_user_object_pop(ctx, params + 1, *num_params - 1, uaddr, ubuf_size, &data); + if (ret) + return ret; + + params->u.value.a = data.object_id; + params->u.value.b = data.id; + params->u.value.c = 0; + *op = data.op; + *num_params = data.np + 1; + + return 0; +} + +/** + * qcom_tee_supp_send() - Pick a request for the supplicant. + * @tee_ctx: TEE context. + * @ret: return value of the request. + * @num_params: number of elements in the parameter array. + * @params: returned parameters. + * + * The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT meta parameter. + * It specifies the request id this response is belong to. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_supp_send(struct tee_context *tee_ctx, u32 errno, u32 num_params, + struct tee_param *params) +{ + struct qcom_tee_context *ctx = tee_ctx->data; + int id; + + if (!num_params) + return -EINVAL; + + /* We expect the first parameter to be an INPUT + meta parameter. */ + if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT | TEE_IOCTL_PARAM_ATTR_META)) + return -EINVAL; + + /* Get the req_id of response. */ + id = params->u.value.a; + + /* Process TEE parameters. +/-1 to ignore meta parameter. */ + return qcom_tee_user_object_submit(ctx, params + 1, num_params - 1, id, errno); +} + +static int qcom_tee_open(struct tee_context *tee_context) +{ + struct qcom_tee_context *ctx __free(kfree) = NULL; + int err; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + err = init_srcu_struct(&ctx->req_srcu); + if (err) + return err; + + ctx->tee_context = tee_context; + idr_init(&ctx->qtee_objects_idr); + idr_init(&ctx->reqs_idr); + mutex_init(&ctx->lock); + init_completion(&ctx->req_c); + kref_init(&ctx->ref_cnt); + + tee_context->data = no_free_ptr(ctx); + + return 0; +} + +static void qcom_tee_release(struct tee_context *tee_context) +{ + struct qcom_tee_context *ctx = tee_context->data; + struct qcom_tee_object *object; + int id; + + /* Process QUEUED or PROCESSING requests. */ + qcom_tee_requests_destroy(ctx); + + /* Release QTEE objects. */ + idr_for_each_entry(&ctx->qtee_objects_idr, object, id) + qcom_tee_object_put(object); + + /* Put the context; wait for all user objects to go away. */ + kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy); +} + +void __qcom_tee_context_destroy(struct kref *ref_cnt) +{ + struct qcom_tee_context *ctx = container_of(ref_cnt, struct qcom_tee_context, ref_cnt); + + idr_destroy(&ctx->qtee_objects_idr); + idr_destroy(&ctx->reqs_idr); + cleanup_srcu_struct(&ctx->req_srcu); + kfree(ctx); +} + +static void qcom_tee_get_version(struct tee_device *teedev, struct tee_ioctl_version_data *vers) +{ + struct tee_ioctl_version_data v = { + .impl_id = TEE_IMPL_ID_QTEE, + .gen_caps = TEE_GEN_CAP_OBJREF, + }; + + *vers = v; +} + +static const struct tee_driver_ops qcom_tee_ops = { + .get_version = qcom_tee_get_version, + .open = qcom_tee_open, + .release = qcom_tee_release, + .object_invoke_func = qcom_tee_object_invoke, + .supp_recv = qcom_tee_supp_recv, + .supp_send = qcom_tee_supp_send, +}; + +static const struct tee_desc qcom_tee_desc = { + .name = "qcom_tee", + .ops = &qcom_tee_ops, + .owner = THIS_MODULE, +}; + +static int qcom_tee_probe(struct platform_device *pdev) +{ + struct tee_device *teedev; + int err; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + teedev = tee_device_alloc(&qcom_tee_desc, NULL, NULL, NULL); + if (IS_ERR(teedev)) + return PTR_ERR(teedev); + + err = tee_device_register(teedev); + if (err) + goto err_unreg_teedev; + + platform_set_drvdata(pdev, teedev); + return 0; + +err_unreg_teedev: + tee_device_unregister(teedev); + + return err; +} + +static void qcom_tee_remove(struct platform_device *pdev) +{ + struct tee_device *teedev = platform_get_drvdata(pdev); + + /* Keep a copy, tee_device_unregister() sets it to NULL. */ + struct tee_shm_pool *pool = teedev->pool; + + /* Wait for users to go away. */ + tee_device_unregister(teedev); + tee_shm_pool_free(pool); +} + +static const struct of_device_id qcom_tee_dt_match[] = { + { .compatible = "qcom,tee" }, + {}, +}; +MODULE_DEVICE_TABLE(of, qcom_tee_dt_match); + +static struct platform_driver qcom_tee_platform_driver = { + .probe = qcom_tee_probe, + .remove = qcom_tee_remove, + .driver = { + .name = "qcom_tee", + .of_match_table = qcom_tee_dt_match, + }, +}; + +int qcom_tee_driver_register(void) +{ + return platform_driver_register(&qcom_tee_platform_driver); +} + +void qcom_tee_driver_unregister(void) +{ + platform_driver_unregister(&qcom_tee_platform_driver); +} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c index 79f1181cf676..545857e117db 100644 --- a/drivers/tee/qcomtee/core.c +++ b/drivers/tee/qcomtee/core.c @@ -904,8 +904,14 @@ static int __init qcom_tee_object_invoke_init(void) if (ret) goto err_kobject_put;
+ ret = qcom_tee_driver_register(); + if (ret) + goto err_remove_group; + return 0;
+err_remove_group: + sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group); err_kobject_put: /* Remove '/sys/firmware/qcom_tee'. */ kobject_put(qcom_tee_object_invoke_kobj); @@ -920,6 +926,8 @@ module_init(qcom_tee_object_invoke_init);
static void __exit qcom_tee_object_invoke_deinit(void) { + qcom_tee_driver_unregister(); + /* Wait for RELEASE operations for QTEE objects. */ qcom_tee_release_destroy(); qcom_tee_msg_buffers_destroy(); diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h index c718cd2d8463..15f358260ed7 100644 --- a/drivers/tee/qcomtee/qcomtee_private.h +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -10,6 +10,14 @@ #include <linux/kobject.h> #include <linux/tee_core.h>
+/* Flags relating to object reference. */ +#define QCOM_TEE_OBJREF_FLAG_USER 1 + +/* Reserved OBJREF operations. */ +/* These operations are not sent to QTEE and handled in driver. */ +#define QCOM_TEE_OBJREF_OP_MIN USHRT_MAX +#define QCOM_TEE_OBJREF_OP_RELEASE (QCOM_TEE_OBJREF_OP_MIN + 1) + struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); void qcom_tee_object_free(struct qcom_tee_object *object);
@@ -44,9 +52,167 @@ int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, int *result);
+/** + * struct qcom_tee_context - Clients or supplicants context. + * @tee_context: TEE context. + * @qtee_objects_idr: QTEE objects in this context. + * @reqs_idr: Requests currently being processed. + * @lock: mutex for @reqs_idr and @qtee_objects_idr. + * @req_srcu: srcu for exclusive access to requests. + * @req_c: completion used when supplicant is waiting for requests. + * @released: state of this context. + * @ref_cnt: ref count. + */ +struct qcom_tee_context { + struct tee_context *tee_context; + + struct idr qtee_objects_idr; + struct idr reqs_idr; + /* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */ + struct mutex lock; + struct srcu_struct req_srcu; + struct completion req_c; + + int released; + + struct kref ref_cnt; +}; + +void __qcom_tee_context_destroy(struct kref *ref_cnt); + +/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context. + * @param: TEE parameter represents @object. + * @object: QTEE object. + * @ctx: context to add the object. + * + * It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued + * qcom_tee_object_get() for @object. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object, + struct qcom_tee_context *ctx); + +/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param, + struct qcom_tee_context *ctx); + +/** + * qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context. + * @param: TEE parameter represents @object. + * @ctx: context to delete the object. + * + * @param returned by qcom_tee_context_add_qtee_object(). + */ +void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx); + +/** + * qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context. + * @arg: QTEE argument. + * @param: TEE parameter. + * @ctx: context in which the conversion should happen. + * + * It assumes @param is OBJREF. + * It does not set @arg.type; caller should initialize it to a correct + * &enum qcom_tee_arg_type value. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param, + struct qcom_tee_context *ctx); + +/** + * qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context. + * @param: TEE parameter. + * @arg: QTEE argument. + * @ctx: context in which the conversion should happen. + * + * It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO. + * It does not set @param.attr; caller should initialize it to a correct OBJREF type. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg, + struct qcom_tee_context *ctx); + +int qcom_tee_driver_register(void); +void qcom_tee_driver_unregister(void); + /* OBJECTS: */
/* (1) Primordial Object. */ extern struct qcom_tee_object qcom_tee_primordial_object;
+/* (2) User Object API. */ + +/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object); + +/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify); + +/** + * qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object. + * @object: object returned. + * @param: TEE parameter. + * @ctx: context in which the conversion should happen. + * + * @param is OBJREF with %TEE_IOCTL_OBJREF_USER flags. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param, + struct qcom_tee_context *ctx); + +/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object, + struct qcom_tee_context *ctx); + +struct qcom_tee_user_object_request_data { + int id; /* Id assigned to the request. */ + u64 object_id; /* Object id being invoked by QTEE. */ + u32 op; /* Requested operation on object. */ + int np; /* Number of parameters in the request.*/ +}; + +/** + * qcom_tee_user_object_pop() - Pop a request for a user object. + * @ctx: context to look for user object. + * @params: parameters for @op. + * @num_params: number of elements in the parameter array. + * @uaddr: user buffer for output MEMBUF parameters. + * @size: size of user buffer @uaddr. + * @data: information for the pop request. + * + * @params is filled along with @data for the picked request. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_object_pop(struct qcom_tee_context *ctx, + struct tee_param *params, int num_params, + void __user *uaddr, size_t size, + struct qcom_tee_user_object_request_data *data); + +/** + * qcom_tee_user_object_submit() - Submit a response for a user object. + * @ctx: context to look for user object. + * @params: returned parameters. + * @num_params: number of elements in the parameter array. + * @id: request id for the response. + * @errno: result of user object invocation. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_object_submit(struct qcom_tee_context *ctx, + struct tee_param *params, int num_params, int id, int errno); + +/** + * qcom_tee_requests_destroy() - Destroy requests in a context. + * @ctx: context for which to destroy requests. + * + * After calling qcom_tee_requests_destroy(), @ctx can not be reused. + * It should be called on @ctx cleanup path. + */ +void qcom_tee_requests_destroy(struct qcom_tee_context *ctx); + #endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/user_obj.c b/drivers/tee/qcomtee/user_obj.c new file mode 100644 index 000000000000..4c671a3ae0de --- /dev/null +++ b/drivers/tee/qcomtee/user_obj.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "qcomtee_private.h" + +/** + * DOC: User Objects aka Supplicants + * + * Any userspace process with access to the TEE device file can behave as a supplicant + * by creating a user object. Any TEE parameter of type OBJREF with %QCOM_TEE_OBJREF_FLAG_USER + * flag set is considered as user object. + * + * A supplicant uses qcom_tee_user_object_pick() (i.e. TEE_IOC_SUPPL_RECV) to receive a + * QTEE user object request and qcom_tee_user_object_submit() (i.e. TEE_IOC_SUPPL_SEND) + * to submit a response. QTEE expects to receive the response, including OB and OO in + * specific order in the message; parameters submitted with qcom_tee_user_object_submit() + * should maintain this order. + */ + +/** + * struct qcom_tee_user_object - User object. + * @object: &struct qcom_tee_object representing this user object. + * @ctx: context for which user object is defined. + * @object_id: object ID in @ctx. + * @nor: notify userspace if object is released. + * + * Any object managed in userspace is represented with this struct. + * If @nor is set, on release a notification message is send back to the userspace. + */ +struct qcom_tee_user_object { + struct qcom_tee_object object; + struct qcom_tee_context *ctx; + u64 object_id; + + bool nor; +}; + +#define to_qcom_tee_user_object(o) container_of((o), struct qcom_tee_user_object, object) + +static struct qcom_tee_object_operations qcom_tee_user_object_ops; + +/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object) +{ + return object != NULL_QCOM_TEE_OBJECT && + typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT && + object->ops == &qcom_tee_user_object_ops; +} + +/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify) +{ + if (is_qcom_tee_user_object(object)) + WRITE_ONCE(to_qcom_tee_user_object(object)->nor, notify); +} + +/** + * enum qcom_tee_req_state - Current state of request. + * @QCOM_TEE_REQ_QUEUED: Request is waiting for supplicant. + * @QCOM_TEE_REQ_PROCESSING: Request has been picked by the supplicant. + * @QCOM_TEE_REQ_PROCESSED: Response has been submitted for the request. + */ +enum qcom_tee_req_state { + QCOM_TEE_REQ_QUEUED = 1, + QCOM_TEE_REQ_PROCESSING, + QCOM_TEE_REQ_PROCESSED, +}; + +/* User requests sent to supplicants. */ +struct qcom_tee_user_req { + enum qcom_tee_req_state state; + + int req_id; /* Request ID. */ + u64 object_id; /* User object ID. */ + u32 op; /* Operation to perform on object. */ + struct qcom_tee_arg *args; /* QTEE arguments for this operation. */ + int errno; /* Result of operation. */ + + struct completion c; /* Completion for whoever wait for results. */ +}; + +/* Static placeholder for a request in PROCESSING state in qcom_tee_context.reqs_idr. + * If the thread initiated the QTEE call using qcom_tee_object_invoke() dies, and supplicant + * is processing the request, we replace the entry in qcom_tee_context.reqs_idr with + * __empty_ureq. So (1) the req_id remains busy and not reused, and (2) supplicant fails to + * submit response and does the necessary rollback. + */ +static struct qcom_tee_user_req __empty_ureq = { .state = QCOM_TEE_REQ_PROCESSING }; + +/* Enqueue a user request for a context. */ +static int qcom_tee_request_enqueue(struct qcom_tee_user_req *ureq, struct qcom_tee_context *ctx) +{ + int ret; + + guard(mutex)(&ctx->lock); + /* Supplicant is dying. */ + if (ctx->released) + return -ENODEV; + + ret = idr_alloc(&ctx->reqs_idr, ureq, 0, 0, GFP_KERNEL); + if (ret < 0) + return ret; + + ureq->req_id = ret; + ureq->state = QCOM_TEE_REQ_QUEUED; + + return 0; +} + +/** + * qcom_tee_requests_destroy() - Destroy requests in a context. + * @ctx: context for which to destroy requests. + */ +void qcom_tee_requests_destroy(struct qcom_tee_context *ctx) +{ + struct qcom_tee_user_req *ureq; + int id; + + guard(mutex)(&ctx->lock); + + /* So qcom_tee_request_enqueue() refuses new requests. */ + ctx->released = 1; + idr_for_each_entry(&ctx->reqs_idr, ureq, id) { + if (ureq == &__empty_ureq) + continue; + /* ureq in QUEUED or PROCESSING state, terminate them. */ + if (ureq->op == QCOM_TEE_OBJREF_OP_RELEASE) { + kfree(ureq); + } else { + ureq->state = QCOM_TEE_REQ_PROCESSED; + ureq->errno = -ENODEV; + complete(&ureq->c); + } + } +} + +/** + * qcom_tee_supp_pop_entry() - Pop the next request in a context. + * @ctx: context from which to pop a request. + * @ubuf_size: size of available buffer for MEMBUF parameters. + * @num_params: number of entries for TEE parameter array. + * + * It does not remove the request from &qcom_tee_context.reqs_idr. + * It checks if @num_params is large enough to fit the next request arguments. + * It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE. + * It updates request state to %QCOM_TEE_REQ_PROCESSING state. + * + * Return: On success return a request or NULL and ERR_PTR on failure. + */ +static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx, + size_t ubuf_size, int num_params) +{ + struct qcom_tee_user_req *ureq; + struct qcom_tee_arg *u; + int i, id; + + guard(mutex)(&ctx->lock); + + /* Find the a QUEUED request. */ + idr_for_each_entry(&ctx->reqs_idr, ureq, id) + if (ureq->state == QCOM_TEE_REQ_QUEUED) + break; + + if (!ureq) + return NULL; + + u = ureq->args; + /* (1) Is there enough TEE parameters? */ + if (num_params < qcom_tee_args_len(u)) + return ERR_PTR(-EINVAL); + + /* (2) Is there enough space to pass input buffers? */ + qcom_tee_arg_for_each_input_buffer(i, u) { + ubuf_size = size_sub(ubuf_size, u[i].b.size); + if (ubuf_size == SIZE_MAX) + return ERR_PTR(-EINVAL); + + ubuf_size = round_down(ubuf_size, 8); + } + + /* Ready to process request 'QUEUED -> PROCESSING'. */ + ureq->state = QCOM_TEE_REQ_PROCESSING; + + return ureq; +} + +/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *object, u32 op, + struct qcom_tee_arg *args) +{ + struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object); + struct qcom_tee_user_req *ureq __free(kfree); + struct qcom_tee_context *ctx = uo->ctx; + int errno; + + ureq = kzalloc(sizeof(*ureq), GFP_KERNEL); + if (!ureq) + return -ENOMEM; + + init_completion(&ureq->c); + ureq->object_id = uo->object_id; + ureq->op = op; + ureq->args = args; + + /* Queue the request. */ + if (qcom_tee_request_enqueue(ureq, ctx)) + return -ENODEV; + + /* Wakeup supplicant to process it. */ + complete(&ctx->req_c); + + /* Wait for supplicant to process the request. */ + /* Supplicant is expected to process request in a timely manner. We wait as KILLABLE, + * in case supplicant and invoke thread both running from a same user process, otherwise + * the process stuck on fatal signal. + */ + if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) { + errno = ureq->errno; + /* On SUCCESS, end_cb_notify frees the request. */ + if (!errno) + oic->data = no_free_ptr(ureq); + } else { + enum qcom_tee_req_state prev_state; + + errno = -ENODEV; + + scoped_guard(mutex, &ctx->lock) { + prev_state = ureq->state; + /* Replace ureq with '__empty_ureq' to keep req_id reserved. */ + if (prev_state == QCOM_TEE_REQ_PROCESSING) + idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id); + /* Remove ureq as supplicant has never seen this request. */ + else if (prev_state == QCOM_TEE_REQ_QUEUED) + idr_remove(&ctx->reqs_idr, ureq->req_id); + } + + /* Wait for exclusive access to ureq. */ + synchronize_srcu(&ctx->req_srcu); + + /* Supplicant did some work for us, we should not discard it. */ + if (prev_state == QCOM_TEE_REQ_PROCESSED) { + errno = ureq->errno; + /* On SUCCESS, end_cb_notify frees the request. */ + if (!errno) + oic->data = no_free_ptr(ureq); + } + } + + return errno; +} + +/* Called after submitting the callback response. */ +static void qcom_tee_user_object_end_cb_notify(struct qcom_tee_object_invoke_ctx *oic, + struct qcom_tee_object *unused_object, int err) +{ + struct qcom_tee_user_req *ureq = oic->data; + struct qcom_tee_arg *u = ureq->args; + struct qcom_tee_object *object; + int i; + + qcom_tee_arg_for_each_output_object(i, u) { + object = u[i].o; + /* If err, drop QTEE copy otherwise just drop driver's copy. */ + if (err && (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)) + qcom_tee_object_put(object); + qcom_tee_object_put(object); + } + + kfree(ureq); +} + +static void qcom_tee_user_object_release(struct qcom_tee_object *object) +{ + struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object); + struct qcom_tee_context *ctx = uo->ctx; + struct qcom_tee_user_req *ureq; + + static struct qcom_tee_arg args[] = { { .type = QCOM_TEE_ARG_TYPE_INV } }; + + if (READ_ONCE(uo->nor)) { + ureq = kzalloc(sizeof(*ureq), GFP_KERNEL); + if (ureq) { + ureq->object_id = uo->object_id; + ureq->op = QCOM_TEE_OBJREF_OP_RELEASE; + ureq->args = args; + + /* Queue the RELEASE request and wake supplicant to process it. */ + qcom_tee_request_enqueue(ureq, ctx); + + complete(&ctx->req_c); + } else { + pr_err("failed to notify user object (%s) release.\n", + qcom_tee_object_name(object)); + } + } + + /* Matching get is in qcom_tee_user_param_to_object(). */ + kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy); + kfree(uo); +} + +static struct qcom_tee_object_operations qcom_tee_user_object_ops = { + .release = qcom_tee_user_object_release, + .notify = qcom_tee_user_object_end_cb_notify, + .dispatch = qcom_tee_user_object_dispatch, +}; + +/** + * qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object. + * @object: object returned. + * @param: TEE parameter. + * @ctx: context in which the conversion should happen. + * + * @param is OBJREF with %QCOM_TEE_OBJREF_FLAG_USER flags. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_user_object *user_object __free(kfree) = NULL; + struct qcom_tee_object *uo; + int err; + + user_object = kzalloc(sizeof(*user_object), GFP_KERNEL); + if (!user_object) + return -ENOMEM; + + user_object->ctx = ctx; + user_object->object_id = param->u.objref.id; + /* By default, always notify userspace on release. */ + user_object->nor = true; + + err = qcom_tee_object_user_init(&user_object->object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT, + &qcom_tee_user_object_ops, "uo-%lu", param->u.objref.id); + if (err) + return err; + + uo = &no_free_ptr(user_object)->object; + /* Keep context alive as user object is alive. */ + kref_get(&ctx->ref_cnt); + + *object = uo; + + return 0; +} + +/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_user_object *uo; + + if (!is_qcom_tee_user_object(object)) + return -EINVAL; + + uo = to_qcom_tee_user_object(object); + /* Sure if the object is in a same context as caller? */ + if (uo->ctx != ctx) + return -EINVAL; + + param->u.objref.id = uo->object_id; + param->u.objref.flags = QCOM_TEE_OBJREF_FLAG_USER; + + /* User objects are valid in the context of userspace; drop the driver copy. */ + qcom_tee_object_put(object); + + return 0; +} + +/** + * qcom_tee_cb_params_from_args() - Convert QTEE arguments to TEE parameters in a context. + * @params: TEE parameters. + * @u: QTEE arguments. + * @num_params: number of elements in the parameter array. + * @ubuf_addr: user buffer for argument of type %QCOM_TEE_ARG_TYPE_IB. + * @ubuf_size: size of user buffer. + * @ctx: context in which the conversion should happen. + * + * It expects @params to have enough entries for @u. Entries in @params are of + * %TEE_IOCTL_PARAM_ATTR_TYPE_NONE. On failure, it puts IO objects. + * + * Return: On success return number of input parameters processed or <0 on failure. + */ +static int qcom_tee_cb_params_from_args(struct tee_param *params, + struct qcom_tee_arg *u, int num_params, + void __user *ubuf_addr, size_t ubuf_size, + struct qcom_tee_context *ctx) +{ + int i, np = 0; + + qcom_tee_arg_for_each(i, u) { + enum qcom_tee_arg_type at = u[i].type; + + if (at == QCOM_TEE_ARG_TYPE_IB) { + params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT; + + /* Underflow already checked in qcom_tee_supp_pop_entry(). */ + ubuf_size = round_down(ubuf_size - u[i].b.size, 8); + params[np].u.membuf.uaddr = (void * __user)(ubuf_addr + ubuf_size); + params[np].u.membuf.size = u[i].b.size; + if (copy_to_user(params[np].u.membuf.uaddr, u[i].b.addr, u[i].b.size)) + goto out_failed; + + np++; + } else if (at == QCOM_TEE_ARG_TYPE_IO) { + params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT; + if (qcom_tee_objref_from_arg(¶ms[np], &u[i], ctx)) + goto out_failed; + + np++; + } + } + + return np; + +out_failed: + + /* On ERROR: */ + /* - Release params associated to QTEE objects in this context so far. */ + for (; np >= 0; np--) { + if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT) + qcom_tee_context_del_qtee_object(¶ms[np], ctx); + } + /* - Release any IO objects not processed so far. */ + while (u[i].type) { + if (u[i].type == QCOM_TEE_ARG_TYPE_IO) + qcom_tee_object_put(u[i++].o); + } + + return -EINVAL; +} + +/** + * qcom_tee_cb_params_to_args() - Convert TEE parameters to QTEE arguments in a context. + * @u: QTEE arguments. + * @params: TEE parameters. + * @num_params: number of elements in the parameter array. + * @ctx: context in which the conversion should happen. + * + * Return: On success return 0 or <0 on failure. + */ +static int qcom_tee_cb_params_to_args(struct qcom_tee_arg *u, + struct tee_param *params, int num_params, + struct qcom_tee_context *ctx) +{ + struct qcom_tee_object *object; + int i, np = 0; + + qcom_tee_arg_for_each(i, u) { + enum qcom_tee_arg_type at = u[i].type; + + if (at == QCOM_TEE_ARG_TYPE_OB) { + if (params[np].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT || + params[np].u.membuf.size > u[i].b.size) + goto out_failed; + + if (copy_from_user(u[i].b.addr, params[np].u.membuf.uaddr, u[i].b.size)) + goto out_failed; + + u[i].b.size = params[np].u.membuf.size; + + np++; + } else if (at == QCOM_TEE_ARG_TYPE_OO) { + if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) { + if (qcom_tee_objref_to_arg(&u[i], ¶ms[np], ctx)) + goto out_failed; + } else { + goto out_failed; + } + + np++; + } + } + + return 0; + +out_failed: + + /* On ERROR: */ + /* Put OO objects processed so far. */ + for (--i; i >= 0; i--) { + if (u[i].type != QCOM_TEE_ARG_TYPE_OO) + continue; + + object = u[i].o; + qcom_tee_user_object_set_notify(object, false); + /* For callback object, we hold a reference for the driver, put it. */ + if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT) + qcom_tee_object_put(object); + /* Put QTEE copy of object. */ + qcom_tee_object_put(object); + } + + return -EINVAL; +} + +/** + * qcom_tee_user_object_pop() - Pop a request for a user object. + * @ctx: context to look for user object. + * @params: parameters for @op. + * @num_params: number of elements in the parameter array. + * @uaddr: user buffer for output MEMBUF parameters. + * @size: size of user buffer @uaddr. + * @data: information for the pop request. + * + * @params is filled along with @data for the picked request. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_object_pop(struct qcom_tee_context *ctx, + struct tee_param *params, int num_params, + void __user *uaddr, size_t size, + struct qcom_tee_user_object_request_data *data) +{ + struct qcom_tee_user_req *ureq; + struct qcom_tee_arg *req_args; + u64 req_object_id; + u32 req_op; + int req_id; + int ret; + + while (1) { + scoped_guard(srcu, &ctx->req_srcu) { + /* Pop a request 'QUEUED -> PROCESSING'. */ + ureq = qcom_tee_supp_pop_entry(ctx, size, num_params); + if (!ureq) + goto wait_for_request; + + /* On failure, issue with params, e.g. not enough space in user buffer. */ + if (IS_ERR(ureq)) + return PTR_ERR(ureq); + + /* ''Prepare user request:'' */ + req_id = ureq->req_id; + req_object_id = ureq->object_id; + req_op = ureq->op; + req_args = ureq->args; + ret = qcom_tee_cb_params_from_args(params, req_args, + num_params, uaddr, + size, ctx); + } + + if (ret >= 0) + break; + + /* On failure, issue with req_args, e.g. invalid object. */ + scoped_guard(mutex, &ctx->lock) { + /* If (!= __empty_req) then 'PROCESSING -> PROCESSED'. */ + if (idr_remove(&ctx->reqs_idr, req_id) == ureq) { + ureq->state = QCOM_TEE_REQ_PROCESSED; + ureq->errno = ret; + /* Send error to QTEE. */ + complete(&ureq->c); + } + } + + /* Try next request. */ + continue; + +wait_for_request: + /* Wait for a new QUEUED request. */ + if (wait_for_completion_interruptible(&ctx->req_c)) + return -ERESTARTSYS; + } + + /* It is a RELEASE message; no one is waiting for result. */ + if (req_op == QCOM_TEE_OBJREF_OP_RELEASE) { + scoped_guard(mutex, &ctx->lock) + idr_remove(&ctx->reqs_idr, req_id); + kfree(ureq); + } + + /* ''Pick a pending request:'' */ + data->id = req_id; + data->object_id = req_object_id; + data->op = req_op; + data->np = ret; + + return 0; +} + +/** + * qcom_tee_user_object_submit() - Submit a response for a user object. + * @ctx: context to look for user object. + * @params: returned parameters. + * @num_params: number of elements in the parameter array. + * @id: request id for the response. + * @errno: result of user object invocation. + * + * Return: On success return 0 or <0 on failure. + */ +int qcom_tee_user_object_submit(struct qcom_tee_context *ctx, + struct tee_param *params, int num_params, int id, int errno) +{ + struct qcom_tee_user_req *ureq; + + scoped_guard(srcu, &ctx->req_srcu) { + scoped_guard(mutex, &ctx->lock) { + ureq = idr_remove(&ctx->reqs_idr, id); + /* Is id invalid, or no one is waiting on response. */ + if (ureq == &__empty_ureq || !ureq) + return -ENODEV; + + ureq->state = QCOM_TEE_REQ_PROCESSED; + } + + ureq->errno = errno; + /* Process params only on SUCCESS. */ + if (!errno) + ureq->errno = qcom_tee_cb_params_to_args(ureq->args, params, + num_params, ctx); + + errno = (!errno && ureq->errno) ? ureq->errno : 0; + /* Send result to QTEE. */ + complete(&ureq->c); + } + + return errno; +} diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 5d33a8009efb..00e599d1582d 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -59,6 +59,7 @@ #define TEE_IMPL_ID_OPTEE 1 #define TEE_IMPL_ID_AMDTEE 2 #define TEE_IMPL_ID_TSTEE 3 +#define TEE_IMPL_ID_QTEE 4
/* * OP-TEE specific capabilities
On 03/12/2024 05:19, Amirreza Zarrabi wrote:
+static const struct tee_desc qcom_tee_desc = {
- .name = "qcom_tee",
- .ops = &qcom_tee_ops,
- .owner = THIS_MODULE,
+};
+static int qcom_tee_probe(struct platform_device *pdev) +{
- struct tee_device *teedev;
- int err;
- if (!qcom_scm_is_available())
return -EPROBE_DEFER;
So this is part of SCM? Instantiate it there instead of creating fake DTS nodes.
- teedev = tee_device_alloc(&qcom_tee_desc, NULL, NULL, NULL);
- if (IS_ERR(teedev))
return PTR_ERR(teedev);
- err = tee_device_register(teedev);
- if (err)
goto err_unreg_teedev;
- platform_set_drvdata(pdev, teedev);
- return 0;
+err_unreg_teedev:
- tee_device_unregister(teedev);
- return err;
+}
+static void qcom_tee_remove(struct platform_device *pdev) +{
- struct tee_device *teedev = platform_get_drvdata(pdev);
- /* Keep a copy, tee_device_unregister() sets it to NULL. */
- struct tee_shm_pool *pool = teedev->pool;
- /* Wait for users to go away. */
- tee_device_unregister(teedev);
- tee_shm_pool_free(pool);
+}
+static const struct of_device_id qcom_tee_dt_match[] = {
- { .compatible = "qcom,tee" },
- {},
+}; +MODULE_DEVICE_TABLE(of, qcom_tee_dt_match);
+static struct platform_driver qcom_tee_platform_driver = {
- .probe = qcom_tee_probe,
- .remove = qcom_tee_remove,
- .driver = {
.name = "qcom_tee",
.of_match_table = qcom_tee_dt_match,
- },
+};
+int qcom_tee_driver_register(void) +{
- return platform_driver_register(&qcom_tee_platform_driver);
+}
+void qcom_tee_driver_unregister(void) +{
- platform_driver_unregister(&qcom_tee_platform_driver);
+}
Why open-coding typical module platform driver macro?
Best regards, Krzysztof
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce basic support for invoking objects hosted in QTEE and userspace through the TEE subsystem.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/qcomtee/Makefile | 2 + drivers/tee/qcomtee/call.c | 707 ++++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/core.c | 8 + drivers/tee/qcomtee/qcomtee_private.h | 166 ++++++++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++++++++++++++++++ include/uapi/linux/tee.h | 1 + 6 files changed, 1509 insertions(+)
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile index 108bc7fdabcb..6bf91481fde3 100644 --- a/drivers/tee/qcomtee/Makefile +++ b/drivers/tee/qcomtee/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_QCOMTEE) += qcomtee.o qcomtee-objs += async.o +qcomtee-objs += call.o qcomtee-objs += core.o qcomtee-objs += primordial_obj.o qcomtee-objs += qcom_scm.o qcomtee-objs += release.o +qcomtee-objs += user_obj.o diff --git a/drivers/tee/qcomtee/call.c b/drivers/tee/qcomtee/call.c new file mode 100644 index 000000000000..11bb31836808 --- /dev/null +++ b/drivers/tee/qcomtee/call.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/tee.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/firmware/qcom/qcom_scm.h>
+#include "qcomtee_private.h"
+/**
- enum qcom_tee_object_host - Object host where it is managed.
- @QCOM_TEE_OBJECT_HOST_USER: objects in userspace.
- @QCOM_TEE_OBJECT_HOST_TEE: objects in QTEE.
- We refer to object hosted in userspace as 'Local Object' and objects hosted
- in QTEE as 'Remote Object'.
- */
+enum qcom_tee_object_host {
QCOM_TEE_OBJECT_HOST_USER, /* Object that is managed in userspace. */
QCOM_TEE_OBJECT_HOST_TEE, /* Object that is managed in QTEE. */
+};
+/* Read object ID host information. */ +static enum qcom_tee_object_host qcom_tee_object_host(struct tee_param *param) +{
if (param->u.objref.flags & QCOM_TEE_OBJREF_FLAG_USER)
return QCOM_TEE_OBJECT_HOST_USER;
return QCOM_TEE_OBJECT_HOST_TEE;
+}
+/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context.
- @param: TEE parameter represents @object.
- @object: QTEE object.
- @ctx: context to add the object.
- It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued
- qcom_tee_object_get() for @object.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx)
+{
int ret;
guard(mutex)(&ctx->lock);
ret = idr_alloc(&ctx->qtee_objects_idr, object, 0, 0, GFP_KERNEL);
if (ret < 0)
return ret;
param->u.objref.id = ret;
/* QTEE Object: !QCOM_TEE_OBJREF_FLAG_USER. */
param->u.objref.flags = 0;
return 0;
+}
+static int find_qtee_object(struct qcom_tee_object **object, unsigned long id,
struct qcom_tee_context *ctx)
+{
int err = 0;
guard(rcu)();
/* Object release is RCU protected. */
*object = idr_find(&ctx->qtee_objects_idr, id);
if (!qcom_tee_object_get(*object))
err = -EINVAL;
return err;
+}
+static void del_qtee_object(unsigned long id, struct qcom_tee_context *ctx) +{
struct qcom_tee_object *object;
scoped_guard(mutex, &ctx->lock)
object = idr_remove(&ctx->qtee_objects_idr, id);
qcom_tee_object_put(object);
+}
+/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
/* 'qtee_objects_idr' stores QTEE objects only. */
if (qcom_tee_object_host(param) != QCOM_TEE_OBJECT_HOST_TEE)
return -EINVAL;
return find_qtee_object(object, param->u.objref.id, ctx);
+}
+/**
- qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context.
- @param: TEE parameter represents @object.
- @ctx: context to delete the object.
- @param returned by qcom_tee_context_add_qtee_object().
- */
+void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx) +{
/* 'qtee_objects_idr' stores QTEE objects only. */
if (qcom_tee_object_host(param) == QCOM_TEE_OBJECT_HOST_TEE)
del_qtee_object(param->u.objref.id, ctx);
+}
+/**
- qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context.
- @arg: QTEE argument.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- It assumes @param is OBJREF.
- It does not set @arg.type; caller should initialize it to a correct
- &enum qcom_tee_arg_type value.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int err;
if (arg->type != QCOM_TEE_ARG_TYPE_IO &&
arg->type != QCOM_TEE_ARG_TYPE_OO)
return -EINVAL;
/* It is a NULL object?! */
if (param->u.objref.id == TEE_OBJREF_NULL) {
arg->o = NULL_QCOM_TEE_OBJECT;
return 0;
}
switch (qcom_tee_object_host(param)) {
case QCOM_TEE_OBJECT_HOST_USER:
err = qcom_tee_user_param_to_object(&object, param, ctx);
if (err)
break;
/* Keep a copy for driver as QTEE may release it (e.g. using async msg). */
qcom_tee_object_get(object);
break;
case QCOM_TEE_OBJECT_HOST_TEE:
err = qcom_tee_context_find_qtee_object(&object, param, ctx);
break;
}
arg->o = err ? NULL_QCOM_TEE_OBJECT : object;
return err;
+}
+/**
- qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context.
- @param: TEE parameter.
- @arg: QTEE argument.
- @ctx: context in which the conversion should happen.
- It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO.
- It does not set @param.attr; caller should initialize it to a correct OBJREF type.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int err;
/* param should be of OBJREF. */
if (param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT &&
param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT &&
param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT)
return -EINVAL;
object = arg->o;
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_NULL:
param->u.objref.id = TEE_OBJREF_NULL;
err = 0;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
err = qcom_tee_user_param_from_object(param, object, ctx);
break;
case QCOM_TEE_OBJECT_TYPE_TEE:
err = qcom_tee_context_add_qtee_object(param, object, ctx);
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
default:
return -EINVAL;
}
return err;
+}
+/**
- qcom_tee_params_to_args() - Convert TEE parameters to QTEE arguments in a context.
- @u: QTEE arguments.
- @params: TEE parameters.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- It assumes @u has at least @num_param + 1 entries and has been initialized
- with %QCOM_TEE_ARG_TYPE_INV as &struct qcom_tee_arg.type.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_params_to_args(struct qcom_tee_arg *u,
struct tee_param *params, int num_params,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int i;
for (i = 0; i < num_params; i++) {
switch (params[i].attr) {
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
u[i].flags = QCOM_TEE_ARG_FLAGS_UADDR;
u[i].b.uaddr = params[i].u.membuf.uaddr;
u[i].b.size = params[i].u.membuf.size;
if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT)
u[i].type = QCOM_TEE_ARG_TYPE_IB;
else /* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */
u[i].type = QCOM_TEE_ARG_TYPE_OB;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
u[i].type = QCOM_TEE_ARG_TYPE_IO;
if (qcom_tee_objref_to_arg(&u[i], ¶ms[i], ctx))
goto out_failed;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
u[i].type = QCOM_TEE_ARG_TYPE_OO;
u[i].o = NULL_QCOM_TEE_OBJECT;
break;
default: /* Unsupported TEE parameters. */
goto out_failed;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* Put IO objects processed so far. */
for (--i; i >= 0; i--) {
if (u[i].type != QCOM_TEE_ARG_TYPE_IO)
continue;
object = u[i].o;
qcom_tee_user_object_set_notify(object, false);
/* For callback object, we hold a reference for the driver, put it. */
if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(object);
/* Put QTEE copy of object. */
qcom_tee_object_put(object);
}
return -EINVAL;
+}
+/**
- qcom_tee_params_from_args() - Convert QTEE arguments to TEE parameters in a context.
- @params: TEE parameters.
- @u: QTEE arguments.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- @u should have been already initialized by qcom_tee_params_to_args().
- This also represents end of a QTEE invocation that started with qcom_tee_params_to_args()
- by releasing %QCOM_TEE_ARG_TYPE_IO objects.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_params_from_args(struct tee_param *params,
struct qcom_tee_arg *u, int num_params,
struct qcom_tee_context *ctx)
+{
int i, np;
for (np = 0; u[np].type; np++) {
enum qcom_tee_arg_type at = u[np].type;
if (at == QCOM_TEE_ARG_TYPE_OB) {
/* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */
params[np].u.value.b = u[np].b.size;
} else if (at == QCOM_TEE_ARG_TYPE_IO) {
/* IEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT */
qcom_tee_object_put(u[np].o);
} else if (at == QCOM_TEE_ARG_TYPE_OO) {
/* TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT */
if (qcom_tee_objref_from_arg(¶ms[np], &u[np], ctx))
goto out_failed;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* - Release params associated to QTEE objects in this context so far. */
for (i = 0; i < np; i++) {
if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT)
qcom_tee_context_del_qtee_object(¶ms[i], ctx);
}
/* - Release any IO and OO objects not processed so far. */
while (u[i].type) {
if (u[i].type == QCOM_TEE_ARG_TYPE_OO ||
u[i].type == QCOM_TEE_ARG_TYPE_IO)
qcom_tee_object_put(u[i++].o);
}
return -EINVAL;
+}
+/* TEE Device Ops. */
+static int qcom_tee_params_check(struct tee_param *params, int num_params) +{
int io = 0, oo = 0, ib = 0, ob = 0;
int i;
/* QTEE accepts 64 arguments. */
if (num_params > QCOM_TEE_ARGS_MAX)
return -EINVAL;
/* Supported parameter types. */
for (i = 0; i < num_params; i++) {
switch (params[i].attr) {
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
ib++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
ob++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
io++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
oo++; break;
default:
return -EINVAL;
}
}
/* QTEE accepts 16 arguments of each supported types. */
if (io > QCOM_TEE_ARGS_PER_TYPE || oo > QCOM_TEE_ARGS_PER_TYPE ||
ib > QCOM_TEE_ARGS_PER_TYPE || ob > QCOM_TEE_ARGS_PER_TYPE)
return -EINVAL;
return 0;
+}
+/* Check if user issued a permitted operation on ROOT_QCOM_TEE_OBJECT from userspace. */ +static int qcom_tee_root_object_check(u32 op, struct tee_param *params, int num_params) +{
/* Some privileged operations recognized by QTEE. */
if (op == 4 || op == 8 || op == 9)
return -EINVAL;
/* OP 5 is to register with QTEE by passing credential object as input OBJREF. */
/* TEE_OBJREF_NULL as credential object represents a privileged client for QTEE,
* only kernel can pass TEE_OBJREF_NULL.
*/
if (op == 5) {
if (num_params != 2)
return -EINVAL;
if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT &&
params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) {
if (params[0].u.objref.id == TEE_OBJREF_NULL)
return -EINVAL;
} else if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT &&
params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT) {
if (params[1].u.objref.id == TEE_OBJREF_NULL)
return -EINVAL;
}
}
return 0;
+}
+/**
- qcom_tee_object_invoke() - Invoke a QTEE object.
- @tee_ctx: TEE context.
- @arg: ioctl arguments.
- @params: parameters for the object.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_invoke(struct tee_context *tee_ctx,
struct tee_ioctl_object_invoke_arg *arg,
struct tee_param *params)
+{
struct qcom_tee_object_invoke_ctx *oic __free(kfree) = NULL;
struct qcom_tee_context *ctx = tee_ctx->data;
struct qcom_tee_arg *u __free(kfree) = NULL;
struct qcom_tee_object *object;
int i, ret, result;
if (qcom_tee_params_check(params, arg->num_params))
return -EINVAL;
/* Handle OBJREF reserved operations. */
if (arg->op == QCOM_TEE_OBJREF_OP_RELEASE) {
del_qtee_object(arg->object, ctx);
return 0;
}
/* Unsupported reserved operation. */
if (arg->op > QCOM_TEE_OBJREF_OP_MIN)
return -EINVAL;
oic = kzalloc(sizeof(*oic), GFP_KERNEL);
if (!oic)
return -ENOMEM;
/* +1 for ending QCOM_TEE_ARG_TYPE_INV. */
u = kzalloc(sizeof(*u) * (arg->num_params + 1), GFP_KERNEL);
Please use kcalloc().
if (!u)
return -ENOMEM;
if (arg->object == TEE_OBJREF_NULL &&
!qcom_tee_root_object_check(arg->op, params, arg->num_params)) {
/* Use ROOT if NULL is invoked. */
object = ROOT_QCOM_TEE_OBJECT;
} else {
/* Get object being invoked. */
ret = find_qtee_object(&object, arg->object, ctx);
if (ret)
return ret;
}
ret = qcom_tee_params_to_args(u, params, arg->num_params, ctx);
if (ret)
goto out;
ret = qcom_tee_object_do_invoke(oic, object, arg->op, u, &result);
if (!ret) {
if (!result) {
/* Only parse QTEE response on SUCCESS. */
ret = qcom_tee_params_from_args(params, u, arg->num_params, ctx);
} else {
/* Put driver's IO objects copy; get in qcom_tee_params_to_args(). */
qcom_tee_arg_for_each_input_object(i, u)
qcom_tee_object_put(u[i++].o);
}
} else if (ret != -EAGAIN && ret != -ENODEV) {
/* Unable to initiate a QTEE invocation; cleanup qcom_tee_params_to_args(). */
qcom_tee_arg_for_each_input_object(i, u) {
qcom_tee_user_object_set_notify(u[i].o, false);
if (typeof_qcom_tee_object(u[i].o) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(u[i].o);
qcom_tee_object_put(u[i].o);
}
}
arg->ret = result;
+out:
qcom_tee_object_put(object);
return ret;
+}
+/**
- qcom_tee_supp_recv() - Pick a request for the supplicant.
- @tee_ctx: TEE context.
- @op: requested operation on object.
- @num_params: number of elements in the parameter array, updated with number used.
- @params: parameters for @op.
- The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT meta parameter.
- On input, it provides a user buffer. This buffer is used for parameters of type
- %TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT in qcom_tee_cb_params_from_args().
- On output, object id and request id are stored in the meta parameter.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_supp_recv(struct tee_context *tee_ctx, u32 *op, u32 *num_params,
struct tee_param *params)
+{
struct qcom_tee_user_object_request_data data;
struct qcom_tee_context *ctx = tee_ctx->data;
void __user *uaddr;
size_t ubuf_size;
int i, ret;
if (!*num_params)
return -EINVAL;
/* We expect the first parameter to be an INOUT + meta parameter. */
if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT | TEE_IOCTL_PARAM_ATTR_META))
return -EINVAL;
/* Others parameters are none. */
for (i = 1; i < *num_params; i++)
if (params[i].attr)
return -EINVAL;
if (IS_ALIGNED(params->u.value.a, 8))
return -EINVAL;
/* User buffer and size from meta parameter. */
uaddr = u64_to_user_ptr(params->u.value.a);
ubuf_size = params->u.value.b;
/* Process TEE parameters. +/-1 to ignore meta parameter. */
ret = qcom_tee_user_object_pop(ctx, params + 1, *num_params - 1, uaddr, ubuf_size, &data);
if (ret)
return ret;
params->u.value.a = data.object_id;
params->u.value.b = data.id;
params->u.value.c = 0;
*op = data.op;
*num_params = data.np + 1;
return 0;
+}
+/**
- qcom_tee_supp_send() - Pick a request for the supplicant.
- @tee_ctx: TEE context.
- @ret: return value of the request.
- @num_params: number of elements in the parameter array.
- @params: returned parameters.
- The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT meta parameter.
- It specifies the request id this response is belong to.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_supp_send(struct tee_context *tee_ctx, u32 errno, u32 num_params,
struct tee_param *params)
+{
struct qcom_tee_context *ctx = tee_ctx->data;
int id;
if (!num_params)
return -EINVAL;
/* We expect the first parameter to be an INPUT + meta parameter. */
if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT | TEE_IOCTL_PARAM_ATTR_META))
return -EINVAL;
/* Get the req_id of response. */
id = params->u.value.a;
/* Process TEE parameters. +/-1 to ignore meta parameter. */
return qcom_tee_user_object_submit(ctx, params + 1, num_params - 1, id, errno);
+}
+static int qcom_tee_open(struct tee_context *tee_context) +{
struct qcom_tee_context *ctx __free(kfree) = NULL;
int err;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
err = init_srcu_struct(&ctx->req_srcu);
if (err)
return err;
ctx->tee_context = tee_context;
idr_init(&ctx->qtee_objects_idr);
idr_init(&ctx->reqs_idr);
mutex_init(&ctx->lock);
init_completion(&ctx->req_c);
kref_init(&ctx->ref_cnt);
tee_context->data = no_free_ptr(ctx);
return 0;
+}
+static void qcom_tee_release(struct tee_context *tee_context) +{
struct qcom_tee_context *ctx = tee_context->data;
struct qcom_tee_object *object;
int id;
/* Process QUEUED or PROCESSING requests. */
qcom_tee_requests_destroy(ctx);
/* Release QTEE objects. */
idr_for_each_entry(&ctx->qtee_objects_idr, object, id)
qcom_tee_object_put(object);
/* Put the context; wait for all user objects to go away. */
kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy);
+}
+void __qcom_tee_context_destroy(struct kref *ref_cnt) +{
struct qcom_tee_context *ctx = container_of(ref_cnt, struct qcom_tee_context, ref_cnt);
idr_destroy(&ctx->qtee_objects_idr);
idr_destroy(&ctx->reqs_idr);
cleanup_srcu_struct(&ctx->req_srcu);
kfree(ctx);
+}
+static void qcom_tee_get_version(struct tee_device *teedev, struct tee_ioctl_version_data *vers) +{
struct tee_ioctl_version_data v = {
.impl_id = TEE_IMPL_ID_QTEE,
.gen_caps = TEE_GEN_CAP_OBJREF,
};
*vers = v;
+}
+static const struct tee_driver_ops qcom_tee_ops = {
.get_version = qcom_tee_get_version,
.open = qcom_tee_open,
.release = qcom_tee_release,
.object_invoke_func = qcom_tee_object_invoke,
.supp_recv = qcom_tee_supp_recv,
.supp_send = qcom_tee_supp_send,
+};
+static const struct tee_desc qcom_tee_desc = {
.name = "qcom_tee",
.ops = &qcom_tee_ops,
.owner = THIS_MODULE,
+};
+static int qcom_tee_probe(struct platform_device *pdev) +{
struct tee_device *teedev;
int err;
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
teedev = tee_device_alloc(&qcom_tee_desc, NULL, NULL, NULL);
if (IS_ERR(teedev))
return PTR_ERR(teedev);
err = tee_device_register(teedev);
if (err)
goto err_unreg_teedev;
platform_set_drvdata(pdev, teedev);
return 0;
+err_unreg_teedev:
tee_device_unregister(teedev);
return err;
+}
+static void qcom_tee_remove(struct platform_device *pdev) +{
struct tee_device *teedev = platform_get_drvdata(pdev);
/* Keep a copy, tee_device_unregister() sets it to NULL. */
teedev shouldn't be accessed after a call to tee_device_unregister()
struct tee_shm_pool *pool = teedev->pool;
/* Wait for users to go away. */
tee_device_unregister(teedev);
tee_shm_pool_free(pool);
Why? You supplied NULL when tee_device_alloc() was called.
+}
+static const struct of_device_id qcom_tee_dt_match[] = {
{ .compatible = "qcom,tee" },
{},
+}; +MODULE_DEVICE_TABLE(of, qcom_tee_dt_match);
+static struct platform_driver qcom_tee_platform_driver = {
.probe = qcom_tee_probe,
.remove = qcom_tee_remove,
.driver = {
.name = "qcom_tee",
.of_match_table = qcom_tee_dt_match,
},
+};
+int qcom_tee_driver_register(void) +{
return platform_driver_register(&qcom_tee_platform_driver);
+}
+void qcom_tee_driver_unregister(void) +{
platform_driver_unregister(&qcom_tee_platform_driver);
+} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c index 79f1181cf676..545857e117db 100644 --- a/drivers/tee/qcomtee/core.c +++ b/drivers/tee/qcomtee/core.c @@ -904,8 +904,14 @@ static int __init qcom_tee_object_invoke_init(void) if (ret) goto err_kobject_put;
ret = qcom_tee_driver_register();
if (ret)
goto err_remove_group;
return 0;
+err_remove_group:
sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group);
err_kobject_put: /* Remove '/sys/firmware/qcom_tee'. */ kobject_put(qcom_tee_object_invoke_kobj); @@ -920,6 +926,8 @@ module_init(qcom_tee_object_invoke_init);
static void __exit qcom_tee_object_invoke_deinit(void) {
qcom_tee_driver_unregister();
/* Wait for RELEASE operations for QTEE objects. */ qcom_tee_release_destroy(); qcom_tee_msg_buffers_destroy();
diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h index c718cd2d8463..15f358260ed7 100644 --- a/drivers/tee/qcomtee/qcomtee_private.h +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -10,6 +10,14 @@ #include <linux/kobject.h> #include <linux/tee_core.h>
+/* Flags relating to object reference. */ +#define QCOM_TEE_OBJREF_FLAG_USER 1
+/* Reserved OBJREF operations. */ +/* These operations are not sent to QTEE and handled in driver. */ +#define QCOM_TEE_OBJREF_OP_MIN USHRT_MAX +#define QCOM_TEE_OBJREF_OP_RELEASE (QCOM_TEE_OBJREF_OP_MIN + 1)
struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); void qcom_tee_object_free(struct qcom_tee_object *object);
@@ -44,9 +52,167 @@ int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, int *result);
+/**
- struct qcom_tee_context - Clients or supplicants context.
- @tee_context: TEE context.
- @qtee_objects_idr: QTEE objects in this context.
- @reqs_idr: Requests currently being processed.
- @lock: mutex for @reqs_idr and @qtee_objects_idr.
- @req_srcu: srcu for exclusive access to requests.
- @req_c: completion used when supplicant is waiting for requests.
- @released: state of this context.
- @ref_cnt: ref count.
- */
+struct qcom_tee_context {
Other drivers call their conterpart of this struct *_context_data. Using the same pattern here makes it easier to recognize the struct in the rest of the code.
struct tee_context *tee_context;
struct idr qtee_objects_idr;
struct idr reqs_idr;
/* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */
struct mutex lock;
struct srcu_struct req_srcu;
Why do you use this synchronization primitive? I don't know enough about this primitive to tell if you use it for the right purpose so perhaps you can help me understand which properties you need.
struct completion req_c;
int released;
struct kref ref_cnt;
Why does this struct need a different lifetime than struct tee_context?
+};
+void __qcom_tee_context_destroy(struct kref *ref_cnt);
+/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context.
- @param: TEE parameter represents @object.
- @object: QTEE object.
- @ctx: context to add the object.
- It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued
- qcom_tee_object_get() for @object.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx);
+/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx);
+/**
- qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context.
- @param: TEE parameter represents @object.
- @ctx: context to delete the object.
- @param returned by qcom_tee_context_add_qtee_object().
- */
+void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx);
+/**
- qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context.
- @arg: QTEE argument.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- It assumes @param is OBJREF.
- It does not set @arg.type; caller should initialize it to a correct
- &enum qcom_tee_arg_type value.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param,
struct qcom_tee_context *ctx);
+/**
- qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context.
- @param: TEE parameter.
- @arg: QTEE argument.
- @ctx: context in which the conversion should happen.
- It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO.
- It does not set @param.attr; caller should initialize it to a correct OBJREF type.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg,
struct qcom_tee_context *ctx);
+int qcom_tee_driver_register(void); +void qcom_tee_driver_unregister(void);
/* OBJECTS: */
/* (1) Primordial Object. */ extern struct qcom_tee_object qcom_tee_primordial_object;
+/* (2) User Object API. */
+/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object);
+/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify);
+/**
- qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object.
- @object: object returned.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- @param is OBJREF with %TEE_IOCTL_OBJREF_USER flags.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx);
+/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx);
+struct qcom_tee_user_object_request_data {
int id; /* Id assigned to the request. */
u64 object_id; /* Object id being invoked by QTEE. */
u32 op; /* Requested operation on object. */
int np; /* Number of parameters in the request.*/
+};
+/**
- qcom_tee_user_object_pop() - Pop a request for a user object.
- @ctx: context to look for user object.
- @params: parameters for @op.
- @num_params: number of elements in the parameter array.
- @uaddr: user buffer for output MEMBUF parameters.
- @size: size of user buffer @uaddr.
- @data: information for the pop request.
- @params is filled along with @data for the picked request.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_pop(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params,
void __user *uaddr, size_t size,
struct qcom_tee_user_object_request_data *data);
+/**
- qcom_tee_user_object_submit() - Submit a response for a user object.
- @ctx: context to look for user object.
- @params: returned parameters.
- @num_params: number of elements in the parameter array.
- @id: request id for the response.
- @errno: result of user object invocation.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_submit(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params, int id, int errno);
+/**
- qcom_tee_requests_destroy() - Destroy requests in a context.
- @ctx: context for which to destroy requests.
- After calling qcom_tee_requests_destroy(), @ctx can not be reused.
- It should be called on @ctx cleanup path.
- */
+void qcom_tee_requests_destroy(struct qcom_tee_context *ctx);
#endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/user_obj.c b/drivers/tee/qcomtee/user_obj.c new file mode 100644 index 000000000000..4c671a3ae0de --- /dev/null +++ b/drivers/tee/qcomtee/user_obj.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include "qcomtee_private.h"
+/**
- DOC: User Objects aka Supplicants
- Any userspace process with access to the TEE device file can behave as a supplicant
- by creating a user object. Any TEE parameter of type OBJREF with %QCOM_TEE_OBJREF_FLAG_USER
- flag set is considered as user object.
- A supplicant uses qcom_tee_user_object_pick() (i.e. TEE_IOC_SUPPL_RECV) to receive a
- QTEE user object request and qcom_tee_user_object_submit() (i.e. TEE_IOC_SUPPL_SEND)
- to submit a response. QTEE expects to receive the response, including OB and OO in
- specific order in the message; parameters submitted with qcom_tee_user_object_submit()
- should maintain this order.
- */
+/**
- struct qcom_tee_user_object - User object.
- @object: &struct qcom_tee_object representing this user object.
- @ctx: context for which user object is defined.
- @object_id: object ID in @ctx.
- @nor: notify userspace if object is released.
- Any object managed in userspace is represented with this struct.
- If @nor is set, on release a notification message is send back to the userspace.
- */
+struct qcom_tee_user_object {
struct qcom_tee_object object;
struct qcom_tee_context *ctx;
u64 object_id;
bool nor;
+};
+#define to_qcom_tee_user_object(o) container_of((o), struct qcom_tee_user_object, object)
+static struct qcom_tee_object_operations qcom_tee_user_object_ops;
+/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object) +{
return object != NULL_QCOM_TEE_OBJECT &&
typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT &&
object->ops == &qcom_tee_user_object_ops;
+}
+/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify) +{
if (is_qcom_tee_user_object(object))
WRITE_ONCE(to_qcom_tee_user_object(object)->nor, notify);
It looks like this depends on some synchronization. Please add a comment on how this works.
+}
+/**
- enum qcom_tee_req_state - Current state of request.
- @QCOM_TEE_REQ_QUEUED: Request is waiting for supplicant.
- @QCOM_TEE_REQ_PROCESSING: Request has been picked by the supplicant.
- @QCOM_TEE_REQ_PROCESSED: Response has been submitted for the request.
- */
+enum qcom_tee_req_state {
QCOM_TEE_REQ_QUEUED = 1,
QCOM_TEE_REQ_PROCESSING,
QCOM_TEE_REQ_PROCESSED,
+};
+/* User requests sent to supplicants. */ +struct qcom_tee_user_req {
enum qcom_tee_req_state state;
int req_id; /* Request ID. */
u64 object_id; /* User object ID. */
u32 op; /* Operation to perform on object. */
struct qcom_tee_arg *args; /* QTEE arguments for this operation. */
int errno; /* Result of operation. */
struct completion c; /* Completion for whoever wait for results. */
+};
+/* Static placeholder for a request in PROCESSING state in qcom_tee_context.reqs_idr.
- If the thread initiated the QTEE call using qcom_tee_object_invoke() dies, and supplicant
- is processing the request, we replace the entry in qcom_tee_context.reqs_idr with
- __empty_ureq. So (1) the req_id remains busy and not reused, and (2) supplicant fails to
- submit response and does the necessary rollback.
- */
+static struct qcom_tee_user_req __empty_ureq = { .state = QCOM_TEE_REQ_PROCESSING };
+/* Enqueue a user request for a context. */ +static int qcom_tee_request_enqueue(struct qcom_tee_user_req *ureq, struct qcom_tee_context *ctx) +{
int ret;
guard(mutex)(&ctx->lock);
/* Supplicant is dying. */
if (ctx->released)
return -ENODEV;
ret = idr_alloc(&ctx->reqs_idr, ureq, 0, 0, GFP_KERNEL);
if (ret < 0)
return ret;
ureq->req_id = ret;
ureq->state = QCOM_TEE_REQ_QUEUED;
return 0;
+}
+/**
- qcom_tee_requests_destroy() - Destroy requests in a context.
- @ctx: context for which to destroy requests.
- */
+void qcom_tee_requests_destroy(struct qcom_tee_context *ctx) +{
struct qcom_tee_user_req *ureq;
int id;
guard(mutex)(&ctx->lock);
/* So qcom_tee_request_enqueue() refuses new requests. */
ctx->released = 1;
idr_for_each_entry(&ctx->reqs_idr, ureq, id) {
if (ureq == &__empty_ureq)
continue;
/* ureq in QUEUED or PROCESSING state, terminate them. */
if (ureq->op == QCOM_TEE_OBJREF_OP_RELEASE) {
kfree(ureq);
} else {
ureq->state = QCOM_TEE_REQ_PROCESSED;
ureq->errno = -ENODEV;
complete(&ureq->c);
}
}
+}
+/**
- qcom_tee_supp_pop_entry() - Pop the next request in a context.
When you pop something you'd expect it to be removed also.
- @ctx: context from which to pop a request.
- @ubuf_size: size of available buffer for MEMBUF parameters.
- @num_params: number of entries for TEE parameter array.
- It does not remove the request from &qcom_tee_context.reqs_idr.
- It checks if @num_params is large enough to fit the next request arguments.
- It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE.
- It updates request state to %QCOM_TEE_REQ_PROCESSING state.
- Return: On success return a request or NULL and ERR_PTR on failure.
- */
+static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx,
size_t ubuf_size, int num_params)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *u;
int i, id;
guard(mutex)(&ctx->lock);
/* Find the a QUEUED request. */
Is it _a_ or _the_?
idr_for_each_entry(&ctx->reqs_idr, ureq, id)
if (ureq->state == QCOM_TEE_REQ_QUEUED)
break;
Will this always result in a FIFO processing?
if (!ureq)
return NULL;
u = ureq->args;
/* (1) Is there enough TEE parameters? */
if (num_params < qcom_tee_args_len(u))
return ERR_PTR(-EINVAL);
/* (2) Is there enough space to pass input buffers? */
qcom_tee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
/* Ready to process request 'QUEUED -> PROCESSING'. */
ureq->state = QCOM_TEE_REQ_PROCESSING;
return ureq;
+}
+/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op,
struct qcom_tee_arg *args)
+{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_user_req *ureq __free(kfree);
struct qcom_tee_context *ctx = uo->ctx;
int errno;
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
/* Queue the request. */
if (qcom_tee_request_enqueue(ureq, ctx))
return -ENODEV;
/* Wakeup supplicant to process it. */
complete(&ctx->req_c);
/* Wait for supplicant to process the request. */
/* Supplicant is expected to process request in a timely manner. We wait as KILLABLE,
requests
* in case supplicant and invoke thread both running from a same user process, otherwise
the same
* the process stuck on fatal signal.
might get stuck on a fatal signal?
*/
Please combine into one comment.
if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcom_tee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctx->lock) {
prev_state = ureq->state;
/* Replace ureq with '__empty_ureq' to keep req_id reserved. */
if (prev_state == QCOM_TEE_REQ_PROCESSING)
idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id);
/* Remove ureq as supplicant has never seen this request. */
else if (prev_state == QCOM_TEE_REQ_QUEUED)
idr_remove(&ctx->reqs_idr, ureq->req_id);
}
/* Wait for exclusive access to ureq. */
synchronize_srcu(&ctx->req_srcu);
I'm sorry, I don't follow.
Cheers, Jens
/* Supplicant did some work for us, we should not discard it. */
if (prev_state == QCOM_TEE_REQ_PROCESSED) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
}
}
return errno;
+}
+/* Called after submitting the callback response. */ +static void qcom_tee_user_object_end_cb_notify(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *unused_object, int err)
+{
struct qcom_tee_user_req *ureq = oic->data;
struct qcom_tee_arg *u = ureq->args;
struct qcom_tee_object *object;
int i;
qcom_tee_arg_for_each_output_object(i, u) {
object = u[i].o;
/* If err, drop QTEE copy otherwise just drop driver's copy. */
if (err && (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT))
qcom_tee_object_put(object);
qcom_tee_object_put(object);
}
kfree(ureq);
+}
+static void qcom_tee_user_object_release(struct qcom_tee_object *object) +{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_context *ctx = uo->ctx;
struct qcom_tee_user_req *ureq;
static struct qcom_tee_arg args[] = { { .type = QCOM_TEE_ARG_TYPE_INV } };
if (READ_ONCE(uo->nor)) {
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (ureq) {
ureq->object_id = uo->object_id;
ureq->op = QCOM_TEE_OBJREF_OP_RELEASE;
ureq->args = args;
/* Queue the RELEASE request and wake supplicant to process it. */
qcom_tee_request_enqueue(ureq, ctx);
complete(&ctx->req_c);
} else {
pr_err("failed to notify user object (%s) release.\n",
qcom_tee_object_name(object));
}
}
/* Matching get is in qcom_tee_user_param_to_object(). */
kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy);
kfree(uo);
+}
+static struct qcom_tee_object_operations qcom_tee_user_object_ops = {
.release = qcom_tee_user_object_release,
.notify = qcom_tee_user_object_end_cb_notify,
.dispatch = qcom_tee_user_object_dispatch,
+};
+/**
- qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object.
- @object: object returned.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- @param is OBJREF with %QCOM_TEE_OBJREF_FLAG_USER flags.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_user_object *user_object __free(kfree) = NULL;
struct qcom_tee_object *uo;
int err;
user_object = kzalloc(sizeof(*user_object), GFP_KERNEL);
if (!user_object)
return -ENOMEM;
user_object->ctx = ctx;
user_object->object_id = param->u.objref.id;
/* By default, always notify userspace on release. */
user_object->nor = true;
err = qcom_tee_object_user_init(&user_object->object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
&qcom_tee_user_object_ops, "uo-%lu", param->u.objref.id);
if (err)
return err;
uo = &no_free_ptr(user_object)->object;
/* Keep context alive as user object is alive. */
kref_get(&ctx->ref_cnt);
*object = uo;
return 0;
+}
+/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_user_object *uo;
if (!is_qcom_tee_user_object(object))
return -EINVAL;
uo = to_qcom_tee_user_object(object);
/* Sure if the object is in a same context as caller? */
if (uo->ctx != ctx)
return -EINVAL;
param->u.objref.id = uo->object_id;
param->u.objref.flags = QCOM_TEE_OBJREF_FLAG_USER;
/* User objects are valid in the context of userspace; drop the driver copy. */
qcom_tee_object_put(object);
return 0;
+}
+/**
- qcom_tee_cb_params_from_args() - Convert QTEE arguments to TEE parameters in a context.
- @params: TEE parameters.
- @u: QTEE arguments.
- @num_params: number of elements in the parameter array.
- @ubuf_addr: user buffer for argument of type %QCOM_TEE_ARG_TYPE_IB.
- @ubuf_size: size of user buffer.
- @ctx: context in which the conversion should happen.
- It expects @params to have enough entries for @u. Entries in @params are of
- %TEE_IOCTL_PARAM_ATTR_TYPE_NONE. On failure, it puts IO objects.
- Return: On success return number of input parameters processed or <0 on failure.
- */
+static int qcom_tee_cb_params_from_args(struct tee_param *params,
struct qcom_tee_arg *u, int num_params,
void __user *ubuf_addr, size_t ubuf_size,
struct qcom_tee_context *ctx)
+{
int i, np = 0;
qcom_tee_arg_for_each(i, u) {
enum qcom_tee_arg_type at = u[i].type;
if (at == QCOM_TEE_ARG_TYPE_IB) {
params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT;
/* Underflow already checked in qcom_tee_supp_pop_entry(). */
ubuf_size = round_down(ubuf_size - u[i].b.size, 8);
params[np].u.membuf.uaddr = (void * __user)(ubuf_addr + ubuf_size);
params[np].u.membuf.size = u[i].b.size;
if (copy_to_user(params[np].u.membuf.uaddr, u[i].b.addr, u[i].b.size))
goto out_failed;
np++;
} else if (at == QCOM_TEE_ARG_TYPE_IO) {
params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT;
if (qcom_tee_objref_from_arg(¶ms[np], &u[i], ctx))
goto out_failed;
np++;
}
}
return np;
+out_failed:
/* On ERROR: */
/* - Release params associated to QTEE objects in this context so far. */
for (; np >= 0; np--) {
if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT)
qcom_tee_context_del_qtee_object(¶ms[np], ctx);
}
/* - Release any IO objects not processed so far. */
while (u[i].type) {
if (u[i].type == QCOM_TEE_ARG_TYPE_IO)
qcom_tee_object_put(u[i++].o);
}
return -EINVAL;
+}
+/**
- qcom_tee_cb_params_to_args() - Convert TEE parameters to QTEE arguments in a context.
- @u: QTEE arguments.
- @params: TEE parameters.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_cb_params_to_args(struct qcom_tee_arg *u,
struct tee_param *params, int num_params,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int i, np = 0;
qcom_tee_arg_for_each(i, u) {
enum qcom_tee_arg_type at = u[i].type;
if (at == QCOM_TEE_ARG_TYPE_OB) {
if (params[np].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT ||
params[np].u.membuf.size > u[i].b.size)
goto out_failed;
if (copy_from_user(u[i].b.addr, params[np].u.membuf.uaddr, u[i].b.size))
goto out_failed;
u[i].b.size = params[np].u.membuf.size;
np++;
} else if (at == QCOM_TEE_ARG_TYPE_OO) {
if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) {
if (qcom_tee_objref_to_arg(&u[i], ¶ms[np], ctx))
goto out_failed;
} else {
goto out_failed;
}
np++;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* Put OO objects processed so far. */
for (--i; i >= 0; i--) {
if (u[i].type != QCOM_TEE_ARG_TYPE_OO)
continue;
object = u[i].o;
qcom_tee_user_object_set_notify(object, false);
/* For callback object, we hold a reference for the driver, put it. */
if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(object);
/* Put QTEE copy of object. */
qcom_tee_object_put(object);
}
return -EINVAL;
+}
+/**
- qcom_tee_user_object_pop() - Pop a request for a user object.
- @ctx: context to look for user object.
- @params: parameters for @op.
- @num_params: number of elements in the parameter array.
- @uaddr: user buffer for output MEMBUF parameters.
- @size: size of user buffer @uaddr.
- @data: information for the pop request.
- @params is filled along with @data for the picked request.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_pop(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params,
void __user *uaddr, size_t size,
struct qcom_tee_user_object_request_data *data)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *req_args;
u64 req_object_id;
u32 req_op;
int req_id;
int ret;
while (1) {
scoped_guard(srcu, &ctx->req_srcu) {
/* Pop a request 'QUEUED -> PROCESSING'. */
ureq = qcom_tee_supp_pop_entry(ctx, size, num_params);
if (!ureq)
goto wait_for_request;
/* On failure, issue with params, e.g. not enough space in user buffer. */
if (IS_ERR(ureq))
return PTR_ERR(ureq);
/* ''Prepare user request:'' */
req_id = ureq->req_id;
req_object_id = ureq->object_id;
req_op = ureq->op;
req_args = ureq->args;
ret = qcom_tee_cb_params_from_args(params, req_args,
num_params, uaddr,
size, ctx);
}
if (ret >= 0)
break;
/* On failure, issue with req_args, e.g. invalid object. */
scoped_guard(mutex, &ctx->lock) {
/* If (!= __empty_req) then 'PROCESSING -> PROCESSED'. */
if (idr_remove(&ctx->reqs_idr, req_id) == ureq) {
ureq->state = QCOM_TEE_REQ_PROCESSED;
ureq->errno = ret;
/* Send error to QTEE. */
complete(&ureq->c);
}
}
/* Try next request. */
continue;
+wait_for_request:
/* Wait for a new QUEUED request. */
if (wait_for_completion_interruptible(&ctx->req_c))
return -ERESTARTSYS;
}
/* It is a RELEASE message; no one is waiting for result. */
if (req_op == QCOM_TEE_OBJREF_OP_RELEASE) {
scoped_guard(mutex, &ctx->lock)
idr_remove(&ctx->reqs_idr, req_id);
kfree(ureq);
}
/* ''Pick a pending request:'' */
data->id = req_id;
data->object_id = req_object_id;
data->op = req_op;
data->np = ret;
return 0;
+}
+/**
- qcom_tee_user_object_submit() - Submit a response for a user object.
- @ctx: context to look for user object.
- @params: returned parameters.
- @num_params: number of elements in the parameter array.
- @id: request id for the response.
- @errno: result of user object invocation.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_submit(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params, int id, int errno)
+{
struct qcom_tee_user_req *ureq;
scoped_guard(srcu, &ctx->req_srcu) {
scoped_guard(mutex, &ctx->lock) {
ureq = idr_remove(&ctx->reqs_idr, id);
/* Is id invalid, or no one is waiting on response. */
if (ureq == &__empty_ureq || !ureq)
return -ENODEV;
ureq->state = QCOM_TEE_REQ_PROCESSED;
}
ureq->errno = errno;
/* Process params only on SUCCESS. */
if (!errno)
ureq->errno = qcom_tee_cb_params_to_args(ureq->args, params,
num_params, ctx);
errno = (!errno && ureq->errno) ? ureq->errno : 0;
/* Send result to QTEE. */
complete(&ureq->c);
}
return errno;
+} diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 5d33a8009efb..00e599d1582d 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -59,6 +59,7 @@ #define TEE_IMPL_ID_OPTEE 1 #define TEE_IMPL_ID_AMDTEE 2 #define TEE_IMPL_ID_TSTEE 3 +#define TEE_IMPL_ID_QTEE 4
/*
- OP-TEE specific capabilities
-- 2.34.1
Hi Jens,
On 12/10/2024 10:58 PM, Jens Wiklander wrote:
Hi Amirreza,
On Tue, Dec 3, 2024 at 5:20 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Introduce basic support for invoking objects hosted in QTEE and userspace through the TEE subsystem.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
drivers/tee/qcomtee/Makefile | 2 + drivers/tee/qcomtee/call.c | 707 ++++++++++++++++++++++++++++++++++ drivers/tee/qcomtee/core.c | 8 + drivers/tee/qcomtee/qcomtee_private.h | 166 ++++++++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++++++++++++++++++ include/uapi/linux/tee.h | 1 + 6 files changed, 1509 insertions(+)
diff --git a/drivers/tee/qcomtee/Makefile b/drivers/tee/qcomtee/Makefile index 108bc7fdabcb..6bf91481fde3 100644 --- a/drivers/tee/qcomtee/Makefile +++ b/drivers/tee/qcomtee/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_QCOMTEE) += qcomtee.o qcomtee-objs += async.o +qcomtee-objs += call.o qcomtee-objs += core.o qcomtee-objs += primordial_obj.o qcomtee-objs += qcom_scm.o qcomtee-objs += release.o +qcomtee-objs += user_obj.o diff --git a/drivers/tee/qcomtee/call.c b/drivers/tee/qcomtee/call.c new file mode 100644 index 000000000000..11bb31836808 --- /dev/null +++ b/drivers/tee/qcomtee/call.c @@ -0,0 +1,707 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/tee.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/firmware/qcom/qcom_tzmem.h> +#include <linux/firmware/qcom/qcom_scm.h>
+#include "qcomtee_private.h"
+/**
- enum qcom_tee_object_host - Object host where it is managed.
- @QCOM_TEE_OBJECT_HOST_USER: objects in userspace.
- @QCOM_TEE_OBJECT_HOST_TEE: objects in QTEE.
- We refer to object hosted in userspace as 'Local Object' and objects hosted
- in QTEE as 'Remote Object'.
- */
+enum qcom_tee_object_host {
QCOM_TEE_OBJECT_HOST_USER, /* Object that is managed in userspace. */
QCOM_TEE_OBJECT_HOST_TEE, /* Object that is managed in QTEE. */
+};
+/* Read object ID host information. */ +static enum qcom_tee_object_host qcom_tee_object_host(struct tee_param *param) +{
if (param->u.objref.flags & QCOM_TEE_OBJREF_FLAG_USER)
return QCOM_TEE_OBJECT_HOST_USER;
return QCOM_TEE_OBJECT_HOST_TEE;
+}
+/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context.
- @param: TEE parameter represents @object.
- @object: QTEE object.
- @ctx: context to add the object.
- It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued
- qcom_tee_object_get() for @object.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx)
+{
int ret;
guard(mutex)(&ctx->lock);
ret = idr_alloc(&ctx->qtee_objects_idr, object, 0, 0, GFP_KERNEL);
if (ret < 0)
return ret;
param->u.objref.id = ret;
/* QTEE Object: !QCOM_TEE_OBJREF_FLAG_USER. */
param->u.objref.flags = 0;
return 0;
+}
+static int find_qtee_object(struct qcom_tee_object **object, unsigned long id,
struct qcom_tee_context *ctx)
+{
int err = 0;
guard(rcu)();
/* Object release is RCU protected. */
*object = idr_find(&ctx->qtee_objects_idr, id);
if (!qcom_tee_object_get(*object))
err = -EINVAL;
return err;
+}
+static void del_qtee_object(unsigned long id, struct qcom_tee_context *ctx) +{
struct qcom_tee_object *object;
scoped_guard(mutex, &ctx->lock)
object = idr_remove(&ctx->qtee_objects_idr, id);
qcom_tee_object_put(object);
+}
+/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
/* 'qtee_objects_idr' stores QTEE objects only. */
if (qcom_tee_object_host(param) != QCOM_TEE_OBJECT_HOST_TEE)
return -EINVAL;
return find_qtee_object(object, param->u.objref.id, ctx);
+}
+/**
- qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context.
- @param: TEE parameter represents @object.
- @ctx: context to delete the object.
- @param returned by qcom_tee_context_add_qtee_object().
- */
+void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx) +{
/* 'qtee_objects_idr' stores QTEE objects only. */
if (qcom_tee_object_host(param) == QCOM_TEE_OBJECT_HOST_TEE)
del_qtee_object(param->u.objref.id, ctx);
+}
+/**
- qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context.
- @arg: QTEE argument.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- It assumes @param is OBJREF.
- It does not set @arg.type; caller should initialize it to a correct
- &enum qcom_tee_arg_type value.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int err;
if (arg->type != QCOM_TEE_ARG_TYPE_IO &&
arg->type != QCOM_TEE_ARG_TYPE_OO)
return -EINVAL;
/* It is a NULL object?! */
if (param->u.objref.id == TEE_OBJREF_NULL) {
arg->o = NULL_QCOM_TEE_OBJECT;
return 0;
}
switch (qcom_tee_object_host(param)) {
case QCOM_TEE_OBJECT_HOST_USER:
err = qcom_tee_user_param_to_object(&object, param, ctx);
if (err)
break;
/* Keep a copy for driver as QTEE may release it (e.g. using async msg). */
qcom_tee_object_get(object);
break;
case QCOM_TEE_OBJECT_HOST_TEE:
err = qcom_tee_context_find_qtee_object(&object, param, ctx);
break;
}
arg->o = err ? NULL_QCOM_TEE_OBJECT : object;
return err;
+}
+/**
- qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context.
- @param: TEE parameter.
- @arg: QTEE argument.
- @ctx: context in which the conversion should happen.
- It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO.
- It does not set @param.attr; caller should initialize it to a correct OBJREF type.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int err;
/* param should be of OBJREF. */
if (param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT &&
param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT &&
param->attr != TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INOUT)
return -EINVAL;
object = arg->o;
switch (typeof_qcom_tee_object(object)) {
case QCOM_TEE_OBJECT_TYPE_NULL:
param->u.objref.id = TEE_OBJREF_NULL;
err = 0;
break;
case QCOM_TEE_OBJECT_TYPE_CB_OBJECT:
err = qcom_tee_user_param_from_object(param, object, ctx);
break;
case QCOM_TEE_OBJECT_TYPE_TEE:
err = qcom_tee_context_add_qtee_object(param, object, ctx);
break;
case QCOM_TEE_OBJECT_TYPE_ROOT:
default:
return -EINVAL;
}
return err;
+}
+/**
- qcom_tee_params_to_args() - Convert TEE parameters to QTEE arguments in a context.
- @u: QTEE arguments.
- @params: TEE parameters.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- It assumes @u has at least @num_param + 1 entries and has been initialized
- with %QCOM_TEE_ARG_TYPE_INV as &struct qcom_tee_arg.type.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_params_to_args(struct qcom_tee_arg *u,
struct tee_param *params, int num_params,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int i;
for (i = 0; i < num_params; i++) {
switch (params[i].attr) {
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
u[i].flags = QCOM_TEE_ARG_FLAGS_UADDR;
u[i].b.uaddr = params[i].u.membuf.uaddr;
u[i].b.size = params[i].u.membuf.size;
if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT)
u[i].type = QCOM_TEE_ARG_TYPE_IB;
else /* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */
u[i].type = QCOM_TEE_ARG_TYPE_OB;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
u[i].type = QCOM_TEE_ARG_TYPE_IO;
if (qcom_tee_objref_to_arg(&u[i], ¶ms[i], ctx))
goto out_failed;
break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
u[i].type = QCOM_TEE_ARG_TYPE_OO;
u[i].o = NULL_QCOM_TEE_OBJECT;
break;
default: /* Unsupported TEE parameters. */
goto out_failed;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* Put IO objects processed so far. */
for (--i; i >= 0; i--) {
if (u[i].type != QCOM_TEE_ARG_TYPE_IO)
continue;
object = u[i].o;
qcom_tee_user_object_set_notify(object, false);
/* For callback object, we hold a reference for the driver, put it. */
if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(object);
/* Put QTEE copy of object. */
qcom_tee_object_put(object);
}
return -EINVAL;
+}
+/**
- qcom_tee_params_from_args() - Convert QTEE arguments to TEE parameters in a context.
- @params: TEE parameters.
- @u: QTEE arguments.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- @u should have been already initialized by qcom_tee_params_to_args().
- This also represents end of a QTEE invocation that started with qcom_tee_params_to_args()
- by releasing %QCOM_TEE_ARG_TYPE_IO objects.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_params_from_args(struct tee_param *params,
struct qcom_tee_arg *u, int num_params,
struct qcom_tee_context *ctx)
+{
int i, np;
for (np = 0; u[np].type; np++) {
enum qcom_tee_arg_type at = u[np].type;
if (at == QCOM_TEE_ARG_TYPE_OB) {
/* TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT */
params[np].u.value.b = u[np].b.size;
} else if (at == QCOM_TEE_ARG_TYPE_IO) {
/* IEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT */
qcom_tee_object_put(u[np].o);
} else if (at == QCOM_TEE_ARG_TYPE_OO) {
/* TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT */
if (qcom_tee_objref_from_arg(¶ms[np], &u[np], ctx))
goto out_failed;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* - Release params associated to QTEE objects in this context so far. */
for (i = 0; i < np; i++) {
if (params[i].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT)
qcom_tee_context_del_qtee_object(¶ms[i], ctx);
}
/* - Release any IO and OO objects not processed so far. */
while (u[i].type) {
if (u[i].type == QCOM_TEE_ARG_TYPE_OO ||
u[i].type == QCOM_TEE_ARG_TYPE_IO)
qcom_tee_object_put(u[i++].o);
}
return -EINVAL;
+}
+/* TEE Device Ops. */
+static int qcom_tee_params_check(struct tee_param *params, int num_params) +{
int io = 0, oo = 0, ib = 0, ob = 0;
int i;
/* QTEE accepts 64 arguments. */
if (num_params > QCOM_TEE_ARGS_MAX)
return -EINVAL;
/* Supported parameter types. */
for (i = 0; i < num_params; i++) {
switch (params[i].attr) {
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT:
ib++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT:
ob++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT:
io++; break;
case TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT:
oo++; break;
default:
return -EINVAL;
}
}
/* QTEE accepts 16 arguments of each supported types. */
if (io > QCOM_TEE_ARGS_PER_TYPE || oo > QCOM_TEE_ARGS_PER_TYPE ||
ib > QCOM_TEE_ARGS_PER_TYPE || ob > QCOM_TEE_ARGS_PER_TYPE)
return -EINVAL;
return 0;
+}
+/* Check if user issued a permitted operation on ROOT_QCOM_TEE_OBJECT from userspace. */ +static int qcom_tee_root_object_check(u32 op, struct tee_param *params, int num_params) +{
/* Some privileged operations recognized by QTEE. */
if (op == 4 || op == 8 || op == 9)
return -EINVAL;
/* OP 5 is to register with QTEE by passing credential object as input OBJREF. */
/* TEE_OBJREF_NULL as credential object represents a privileged client for QTEE,
* only kernel can pass TEE_OBJREF_NULL.
*/
if (op == 5) {
if (num_params != 2)
return -EINVAL;
if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT &&
params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) {
if (params[0].u.objref.id == TEE_OBJREF_NULL)
return -EINVAL;
} else if (params[0].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT &&
params[1].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT) {
if (params[1].u.objref.id == TEE_OBJREF_NULL)
return -EINVAL;
}
}
return 0;
+}
+/**
- qcom_tee_object_invoke() - Invoke a QTEE object.
- @tee_ctx: TEE context.
- @arg: ioctl arguments.
- @params: parameters for the object.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_object_invoke(struct tee_context *tee_ctx,
struct tee_ioctl_object_invoke_arg *arg,
struct tee_param *params)
+{
struct qcom_tee_object_invoke_ctx *oic __free(kfree) = NULL;
struct qcom_tee_context *ctx = tee_ctx->data;
struct qcom_tee_arg *u __free(kfree) = NULL;
struct qcom_tee_object *object;
int i, ret, result;
if (qcom_tee_params_check(params, arg->num_params))
return -EINVAL;
/* Handle OBJREF reserved operations. */
if (arg->op == QCOM_TEE_OBJREF_OP_RELEASE) {
del_qtee_object(arg->object, ctx);
return 0;
}
/* Unsupported reserved operation. */
if (arg->op > QCOM_TEE_OBJREF_OP_MIN)
return -EINVAL;
oic = kzalloc(sizeof(*oic), GFP_KERNEL);
if (!oic)
return -ENOMEM;
/* +1 for ending QCOM_TEE_ARG_TYPE_INV. */
u = kzalloc(sizeof(*u) * (arg->num_params + 1), GFP_KERNEL);
Please use kcalloc().
Ack.
if (!u)
return -ENOMEM;
if (arg->object == TEE_OBJREF_NULL &&
!qcom_tee_root_object_check(arg->op, params, arg->num_params)) {
/* Use ROOT if NULL is invoked. */
object = ROOT_QCOM_TEE_OBJECT;
} else {
/* Get object being invoked. */
ret = find_qtee_object(&object, arg->object, ctx);
if (ret)
return ret;
}
ret = qcom_tee_params_to_args(u, params, arg->num_params, ctx);
if (ret)
goto out;
ret = qcom_tee_object_do_invoke(oic, object, arg->op, u, &result);
if (!ret) {
if (!result) {
/* Only parse QTEE response on SUCCESS. */
ret = qcom_tee_params_from_args(params, u, arg->num_params, ctx);
} else {
/* Put driver's IO objects copy; get in qcom_tee_params_to_args(). */
qcom_tee_arg_for_each_input_object(i, u)
qcom_tee_object_put(u[i++].o);
}
} else if (ret != -EAGAIN && ret != -ENODEV) {
/* Unable to initiate a QTEE invocation; cleanup qcom_tee_params_to_args(). */
qcom_tee_arg_for_each_input_object(i, u) {
qcom_tee_user_object_set_notify(u[i].o, false);
if (typeof_qcom_tee_object(u[i].o) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(u[i].o);
qcom_tee_object_put(u[i].o);
}
}
arg->ret = result;
+out:
qcom_tee_object_put(object);
return ret;
+}
+/**
- qcom_tee_supp_recv() - Pick a request for the supplicant.
- @tee_ctx: TEE context.
- @op: requested operation on object.
- @num_params: number of elements in the parameter array, updated with number used.
- @params: parameters for @op.
- The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT meta parameter.
- On input, it provides a user buffer. This buffer is used for parameters of type
- %TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT in qcom_tee_cb_params_from_args().
- On output, object id and request id are stored in the meta parameter.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_supp_recv(struct tee_context *tee_ctx, u32 *op, u32 *num_params,
struct tee_param *params)
+{
struct qcom_tee_user_object_request_data data;
struct qcom_tee_context *ctx = tee_ctx->data;
void __user *uaddr;
size_t ubuf_size;
int i, ret;
if (!*num_params)
return -EINVAL;
/* We expect the first parameter to be an INOUT + meta parameter. */
if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT | TEE_IOCTL_PARAM_ATTR_META))
return -EINVAL;
/* Others parameters are none. */
for (i = 1; i < *num_params; i++)
if (params[i].attr)
return -EINVAL;
if (IS_ALIGNED(params->u.value.a, 8))
return -EINVAL;
/* User buffer and size from meta parameter. */
uaddr = u64_to_user_ptr(params->u.value.a);
ubuf_size = params->u.value.b;
/* Process TEE parameters. +/-1 to ignore meta parameter. */
ret = qcom_tee_user_object_pop(ctx, params + 1, *num_params - 1, uaddr, ubuf_size, &data);
if (ret)
return ret;
params->u.value.a = data.object_id;
params->u.value.b = data.id;
params->u.value.c = 0;
*op = data.op;
*num_params = data.np + 1;
return 0;
+}
+/**
- qcom_tee_supp_send() - Pick a request for the supplicant.
- @tee_ctx: TEE context.
- @ret: return value of the request.
- @num_params: number of elements in the parameter array.
- @params: returned parameters.
- The first parameter is a %TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT meta parameter.
- It specifies the request id this response is belong to.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_supp_send(struct tee_context *tee_ctx, u32 errno, u32 num_params,
struct tee_param *params)
+{
struct qcom_tee_context *ctx = tee_ctx->data;
int id;
if (!num_params)
return -EINVAL;
/* We expect the first parameter to be an INPUT + meta parameter. */
if (params->attr != (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT | TEE_IOCTL_PARAM_ATTR_META))
return -EINVAL;
/* Get the req_id of response. */
id = params->u.value.a;
/* Process TEE parameters. +/-1 to ignore meta parameter. */
return qcom_tee_user_object_submit(ctx, params + 1, num_params - 1, id, errno);
+}
+static int qcom_tee_open(struct tee_context *tee_context) +{
struct qcom_tee_context *ctx __free(kfree) = NULL;
int err;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
err = init_srcu_struct(&ctx->req_srcu);
if (err)
return err;
ctx->tee_context = tee_context;
idr_init(&ctx->qtee_objects_idr);
idr_init(&ctx->reqs_idr);
mutex_init(&ctx->lock);
init_completion(&ctx->req_c);
kref_init(&ctx->ref_cnt);
tee_context->data = no_free_ptr(ctx);
return 0;
+}
+static void qcom_tee_release(struct tee_context *tee_context) +{
struct qcom_tee_context *ctx = tee_context->data;
struct qcom_tee_object *object;
int id;
/* Process QUEUED or PROCESSING requests. */
qcom_tee_requests_destroy(ctx);
/* Release QTEE objects. */
idr_for_each_entry(&ctx->qtee_objects_idr, object, id)
qcom_tee_object_put(object);
/* Put the context; wait for all user objects to go away. */
kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy);
+}
+void __qcom_tee_context_destroy(struct kref *ref_cnt) +{
struct qcom_tee_context *ctx = container_of(ref_cnt, struct qcom_tee_context, ref_cnt);
idr_destroy(&ctx->qtee_objects_idr);
idr_destroy(&ctx->reqs_idr);
cleanup_srcu_struct(&ctx->req_srcu);
kfree(ctx);
+}
+static void qcom_tee_get_version(struct tee_device *teedev, struct tee_ioctl_version_data *vers) +{
struct tee_ioctl_version_data v = {
.impl_id = TEE_IMPL_ID_QTEE,
.gen_caps = TEE_GEN_CAP_OBJREF,
};
*vers = v;
+}
+static const struct tee_driver_ops qcom_tee_ops = {
.get_version = qcom_tee_get_version,
.open = qcom_tee_open,
.release = qcom_tee_release,
.object_invoke_func = qcom_tee_object_invoke,
.supp_recv = qcom_tee_supp_recv,
.supp_send = qcom_tee_supp_send,
+};
+static const struct tee_desc qcom_tee_desc = {
.name = "qcom_tee",
.ops = &qcom_tee_ops,
.owner = THIS_MODULE,
+};
+static int qcom_tee_probe(struct platform_device *pdev) +{
struct tee_device *teedev;
int err;
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
teedev = tee_device_alloc(&qcom_tee_desc, NULL, NULL, NULL);
if (IS_ERR(teedev))
return PTR_ERR(teedev);
err = tee_device_register(teedev);
if (err)
goto err_unreg_teedev;
platform_set_drvdata(pdev, teedev);
return 0;
+err_unreg_teedev:
tee_device_unregister(teedev);
return err;
+}
+static void qcom_tee_remove(struct platform_device *pdev) +{
struct tee_device *teedev = platform_get_drvdata(pdev);
/* Keep a copy, tee_device_unregister() sets it to NULL. */
teedev shouldn't be accessed after a call to tee_device_unregister()
True, but I don't access teedev after unregistering.
pdev holds a pointer to teedev, and teedev holds a pointer to the pool. Before calling unregister, I keep a copy of the pool address for cleaning up the pool after unregistering.
I did it this way because we don't have a private struct like optee to hold the driver state. I'm adding struct qcomtee (following your comments in PATCH [5/10]), so this should be removed.
struct tee_shm_pool *pool = teedev->pool;
/* Wait for users to go away. */
tee_device_unregister(teedev);
tee_shm_pool_free(pool);
Why? You supplied NULL when tee_device_alloc() was called.
Sorry, this is my fault. We originally had support for TEE SHM IOCTLs. I removed it to reduce the size of this patchset and planned to send it later. I missed removing this part.
+}
+static const struct of_device_id qcom_tee_dt_match[] = {
{ .compatible = "qcom,tee" },
{},
+}; +MODULE_DEVICE_TABLE(of, qcom_tee_dt_match);
+static struct platform_driver qcom_tee_platform_driver = {
.probe = qcom_tee_probe,
.remove = qcom_tee_remove,
.driver = {
.name = "qcom_tee",
.of_match_table = qcom_tee_dt_match,
},
+};
+int qcom_tee_driver_register(void) +{
return platform_driver_register(&qcom_tee_platform_driver);
+}
+void qcom_tee_driver_unregister(void) +{
platform_driver_unregister(&qcom_tee_platform_driver);
+} diff --git a/drivers/tee/qcomtee/core.c b/drivers/tee/qcomtee/core.c index 79f1181cf676..545857e117db 100644 --- a/drivers/tee/qcomtee/core.c +++ b/drivers/tee/qcomtee/core.c @@ -904,8 +904,14 @@ static int __init qcom_tee_object_invoke_init(void) if (ret) goto err_kobject_put;
ret = qcom_tee_driver_register();
if (ret)
goto err_remove_group;
return 0;
+err_remove_group:
sysfs_remove_group(qcom_tee_object_invoke_kobj, &attr_group);
err_kobject_put: /* Remove '/sys/firmware/qcom_tee'. */ kobject_put(qcom_tee_object_invoke_kobj); @@ -920,6 +926,8 @@ module_init(qcom_tee_object_invoke_init);
static void __exit qcom_tee_object_invoke_deinit(void) {
qcom_tee_driver_unregister();
/* Wait for RELEASE operations for QTEE objects. */ qcom_tee_release_destroy(); qcom_tee_msg_buffers_destroy();
diff --git a/drivers/tee/qcomtee/qcomtee_private.h b/drivers/tee/qcomtee/qcomtee_private.h index c718cd2d8463..15f358260ed7 100644 --- a/drivers/tee/qcomtee/qcomtee_private.h +++ b/drivers/tee/qcomtee/qcomtee_private.h @@ -10,6 +10,14 @@ #include <linux/kobject.h> #include <linux/tee_core.h>
+/* Flags relating to object reference. */ +#define QCOM_TEE_OBJREF_FLAG_USER 1
+/* Reserved OBJREF operations. */ +/* These operations are not sent to QTEE and handled in driver. */ +#define QCOM_TEE_OBJREF_OP_MIN USHRT_MAX +#define QCOM_TEE_OBJREF_OP_RELEASE (QCOM_TEE_OBJREF_OP_MIN + 1)
struct qcom_tee_object *qcom_tee_idx_erase(u32 idx); void qcom_tee_object_free(struct qcom_tee_object *object);
@@ -44,9 +52,167 @@ int __qcom_tee_object_do_invoke(struct qcom_tee_object_invoke_ctx *oic, struct qcom_tee_object *object, u32 op, struct qcom_tee_arg *u, int *result);
+/**
- struct qcom_tee_context - Clients or supplicants context.
- @tee_context: TEE context.
- @qtee_objects_idr: QTEE objects in this context.
- @reqs_idr: Requests currently being processed.
- @lock: mutex for @reqs_idr and @qtee_objects_idr.
- @req_srcu: srcu for exclusive access to requests.
- @req_c: completion used when supplicant is waiting for requests.
- @released: state of this context.
- @ref_cnt: ref count.
- */
+struct qcom_tee_context {
Other drivers call their conterpart of this struct *_context_data. Using the same pattern here makes it easier to recognize the struct in the rest of the code.
Ack.
struct tee_context *tee_context;
struct idr qtee_objects_idr;
struct idr reqs_idr;
/* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */
struct mutex lock;
struct srcu_struct req_srcu;
Why do you use this synchronization primitive? I don't know enough about this primitive to tell if you use it for the right purpose so perhaps you can help me understand which properties you need.
Sure, let me explain it bellow in the qcom_tee_user_object_dispatch, where it is acually used.
struct completion req_c;
int released;
struct kref ref_cnt;
Why does this struct need a different lifetime than struct tee_context?
This is a side effect of how QTEE objects and callback objects are released:
- When a tee_context is closed, we release all QTEE objects in that context. QTEE specifies that object releases are asynchronous. So, we queue the releases in a workqueue and immediately return from the release callback, allowing the TEE subsystem to continue.
- When the workqueue sends a release for a QTEE object, QTEE may respond by requesting the release of a callback object or an operation on a callback object. This requires a valid struct qcom_tee_context. That's why we keep this until all callback objects are gone.
The alternative is to keep a list of callback objects in this context and flag them as orphans. The refcount seems easier :).
+};
+void __qcom_tee_context_destroy(struct kref *ref_cnt);
+/* qcom_tee_context_add_qtee_object() - Add a QTEE object to the context.
- @param: TEE parameter represents @object.
- @object: QTEE object.
- @ctx: context to add the object.
- It assumes @object is %QCOM_TEE_OBJECT_TYPE_TEE and caller has already issued
- qcom_tee_object_get() for @object.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_context_add_qtee_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx);
+/* Get the QTEE object added with qcom_tee_context_add_qtee_object(). */ +int qcom_tee_context_find_qtee_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx);
+/**
- qcom_tee_context_del_qtee_object() - Delete a QTEE object from the context.
- @param: TEE parameter represents @object.
- @ctx: context to delete the object.
- @param returned by qcom_tee_context_add_qtee_object().
- */
+void qcom_tee_context_del_qtee_object(struct tee_param *param, struct qcom_tee_context *ctx);
+/**
- qcom_tee_objref_to_arg() - Convert OBJREF parameter to QTEE argument in a context.
- @arg: QTEE argument.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- It assumes @param is OBJREF.
- It does not set @arg.type; caller should initialize it to a correct
- &enum qcom_tee_arg_type value.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_to_arg(struct qcom_tee_arg *arg, struct tee_param *param,
struct qcom_tee_context *ctx);
+/**
- qcom_tee_objref_from_arg() - Convert QTEE argument to OBJREF param in a context.
- @param: TEE parameter.
- @arg: QTEE argument.
- @ctx: context in which the conversion should happen.
- It assumes @arg is of %QCOM_TEE_ARG_TYPE_IO or %QCOM_TEE_ARG_TYPE_OO.
- It does not set @param.attr; caller should initialize it to a correct OBJREF type.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_objref_from_arg(struct tee_param *param, struct qcom_tee_arg *arg,
struct qcom_tee_context *ctx);
+int qcom_tee_driver_register(void); +void qcom_tee_driver_unregister(void);
/* OBJECTS: */
/* (1) Primordial Object. */ extern struct qcom_tee_object qcom_tee_primordial_object;
+/* (2) User Object API. */
+/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object);
+/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify);
+/**
- qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object.
- @object: object returned.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- @param is OBJREF with %TEE_IOCTL_OBJREF_USER flags.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx);
+/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx);
+struct qcom_tee_user_object_request_data {
int id; /* Id assigned to the request. */
u64 object_id; /* Object id being invoked by QTEE. */
u32 op; /* Requested operation on object. */
int np; /* Number of parameters in the request.*/
+};
+/**
- qcom_tee_user_object_pop() - Pop a request for a user object.
- @ctx: context to look for user object.
- @params: parameters for @op.
- @num_params: number of elements in the parameter array.
- @uaddr: user buffer for output MEMBUF parameters.
- @size: size of user buffer @uaddr.
- @data: information for the pop request.
- @params is filled along with @data for the picked request.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_pop(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params,
void __user *uaddr, size_t size,
struct qcom_tee_user_object_request_data *data);
+/**
- qcom_tee_user_object_submit() - Submit a response for a user object.
- @ctx: context to look for user object.
- @params: returned parameters.
- @num_params: number of elements in the parameter array.
- @id: request id for the response.
- @errno: result of user object invocation.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_submit(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params, int id, int errno);
+/**
- qcom_tee_requests_destroy() - Destroy requests in a context.
- @ctx: context for which to destroy requests.
- After calling qcom_tee_requests_destroy(), @ctx can not be reused.
- It should be called on @ctx cleanup path.
- */
+void qcom_tee_requests_destroy(struct qcom_tee_context *ctx);
#endif /* QCOM_TEE_PRIVATE_H */ diff --git a/drivers/tee/qcomtee/user_obj.c b/drivers/tee/qcomtee/user_obj.c new file mode 100644 index 000000000000..4c671a3ae0de --- /dev/null +++ b/drivers/tee/qcomtee/user_obj.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
- */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include "qcomtee_private.h"
+/**
- DOC: User Objects aka Supplicants
- Any userspace process with access to the TEE device file can behave as a supplicant
- by creating a user object. Any TEE parameter of type OBJREF with %QCOM_TEE_OBJREF_FLAG_USER
- flag set is considered as user object.
- A supplicant uses qcom_tee_user_object_pick() (i.e. TEE_IOC_SUPPL_RECV) to receive a
- QTEE user object request and qcom_tee_user_object_submit() (i.e. TEE_IOC_SUPPL_SEND)
- to submit a response. QTEE expects to receive the response, including OB and OO in
- specific order in the message; parameters submitted with qcom_tee_user_object_submit()
- should maintain this order.
- */
+/**
- struct qcom_tee_user_object - User object.
- @object: &struct qcom_tee_object representing this user object.
- @ctx: context for which user object is defined.
- @object_id: object ID in @ctx.
- @nor: notify userspace if object is released.
- Any object managed in userspace is represented with this struct.
- If @nor is set, on release a notification message is send back to the userspace.
- */
+struct qcom_tee_user_object {
struct qcom_tee_object object;
struct qcom_tee_context *ctx;
u64 object_id;
bool nor;
+};
+#define to_qcom_tee_user_object(o) container_of((o), struct qcom_tee_user_object, object)
+static struct qcom_tee_object_operations qcom_tee_user_object_ops;
+/* Is it a user object? */ +int is_qcom_tee_user_object(struct qcom_tee_object *object) +{
return object != NULL_QCOM_TEE_OBJECT &&
typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT &&
object->ops == &qcom_tee_user_object_ops;
+}
+/* Set user object's 'notify on release' flag. */ +void qcom_tee_user_object_set_notify(struct qcom_tee_object *object, bool notify) +{
if (is_qcom_tee_user_object(object))
WRITE_ONCE(to_qcom_tee_user_object(object)->nor, notify);
It looks like this depends on some synchronization. Please add a comment on how this works.
Absolutely, I'll give detail.
+}
+/**
- enum qcom_tee_req_state - Current state of request.
- @QCOM_TEE_REQ_QUEUED: Request is waiting for supplicant.
- @QCOM_TEE_REQ_PROCESSING: Request has been picked by the supplicant.
- @QCOM_TEE_REQ_PROCESSED: Response has been submitted for the request.
- */
+enum qcom_tee_req_state {
QCOM_TEE_REQ_QUEUED = 1,
QCOM_TEE_REQ_PROCESSING,
QCOM_TEE_REQ_PROCESSED,
+};
+/* User requests sent to supplicants. */ +struct qcom_tee_user_req {
enum qcom_tee_req_state state;
int req_id; /* Request ID. */
u64 object_id; /* User object ID. */
u32 op; /* Operation to perform on object. */
struct qcom_tee_arg *args; /* QTEE arguments for this operation. */
int errno; /* Result of operation. */
struct completion c; /* Completion for whoever wait for results. */
+};
+/* Static placeholder for a request in PROCESSING state in qcom_tee_context.reqs_idr.
- If the thread initiated the QTEE call using qcom_tee_object_invoke() dies, and supplicant
- is processing the request, we replace the entry in qcom_tee_context.reqs_idr with
- __empty_ureq. So (1) the req_id remains busy and not reused, and (2) supplicant fails to
- submit response and does the necessary rollback.
- */
+static struct qcom_tee_user_req __empty_ureq = { .state = QCOM_TEE_REQ_PROCESSING };
+/* Enqueue a user request for a context. */ +static int qcom_tee_request_enqueue(struct qcom_tee_user_req *ureq, struct qcom_tee_context *ctx) +{
int ret;
guard(mutex)(&ctx->lock);
/* Supplicant is dying. */
if (ctx->released)
return -ENODEV;
ret = idr_alloc(&ctx->reqs_idr, ureq, 0, 0, GFP_KERNEL);
if (ret < 0)
return ret;
ureq->req_id = ret;
ureq->state = QCOM_TEE_REQ_QUEUED;
return 0;
+}
+/**
- qcom_tee_requests_destroy() - Destroy requests in a context.
- @ctx: context for which to destroy requests.
- */
+void qcom_tee_requests_destroy(struct qcom_tee_context *ctx) +{
struct qcom_tee_user_req *ureq;
int id;
guard(mutex)(&ctx->lock);
/* So qcom_tee_request_enqueue() refuses new requests. */
ctx->released = 1;
idr_for_each_entry(&ctx->reqs_idr, ureq, id) {
if (ureq == &__empty_ureq)
continue;
/* ureq in QUEUED or PROCESSING state, terminate them. */
if (ureq->op == QCOM_TEE_OBJREF_OP_RELEASE) {
kfree(ureq);
} else {
ureq->state = QCOM_TEE_REQ_PROCESSED;
ureq->errno = -ENODEV;
complete(&ureq->c);
}
}
+}
+/**
- qcom_tee_supp_pop_entry() - Pop the next request in a context.
When you pop something you'd expect it to be removed also.
I'll rename it to more apporpriate name.
- @ctx: context from which to pop a request.
- @ubuf_size: size of available buffer for MEMBUF parameters.
- @num_params: number of entries for TEE parameter array.
- It does not remove the request from &qcom_tee_context.reqs_idr.
- It checks if @num_params is large enough to fit the next request arguments.
- It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE.
- It updates request state to %QCOM_TEE_REQ_PROCESSING state.
- Return: On success return a request or NULL and ERR_PTR on failure.
- */
+static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx,
size_t ubuf_size, int num_params)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *u;
int i, id;
guard(mutex)(&ctx->lock);
/* Find the a QUEUED request. */
Is it _a_ or _the_?
idr_for_each_entry(&ctx->reqs_idr, ureq, id)
if (ureq->state == QCOM_TEE_REQ_QUEUED)
break;
Will this always result in a FIFO processing?
It not a FIFO. I understand your concerns. I'll replace it with a list.
if (!ureq)
return NULL;
u = ureq->args;
/* (1) Is there enough TEE parameters? */
if (num_params < qcom_tee_args_len(u))
return ERR_PTR(-EINVAL);
/* (2) Is there enough space to pass input buffers? */
qcom_tee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
/* Ready to process request 'QUEUED -> PROCESSING'. */
ureq->state = QCOM_TEE_REQ_PROCESSING;
return ureq;
+}
+/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op,
struct qcom_tee_arg *args)
+{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_user_req *ureq __free(kfree);
struct qcom_tee_context *ctx = uo->ctx;
int errno;
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
/* Queue the request. */
if (qcom_tee_request_enqueue(ureq, ctx))
return -ENODEV;
/* Wakeup supplicant to process it. */
complete(&ctx->req_c);
/* Wait for supplicant to process the request. */
/* Supplicant is expected to process request in a timely manner. We wait as KILLABLE,
requests
* in case supplicant and invoke thread both running from a same user process, otherwise
the same
* the process stuck on fatal signal.
might get stuck on a fatal signal?
*/
Please combine into one comment.
Ack.
if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcom_tee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctx->lock) {
prev_state = ureq->state;
/* Replace ureq with '__empty_ureq' to keep req_id reserved. */
if (prev_state == QCOM_TEE_REQ_PROCESSING)
idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id);
/* Remove ureq as supplicant has never seen this request. */
else if (prev_state == QCOM_TEE_REQ_QUEUED)
idr_remove(&ctx->reqs_idr, ureq->req_id);
}
/* Wait for exclusive access to ureq. */
synchronize_srcu(&ctx->req_srcu);
I'm sorry, I don't follow.
I'll try to compare it to the optee.
In optee, clients and the supplicant run in two different contexts. If the supplicant is available, the client will wait for it to finish processing the queued request. The supplicant is guaranteed to be timely and responsive.
In QCOMTEE:
1. There are multiple supplicants. Any process that implements a callback object is considered a supplicant. The general assumption of timeliness or responsiveness may not apply. We allow the client to at least receive fatal signals (this design can be extended later if a timeout is required).
2. A client can implement a callback object and act as both a client and a supplicant simultaneously. To terminate such a process, we need to be able to accept fatal signals.
srcu is specifically used to protect the args array. After returning from qcom_tee_user_object_dispatch, the args array might not be valid. We need to ensure no one is accessing the args array before the retun, hence synchronize_srcu. Whenever we read the contents of args, we do it within an srcu read lock.
For example, qcomtee_user_object_pop, which picks a request for the supplicant to process, will hold the srcu read lock when marshaling the args array to the TEE subsystem's params array.
An alternative to the srcu would be to use "context lock" ctx->lock and hold it throughout the qcomtee_user_object_pop function, even when marshaling the args array to the TEE subsystem's params array.
Using ctx->lock is easier to follow, but since it's shared by everyone in a context and marshaling can be heavy depending on the type of objects, I thought srcu would be more performant.
In other words, srcu just moves the marshaling of objects outside of ctx->lock. What do you think about keeping srcu or replacing it with ctx->lock?
Thank you so much Jens for the comments.
Best Reagrds, Amir
Cheers, Jens
/* Supplicant did some work for us, we should not discard it. */
if (prev_state == QCOM_TEE_REQ_PROCESSED) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
}
}
return errno;
+}
+/* Called after submitting the callback response. */ +static void qcom_tee_user_object_end_cb_notify(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *unused_object, int err)
+{
struct qcom_tee_user_req *ureq = oic->data;
struct qcom_tee_arg *u = ureq->args;
struct qcom_tee_object *object;
int i;
qcom_tee_arg_for_each_output_object(i, u) {
object = u[i].o;
/* If err, drop QTEE copy otherwise just drop driver's copy. */
if (err && (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT))
qcom_tee_object_put(object);
qcom_tee_object_put(object);
}
kfree(ureq);
+}
+static void qcom_tee_user_object_release(struct qcom_tee_object *object) +{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_context *ctx = uo->ctx;
struct qcom_tee_user_req *ureq;
static struct qcom_tee_arg args[] = { { .type = QCOM_TEE_ARG_TYPE_INV } };
if (READ_ONCE(uo->nor)) {
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (ureq) {
ureq->object_id = uo->object_id;
ureq->op = QCOM_TEE_OBJREF_OP_RELEASE;
ureq->args = args;
/* Queue the RELEASE request and wake supplicant to process it. */
qcom_tee_request_enqueue(ureq, ctx);
complete(&ctx->req_c);
} else {
pr_err("failed to notify user object (%s) release.\n",
qcom_tee_object_name(object));
}
}
/* Matching get is in qcom_tee_user_param_to_object(). */
kref_put(&ctx->ref_cnt, __qcom_tee_context_destroy);
kfree(uo);
+}
+static struct qcom_tee_object_operations qcom_tee_user_object_ops = {
.release = qcom_tee_user_object_release,
.notify = qcom_tee_user_object_end_cb_notify,
.dispatch = qcom_tee_user_object_dispatch,
+};
+/**
- qcom_tee_user_param_to_object() - Convert OBJREF parameter to &struct qcom_tee_object.
- @object: object returned.
- @param: TEE parameter.
- @ctx: context in which the conversion should happen.
- @param is OBJREF with %QCOM_TEE_OBJREF_FLAG_USER flags.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_param_to_object(struct qcom_tee_object **object, struct tee_param *param,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_user_object *user_object __free(kfree) = NULL;
struct qcom_tee_object *uo;
int err;
user_object = kzalloc(sizeof(*user_object), GFP_KERNEL);
if (!user_object)
return -ENOMEM;
user_object->ctx = ctx;
user_object->object_id = param->u.objref.id;
/* By default, always notify userspace on release. */
user_object->nor = true;
err = qcom_tee_object_user_init(&user_object->object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
&qcom_tee_user_object_ops, "uo-%lu", param->u.objref.id);
if (err)
return err;
uo = &no_free_ptr(user_object)->object;
/* Keep context alive as user object is alive. */
kref_get(&ctx->ref_cnt);
*object = uo;
return 0;
+}
+/* Reverse what qcom_tee_user_param_to_object() does. */ +int qcom_tee_user_param_from_object(struct tee_param *param, struct qcom_tee_object *object,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_user_object *uo;
if (!is_qcom_tee_user_object(object))
return -EINVAL;
uo = to_qcom_tee_user_object(object);
/* Sure if the object is in a same context as caller? */
if (uo->ctx != ctx)
return -EINVAL;
param->u.objref.id = uo->object_id;
param->u.objref.flags = QCOM_TEE_OBJREF_FLAG_USER;
/* User objects are valid in the context of userspace; drop the driver copy. */
qcom_tee_object_put(object);
return 0;
+}
+/**
- qcom_tee_cb_params_from_args() - Convert QTEE arguments to TEE parameters in a context.
- @params: TEE parameters.
- @u: QTEE arguments.
- @num_params: number of elements in the parameter array.
- @ubuf_addr: user buffer for argument of type %QCOM_TEE_ARG_TYPE_IB.
- @ubuf_size: size of user buffer.
- @ctx: context in which the conversion should happen.
- It expects @params to have enough entries for @u. Entries in @params are of
- %TEE_IOCTL_PARAM_ATTR_TYPE_NONE. On failure, it puts IO objects.
- Return: On success return number of input parameters processed or <0 on failure.
- */
+static int qcom_tee_cb_params_from_args(struct tee_param *params,
struct qcom_tee_arg *u, int num_params,
void __user *ubuf_addr, size_t ubuf_size,
struct qcom_tee_context *ctx)
+{
int i, np = 0;
qcom_tee_arg_for_each(i, u) {
enum qcom_tee_arg_type at = u[i].type;
if (at == QCOM_TEE_ARG_TYPE_IB) {
params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_INPUT;
/* Underflow already checked in qcom_tee_supp_pop_entry(). */
ubuf_size = round_down(ubuf_size - u[i].b.size, 8);
params[np].u.membuf.uaddr = (void * __user)(ubuf_addr + ubuf_size);
params[np].u.membuf.size = u[i].b.size;
if (copy_to_user(params[np].u.membuf.uaddr, u[i].b.addr, u[i].b.size))
goto out_failed;
np++;
} else if (at == QCOM_TEE_ARG_TYPE_IO) {
params[np].attr = TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT;
if (qcom_tee_objref_from_arg(¶ms[np], &u[i], ctx))
goto out_failed;
np++;
}
}
return np;
+out_failed:
/* On ERROR: */
/* - Release params associated to QTEE objects in this context so far. */
for (; np >= 0; np--) {
if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT)
qcom_tee_context_del_qtee_object(¶ms[np], ctx);
}
/* - Release any IO objects not processed so far. */
while (u[i].type) {
if (u[i].type == QCOM_TEE_ARG_TYPE_IO)
qcom_tee_object_put(u[i++].o);
}
return -EINVAL;
+}
+/**
- qcom_tee_cb_params_to_args() - Convert TEE parameters to QTEE arguments in a context.
- @u: QTEE arguments.
- @params: TEE parameters.
- @num_params: number of elements in the parameter array.
- @ctx: context in which the conversion should happen.
- Return: On success return 0 or <0 on failure.
- */
+static int qcom_tee_cb_params_to_args(struct qcom_tee_arg *u,
struct tee_param *params, int num_params,
struct qcom_tee_context *ctx)
+{
struct qcom_tee_object *object;
int i, np = 0;
qcom_tee_arg_for_each(i, u) {
enum qcom_tee_arg_type at = u[i].type;
if (at == QCOM_TEE_ARG_TYPE_OB) {
if (params[np].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF_OUTPUT ||
params[np].u.membuf.size > u[i].b.size)
goto out_failed;
if (copy_from_user(u[i].b.addr, params[np].u.membuf.uaddr, u[i].b.size))
goto out_failed;
u[i].b.size = params[np].u.membuf.size;
np++;
} else if (at == QCOM_TEE_ARG_TYPE_OO) {
if (params[np].attr == TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT) {
if (qcom_tee_objref_to_arg(&u[i], ¶ms[np], ctx))
goto out_failed;
} else {
goto out_failed;
}
np++;
}
}
return 0;
+out_failed:
/* On ERROR: */
/* Put OO objects processed so far. */
for (--i; i >= 0; i--) {
if (u[i].type != QCOM_TEE_ARG_TYPE_OO)
continue;
object = u[i].o;
qcom_tee_user_object_set_notify(object, false);
/* For callback object, we hold a reference for the driver, put it. */
if (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT)
qcom_tee_object_put(object);
/* Put QTEE copy of object. */
qcom_tee_object_put(object);
}
return -EINVAL;
+}
+/**
- qcom_tee_user_object_pop() - Pop a request for a user object.
- @ctx: context to look for user object.
- @params: parameters for @op.
- @num_params: number of elements in the parameter array.
- @uaddr: user buffer for output MEMBUF parameters.
- @size: size of user buffer @uaddr.
- @data: information for the pop request.
- @params is filled along with @data for the picked request.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_pop(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params,
void __user *uaddr, size_t size,
struct qcom_tee_user_object_request_data *data)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *req_args;
u64 req_object_id;
u32 req_op;
int req_id;
int ret;
while (1) {
scoped_guard(srcu, &ctx->req_srcu) {
/* Pop a request 'QUEUED -> PROCESSING'. */
ureq = qcom_tee_supp_pop_entry(ctx, size, num_params);
if (!ureq)
goto wait_for_request;
/* On failure, issue with params, e.g. not enough space in user buffer. */
if (IS_ERR(ureq))
return PTR_ERR(ureq);
/* ''Prepare user request:'' */
req_id = ureq->req_id;
req_object_id = ureq->object_id;
req_op = ureq->op;
req_args = ureq->args;
ret = qcom_tee_cb_params_from_args(params, req_args,
num_params, uaddr,
size, ctx);
}
if (ret >= 0)
break;
/* On failure, issue with req_args, e.g. invalid object. */
scoped_guard(mutex, &ctx->lock) {
/* If (!= __empty_req) then 'PROCESSING -> PROCESSED'. */
if (idr_remove(&ctx->reqs_idr, req_id) == ureq) {
ureq->state = QCOM_TEE_REQ_PROCESSED;
ureq->errno = ret;
/* Send error to QTEE. */
complete(&ureq->c);
}
}
/* Try next request. */
continue;
+wait_for_request:
/* Wait for a new QUEUED request. */
if (wait_for_completion_interruptible(&ctx->req_c))
return -ERESTARTSYS;
}
/* It is a RELEASE message; no one is waiting for result. */
if (req_op == QCOM_TEE_OBJREF_OP_RELEASE) {
scoped_guard(mutex, &ctx->lock)
idr_remove(&ctx->reqs_idr, req_id);
kfree(ureq);
}
/* ''Pick a pending request:'' */
data->id = req_id;
data->object_id = req_object_id;
data->op = req_op;
data->np = ret;
return 0;
+}
+/**
- qcom_tee_user_object_submit() - Submit a response for a user object.
- @ctx: context to look for user object.
- @params: returned parameters.
- @num_params: number of elements in the parameter array.
- @id: request id for the response.
- @errno: result of user object invocation.
- Return: On success return 0 or <0 on failure.
- */
+int qcom_tee_user_object_submit(struct qcom_tee_context *ctx,
struct tee_param *params, int num_params, int id, int errno)
+{
struct qcom_tee_user_req *ureq;
scoped_guard(srcu, &ctx->req_srcu) {
scoped_guard(mutex, &ctx->lock) {
ureq = idr_remove(&ctx->reqs_idr, id);
/* Is id invalid, or no one is waiting on response. */
if (ureq == &__empty_ureq || !ureq)
return -ENODEV;
ureq->state = QCOM_TEE_REQ_PROCESSED;
}
ureq->errno = errno;
/* Process params only on SUCCESS. */
if (!errno)
ureq->errno = qcom_tee_cb_params_to_args(ureq->args, params,
num_params, ctx);
errno = (!errno && ureq->errno) ? ureq->errno : 0;
/* Send result to QTEE. */
complete(&ureq->c);
}
return errno;
+} diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 5d33a8009efb..00e599d1582d 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -59,6 +59,7 @@ #define TEE_IMPL_ID_OPTEE 1 #define TEE_IMPL_ID_AMDTEE 2 #define TEE_IMPL_ID_TSTEE 3 +#define TEE_IMPL_ID_QTEE 4
/*
- OP-TEE specific capabilities
-- 2.34.1
Hi Amirreza,
On Wed, Dec 11, 2024 at 01:30:22PM +1100, Amirreza Zarrabi wrote: [snip]
+/**
- struct qcom_tee_context - Clients or supplicants context.
- @tee_context: TEE context.
- @qtee_objects_idr: QTEE objects in this context.
- @reqs_idr: Requests currently being processed.
- @lock: mutex for @reqs_idr and @qtee_objects_idr.
- @req_srcu: srcu for exclusive access to requests.
- @req_c: completion used when supplicant is waiting for requests.
- @released: state of this context.
- @ref_cnt: ref count.
- */
+struct qcom_tee_context {
Other drivers call their conterpart of this struct *_context_data. Using the same pattern here makes it easier to recognize the struct in the rest of the code.
Ack.
struct tee_context *tee_context;
struct idr qtee_objects_idr;
struct idr reqs_idr;
/* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */
struct mutex lock;
struct srcu_struct req_srcu;
Why do you use this synchronization primitive? I don't know enough about this primitive to tell if you use it for the right purpose so perhaps you can help me understand which properties you need.
Sure, let me explain it bellow in the qcom_tee_user_object_dispatch, where it is acually used.
struct completion req_c;
int released;
struct kref ref_cnt;
Why does this struct need a different lifetime than struct tee_context?
This is a side effect of how QTEE objects and callback objects are released:
When a tee_context is closed, we release all QTEE objects in that context. QTEE specifies that object releases are asynchronous. So, we queue the releases in a workqueue and immediately return from the release callback, allowing the TEE subsystem to continue.
When the workqueue sends a release for a QTEE object, QTEE may respond by requesting the release of a callback object or an operation on a callback object. This requires a valid struct qcom_tee_context. That's why we keep this until all callback objects are gone.
The alternative is to keep a list of callback objects in this context and flag them as orphans. The refcount seems easier :).
It would be even easier if it was already dealt with by the TEE subsystem. :-)
It looks like we have the same problem as with the tee_shm objects when the tee_context should go away. Would it work to add another callback, close_contex(), to tee_driver_ops to be called from teedev_close_context()? The release() callback would still be called as usual when the last reference is gone, but the backend TEE driver would get a notification earlier with core_contex() that it's time to start releasing resources.
[snip]
+/**
- qcom_tee_supp_pop_entry() - Pop the next request in a context.
When you pop something you'd expect it to be removed also.
I'll rename it to more apporpriate name.
- @ctx: context from which to pop a request.
- @ubuf_size: size of available buffer for MEMBUF parameters.
- @num_params: number of entries for TEE parameter array.
- It does not remove the request from &qcom_tee_context.reqs_idr.
- It checks if @num_params is large enough to fit the next request arguments.
- It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE.
- It updates request state to %QCOM_TEE_REQ_PROCESSING state.
- Return: On success return a request or NULL and ERR_PTR on failure.
- */
+static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx,
size_t ubuf_size, int num_params)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *u;
int i, id;
guard(mutex)(&ctx->lock);
/* Find the a QUEUED request. */
Is it _a_ or _the_?
idr_for_each_entry(&ctx->reqs_idr, ureq, id)
if (ureq->state == QCOM_TEE_REQ_QUEUED)
break;
Will this always result in a FIFO processing?
It not a FIFO. I understand your concerns. I'll replace it with a list.
if (!ureq)
return NULL;
u = ureq->args;
/* (1) Is there enough TEE parameters? */
if (num_params < qcom_tee_args_len(u))
return ERR_PTR(-EINVAL);
/* (2) Is there enough space to pass input buffers? */
qcom_tee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
/* Ready to process request 'QUEUED -> PROCESSING'. */
ureq->state = QCOM_TEE_REQ_PROCESSING;
return ureq;
+}
+/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op,
struct qcom_tee_arg *args)
+{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_user_req *ureq __free(kfree);
struct qcom_tee_context *ctx = uo->ctx;
int errno;
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
/* Queue the request. */
if (qcom_tee_request_enqueue(ureq, ctx))
return -ENODEV;
/* Wakeup supplicant to process it. */
complete(&ctx->req_c);
/* Wait for supplicant to process the request. */
/* Supplicant is expected to process request in a timely manner. We wait as KILLABLE,
requests
* in case supplicant and invoke thread both running from a same user process, otherwise
the same
* the process stuck on fatal signal.
might get stuck on a fatal signal?
*/
Please combine into one comment.
Ack.
if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcom_tee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctx->lock) {
prev_state = ureq->state;
/* Replace ureq with '__empty_ureq' to keep req_id reserved. */
if (prev_state == QCOM_TEE_REQ_PROCESSING)
idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id);
/* Remove ureq as supplicant has never seen this request. */
else if (prev_state == QCOM_TEE_REQ_QUEUED)
idr_remove(&ctx->reqs_idr, ureq->req_id);
}
/* Wait for exclusive access to ureq. */
synchronize_srcu(&ctx->req_srcu);
I'm sorry, I don't follow.
I'll try to compare it to the optee.
In optee, clients and the supplicant run in two different contexts. If the supplicant is available, the client will wait for it to finish processing the queued request. The supplicant is guaranteed to be timely and responsive.
Yeah, or at least trusted to be timely and responsive.
In QCOMTEE:
There are multiple supplicants. Any process that implements a callback object is considered a supplicant. The general assumption of timeliness or responsiveness may not apply. We allow the client to at least receive fatal signals (this design can be extended later if a timeout is required).
A client can implement a callback object and act as both a client and a supplicant simultaneously. To terminate such a process, we need to be able to accept fatal signals.
We accept tee-supplicant to be killed so this is similar.
srcu is specifically used to protect the args array. After returning from qcom_tee_user_object_dispatch, the args array might not be valid. We need to ensure no one is accessing the args array before the retun, hence synchronize_srcu. Whenever we read the contents of args, we do it within an srcu read lock.
For example, qcomtee_user_object_pop, which picks a request for the supplicant to process, will hold the srcu read lock when marshaling the args array to the TEE subsystem's params array.
An alternative to the srcu would be to use "context lock" ctx->lock and hold it throughout the qcomtee_user_object_pop function, even when marshaling the args array to the TEE subsystem's params array.
Using ctx->lock is easier to follow, but since it's shared by everyone in a context and marshaling can be heavy depending on the type of objects, I thought srcu would be more performant.
In other words, srcu just moves the marshaling of objects outside of ctx->lock. What do you think about keeping srcu or replacing it with ctx->lock?
Let's continue the comparison with OP-TEE where struct optee_supp_req plays the role of struct qcom_tee_user_req in QCOMTEE. You can say that access rights to the optee_supp_req follows with the owner. The supp->mutex is in principle only held while changing owner. Couldn't the ctx->lock be used in a similar way, avoiding it while marshalling objects?
I'm open to be persuaded if you think that srcu is a better choice.
Cheers, Jens
Hi Jens,
On 12/12/2024 1:04 AM, Jens Wiklander wrote:
Hi Amirreza,
On Wed, Dec 11, 2024 at 01:30:22PM +1100, Amirreza Zarrabi wrote: [snip]
+/**
- struct qcom_tee_context - Clients or supplicants context.
- @tee_context: TEE context.
- @qtee_objects_idr: QTEE objects in this context.
- @reqs_idr: Requests currently being processed.
- @lock: mutex for @reqs_idr and @qtee_objects_idr.
- @req_srcu: srcu for exclusive access to requests.
- @req_c: completion used when supplicant is waiting for requests.
- @released: state of this context.
- @ref_cnt: ref count.
- */
+struct qcom_tee_context {
Other drivers call their conterpart of this struct *_context_data. Using the same pattern here makes it easier to recognize the struct in the rest of the code.
Ack.
struct tee_context *tee_context;
struct idr qtee_objects_idr;
struct idr reqs_idr;
/* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */
struct mutex lock;
struct srcu_struct req_srcu;
Why do you use this synchronization primitive? I don't know enough about this primitive to tell if you use it for the right purpose so perhaps you can help me understand which properties you need.
Sure, let me explain it bellow in the qcom_tee_user_object_dispatch, where it is acually used.
struct completion req_c;
int released;
struct kref ref_cnt;
Why does this struct need a different lifetime than struct tee_context?
This is a side effect of how QTEE objects and callback objects are released:
When a tee_context is closed, we release all QTEE objects in that context. QTEE specifies that object releases are asynchronous. So, we queue the releases in a workqueue and immediately return from the release callback, allowing the TEE subsystem to continue.
When the workqueue sends a release for a QTEE object, QTEE may respond by requesting the release of a callback object or an operation on a callback object. This requires a valid struct qcom_tee_context. That's why we keep this until all callback objects are gone.
The alternative is to keep a list of callback objects in this context and flag them as orphans. The refcount seems easier :).
It would be even easier if it was already dealt with by the TEE subsystem. :-)
It looks like we have the same problem as with the tee_shm objects when the tee_context should go away. Would it work to add another callback, close_contex(), to tee_driver_ops to be called from teedev_close_context()? The release() callback would still be called as usual when the last reference is gone, but the backend TEE driver would get a notification earlier with core_contex() that it's time to start releasing resources.
Yes, it works.
This proposal is similar to our original discussion about adding a shutdown() callback along with release(). With this change, we can also drop [1].
It seems like the easiest solution. I'll add close_context().
[1] https://lore.kernel.org/all/20241120-fix-tee_shm-refcount-upstream-v1-0-5da9...
[snip]
+/**
- qcom_tee_supp_pop_entry() - Pop the next request in a context.
When you pop something you'd expect it to be removed also.
I'll rename it to more apporpriate name.
- @ctx: context from which to pop a request.
- @ubuf_size: size of available buffer for MEMBUF parameters.
- @num_params: number of entries for TEE parameter array.
- It does not remove the request from &qcom_tee_context.reqs_idr.
- It checks if @num_params is large enough to fit the next request arguments.
- It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE.
- It updates request state to %QCOM_TEE_REQ_PROCESSING state.
- Return: On success return a request or NULL and ERR_PTR on failure.
- */
+static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx,
size_t ubuf_size, int num_params)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *u;
int i, id;
guard(mutex)(&ctx->lock);
/* Find the a QUEUED request. */
Is it _a_ or _the_?
idr_for_each_entry(&ctx->reqs_idr, ureq, id)
if (ureq->state == QCOM_TEE_REQ_QUEUED)
break;
Will this always result in a FIFO processing?
It not a FIFO. I understand your concerns. I'll replace it with a list.
if (!ureq)
return NULL;
u = ureq->args;
/* (1) Is there enough TEE parameters? */
if (num_params < qcom_tee_args_len(u))
return ERR_PTR(-EINVAL);
/* (2) Is there enough space to pass input buffers? */
qcom_tee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
/* Ready to process request 'QUEUED -> PROCESSING'. */
ureq->state = QCOM_TEE_REQ_PROCESSING;
return ureq;
+}
+/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op,
struct qcom_tee_arg *args)
+{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_user_req *ureq __free(kfree);
struct qcom_tee_context *ctx = uo->ctx;
int errno;
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
/* Queue the request. */
if (qcom_tee_request_enqueue(ureq, ctx))
return -ENODEV;
/* Wakeup supplicant to process it. */
complete(&ctx->req_c);
/* Wait for supplicant to process the request. */
/* Supplicant is expected to process request in a timely manner. We wait as KILLABLE,
requests
* in case supplicant and invoke thread both running from a same user process, otherwise
the same
* the process stuck on fatal signal.
might get stuck on a fatal signal?
*/
Please combine into one comment.
Ack.
if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcom_tee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctx->lock) {
prev_state = ureq->state;
/* Replace ureq with '__empty_ureq' to keep req_id reserved. */
if (prev_state == QCOM_TEE_REQ_PROCESSING)
idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id);
/* Remove ureq as supplicant has never seen this request. */
else if (prev_state == QCOM_TEE_REQ_QUEUED)
idr_remove(&ctx->reqs_idr, ureq->req_id);
}
/* Wait for exclusive access to ureq. */
synchronize_srcu(&ctx->req_srcu);
I'm sorry, I don't follow.
I'll try to compare it to the optee.
In optee, clients and the supplicant run in two different contexts. If the supplicant is available, the client will wait for it to finish processing the queued request. The supplicant is guaranteed to be timely and responsive.
Yeah, or at least trusted to be timely and responsive.
In QCOMTEE:
There are multiple supplicants. Any process that implements a callback object is considered a supplicant. The general assumption of timeliness or responsiveness may not apply. We allow the client to at least receive fatal signals (this design can be extended later if a timeout is required).
A client can implement a callback object and act as both a client and a supplicant simultaneously. To terminate such a process, we need to be able to accept fatal signals.
We accept tee-supplicant to be killed so this is similar.
True, the tee-supplicant can be terminated, but the client cannot be if it's waiting for a trusted supplicant response. That's reasonable.
However, in qcomtee, both the client and supplicant can be threads within a single process. If the process is killed, the supplicant thread can go away, leaving the client stuck waiting. Therefore, in qcomtee, the client also needs to be killable.
srcu is specifically used to protect the args array. After returning from qcom_tee_user_object_dispatch, the args array might not be valid. We need to ensure no one is accessing the args array before the retun, hence synchronize_srcu. Whenever we read the contents of args, we do it within an srcu read lock.
For example, qcomtee_user_object_pop, which picks a request for the supplicant to process, will hold the srcu read lock when marshaling the args array to the TEE subsystem's params array.
An alternative to the srcu would be to use "context lock" ctx->lock and hold it throughout the qcomtee_user_object_pop function, even when marshaling the args array to the TEE subsystem's params array.
Using ctx->lock is easier to follow, but since it's shared by everyone in a context and marshaling can be heavy depending on the type of objects, I thought srcu would be more performant.
In other words, srcu just moves the marshaling of objects outside of ctx->lock. What do you think about keeping srcu or replacing it with ctx->lock?
Let's continue the comparison with OP-TEE where struct optee_supp_req plays the role of struct qcom_tee_user_req in QCOMTEE. You can say that access rights to the optee_supp_req follows with the owner. The supp->mutex is in principle only held while changing owner. Couldn't the ctx->lock be used in a similar way, avoiding it while marshalling objects?
True, but there's a corner case due to the TASK_KILLABLE flag.
In optee, when a request is placed in the "supplicant queue" supp->reqs (passing the access right to the supplicant), the client won't touch the request until notified by the supplicant.
In qcomtee, besides the notification from the supplicant, we also accept fatal signals. This causes the client to access the request without any notification from supplicant, violating the exclusive access assumption.
I'm open to be persuaded if you think that srcu is a better choice.
The use of the srcu was not for correctness, and purely for the sake of performance. Most of our internal tests are micro tests for the API at the moment, so I do not have any number to support the argument :(.
I can stick to the ctx->lock and add srcu later if necessary when e2e tests are active and I can collect some numbers? What do you think?
Best Regards, Amir
Cheers, Jens
Hi Armirreza,
On Thu, Dec 12, 2024 at 1:19 AM Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
Hi Jens,
On 12/12/2024 1:04 AM, Jens Wiklander wrote:
Hi Amirreza,
On Wed, Dec 11, 2024 at 01:30:22PM +1100, Amirreza Zarrabi wrote: [snip]
+/**
- struct qcom_tee_context - Clients or supplicants context.
- @tee_context: TEE context.
- @qtee_objects_idr: QTEE objects in this context.
- @reqs_idr: Requests currently being processed.
- @lock: mutex for @reqs_idr and @qtee_objects_idr.
- @req_srcu: srcu for exclusive access to requests.
- @req_c: completion used when supplicant is waiting for requests.
- @released: state of this context.
- @ref_cnt: ref count.
- */
+struct qcom_tee_context {
Other drivers call their conterpart of this struct *_context_data. Using the same pattern here makes it easier to recognize the struct in the rest of the code.
Ack.
struct tee_context *tee_context;
struct idr qtee_objects_idr;
struct idr reqs_idr;
/* Synchronize access to @reqs_idr, @qtee_objects_idr and updating requests state. */
struct mutex lock;
struct srcu_struct req_srcu;
Why do you use this synchronization primitive? I don't know enough about this primitive to tell if you use it for the right purpose so perhaps you can help me understand which properties you need.
Sure, let me explain it bellow in the qcom_tee_user_object_dispatch, where it is acually used.
struct completion req_c;
int released;
struct kref ref_cnt;
Why does this struct need a different lifetime than struct tee_context?
This is a side effect of how QTEE objects and callback objects are released:
When a tee_context is closed, we release all QTEE objects in that context. QTEE specifies that object releases are asynchronous. So, we queue the releases in a workqueue and immediately return from the release callback, allowing the TEE subsystem to continue.
When the workqueue sends a release for a QTEE object, QTEE may respond by requesting the release of a callback object or an operation on a callback object. This requires a valid struct qcom_tee_context. That's why we keep this until all callback objects are gone.
The alternative is to keep a list of callback objects in this context and flag them as orphans. The refcount seems easier :).
It would be even easier if it was already dealt with by the TEE subsystem. :-)
It looks like we have the same problem as with the tee_shm objects when the tee_context should go away. Would it work to add another callback, close_contex(), to tee_driver_ops to be called from teedev_close_context()? The release() callback would still be called as usual when the last reference is gone, but the backend TEE driver would get a notification earlier with core_contex() that it's time to start releasing resources.
Yes, it works.
This proposal is similar to our original discussion about adding a shutdown() callback along with release(). With this change, we can also drop [1].
It seems like the easiest solution. I'll add close_context().
[1] https://lore.kernel.org/all/20241120-fix-tee_shm-refcount-upstream-v1-0-5da9...
Good.
[snip]
+/**
- qcom_tee_supp_pop_entry() - Pop the next request in a context.
When you pop something you'd expect it to be removed also.
I'll rename it to more apporpriate name.
- @ctx: context from which to pop a request.
- @ubuf_size: size of available buffer for MEMBUF parameters.
- @num_params: number of entries for TEE parameter array.
- It does not remove the request from &qcom_tee_context.reqs_idr.
- It checks if @num_params is large enough to fit the next request arguments.
- It checks if @ubuf_size is large enough to fit IB buffer arguments from QTEE.
- It updates request state to %QCOM_TEE_REQ_PROCESSING state.
- Return: On success return a request or NULL and ERR_PTR on failure.
- */
+static struct qcom_tee_user_req *qcom_tee_supp_pop_entry(struct qcom_tee_context *ctx,
size_t ubuf_size, int num_params)
+{
struct qcom_tee_user_req *ureq;
struct qcom_tee_arg *u;
int i, id;
guard(mutex)(&ctx->lock);
/* Find the a QUEUED request. */
Is it _a_ or _the_?
idr_for_each_entry(&ctx->reqs_idr, ureq, id)
if (ureq->state == QCOM_TEE_REQ_QUEUED)
break;
Will this always result in a FIFO processing?
It not a FIFO. I understand your concerns. I'll replace it with a list.
if (!ureq)
return NULL;
u = ureq->args;
/* (1) Is there enough TEE parameters? */
if (num_params < qcom_tee_args_len(u))
return ERR_PTR(-EINVAL);
/* (2) Is there enough space to pass input buffers? */
qcom_tee_arg_for_each_input_buffer(i, u) {
ubuf_size = size_sub(ubuf_size, u[i].b.size);
if (ubuf_size == SIZE_MAX)
return ERR_PTR(-EINVAL);
ubuf_size = round_down(ubuf_size, 8);
}
/* Ready to process request 'QUEUED -> PROCESSING'. */
ureq->state = QCOM_TEE_REQ_PROCESSING;
return ureq;
+}
+/* User object dispatcher. */ +static int qcom_tee_user_object_dispatch(struct qcom_tee_object_invoke_ctx *oic,
struct qcom_tee_object *object, u32 op,
struct qcom_tee_arg *args)
+{
struct qcom_tee_user_object *uo = to_qcom_tee_user_object(object);
struct qcom_tee_user_req *ureq __free(kfree);
struct qcom_tee_context *ctx = uo->ctx;
int errno;
ureq = kzalloc(sizeof(*ureq), GFP_KERNEL);
if (!ureq)
return -ENOMEM;
init_completion(&ureq->c);
ureq->object_id = uo->object_id;
ureq->op = op;
ureq->args = args;
/* Queue the request. */
if (qcom_tee_request_enqueue(ureq, ctx))
return -ENODEV;
/* Wakeup supplicant to process it. */
complete(&ctx->req_c);
/* Wait for supplicant to process the request. */
/* Supplicant is expected to process request in a timely manner. We wait as KILLABLE,
requests
* in case supplicant and invoke thread both running from a same user process, otherwise
the same
* the process stuck on fatal signal.
might get stuck on a fatal signal?
*/
Please combine into one comment.
Ack.
if (!wait_for_completion_state(&ureq->c, TASK_KILLABLE | TASK_FREEZABLE)) {
errno = ureq->errno;
/* On SUCCESS, end_cb_notify frees the request. */
if (!errno)
oic->data = no_free_ptr(ureq);
} else {
enum qcom_tee_req_state prev_state;
errno = -ENODEV;
scoped_guard(mutex, &ctx->lock) {
prev_state = ureq->state;
/* Replace ureq with '__empty_ureq' to keep req_id reserved. */
if (prev_state == QCOM_TEE_REQ_PROCESSING)
idr_replace(&ctx->reqs_idr, &__empty_ureq, ureq->req_id);
/* Remove ureq as supplicant has never seen this request. */
else if (prev_state == QCOM_TEE_REQ_QUEUED)
idr_remove(&ctx->reqs_idr, ureq->req_id);
}
/* Wait for exclusive access to ureq. */
synchronize_srcu(&ctx->req_srcu);
I'm sorry, I don't follow.
I'll try to compare it to the optee.
In optee, clients and the supplicant run in two different contexts. If the supplicant is available, the client will wait for it to finish processing the queued request. The supplicant is guaranteed to be timely and responsive.
Yeah, or at least trusted to be timely and responsive.
In QCOMTEE:
There are multiple supplicants. Any process that implements a callback object is considered a supplicant. The general assumption of timeliness or responsiveness may not apply. We allow the client to at least receive fatal signals (this design can be extended later if a timeout is required).
A client can implement a callback object and act as both a client and a supplicant simultaneously. To terminate such a process, we need to be able to accept fatal signals.
We accept tee-supplicant to be killed so this is similar.
True, the tee-supplicant can be terminated, but the client cannot be if it's waiting for a trusted supplicant response. That's reasonable.
However, in qcomtee, both the client and supplicant can be threads within a single process. If the process is killed, the supplicant thread can go away, leaving the client stuck waiting. Therefore, in qcomtee, the client also needs to be killable.
Got it, thanks.
srcu is specifically used to protect the args array. After returning from qcom_tee_user_object_dispatch, the args array might not be valid. We need to ensure no one is accessing the args array before the retun, hence synchronize_srcu. Whenever we read the contents of args, we do it within an srcu read lock.
For example, qcomtee_user_object_pop, which picks a request for the supplicant to process, will hold the srcu read lock when marshaling the args array to the TEE subsystem's params array.
An alternative to the srcu would be to use "context lock" ctx->lock and hold it throughout the qcomtee_user_object_pop function, even when marshaling the args array to the TEE subsystem's params array.
Using ctx->lock is easier to follow, but since it's shared by everyone in a context and marshaling can be heavy depending on the type of objects, I thought srcu would be more performant.
In other words, srcu just moves the marshaling of objects outside of ctx->lock. What do you think about keeping srcu or replacing it with ctx->lock?
Let's continue the comparison with OP-TEE where struct optee_supp_req plays the role of struct qcom_tee_user_req in QCOMTEE. You can say that access rights to the optee_supp_req follows with the owner. The supp->mutex is in principle only held while changing owner. Couldn't the ctx->lock be used in a similar way, avoiding it while marshalling objects?
True, but there's a corner case due to the TASK_KILLABLE flag.
In optee, when a request is placed in the "supplicant queue" supp->reqs (passing the access right to the supplicant), the client won't touch the request until notified by the supplicant.
In qcomtee, besides the notification from the supplicant, we also accept fatal signals. This causes the client to access the request without any notification from supplicant, violating the exclusive access assumption.
I'm open to be persuaded if you think that srcu is a better choice.
The use of the srcu was not for correctness, and purely for the sake of performance. Most of our internal tests are micro tests for the API at the moment, so I do not have any number to support the argument :(.
I can stick to the ctx->lock and add srcu later if necessary when e2e tests are active and I can collect some numbers? What do you think?
That's a good approach. :-)
Cheers, Jens
Best Regards, Amir
Cheers, Jens
Add qcom_tee node.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- arch/arm64/boot/dts/qcom/sm8650.dtsi | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi index 25e47505adcb..41505b032e3a 100644 --- a/arch/arm64/boot/dts/qcom/sm8650.dtsi +++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi @@ -372,6 +372,10 @@ scm: scm { interconnects = <&aggre2_noc MASTER_CRYPTO QCOM_ICC_TAG_ALWAYS &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; }; + + qcom_tee { + compatible = "qcom,tee"; + }; };
clk_virt: interconnect-0 {
On 03/12/2024 05:19, Amirreza Zarrabi wrote:
Add qcom_tee node.
Why? "What" we see from the diff. Stop repeating subjects, but say something useful. This applies to all your patches.
Anyway, your driver and binding say that this is just a fake node used purely to instantiate the Linux driver. Drop the patch.
Best regards, Krzysztof
On 12/3/2024 6:59 PM, Krzysztof Kozlowski wrote:
On 03/12/2024 05:19, Amirreza Zarrabi wrote:
Add qcom_tee node.
Why? "What" we see from the diff. Stop repeating subjects, but say something useful. This applies to all your patches.
Anyway, your driver and binding say that this is just a fake node used purely to instantiate the Linux driver. Drop the patch.
Hi Krzysztof,
Thank you so much for your comments. I totally understand. I'll remove the fake node.
Best Regards, Amir
Best regards, Krzysztof
Add documentation for the Qualcomm TEE driver.
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com --- Documentation/tee/index.rst | 1 + Documentation/tee/qtee.rst | 143 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+)
diff --git a/Documentation/tee/index.rst b/Documentation/tee/index.rst index 4be6e69d7837..62afb7ee9b52 100644 --- a/Documentation/tee/index.rst +++ b/Documentation/tee/index.rst @@ -11,6 +11,7 @@ TEE Subsystem op-tee amd-tee ts-tee + qtee
.. only:: subproject and html
diff --git a/Documentation/tee/qtee.rst b/Documentation/tee/qtee.rst new file mode 100644 index 000000000000..3185af2c2b2b --- /dev/null +++ b/Documentation/tee/qtee.rst @@ -0,0 +1,143 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================= +QTEE (Qualcomm Trusted Execution Environment) +============================================= + +The QTEE driver handle communication with Qualcomm TEE [1]. + +Lowest level of communication with QTEE builds on ARM SMC Calling Convention +(SMCCC) [2], which is the foundation for QTEE's Secure Channel Manager (SCM) [3] +used internally by the driver [4]. + +In a QTEE-based system, services are represented as objects with series of +operations that can be called to produce results, including other objects. When +an object is hosted within QTEE, executing its operations is referred to as direct +invocation. QTEE can invoke objects hosted in the kernel or userspace using a +method known as callback requests. + +The SCM provides two functions for object invocation and callback request: + +- QCOM_SCM_SMCINVOKE_INVOKE for direct invocation. It can return either a result or a callback request. +- QCOM_SCM_SMCINVOKE_CB_RSP submits a response for a previous callback request. + +The QTEE Transport Message [5] is stacked on top of SCM driver functions. + +A message consists of two buffers shared with QTEE, inbound and outbound buffers. +The inbound buffer is used for direct invocation and the outbound buffer is used +to make callback request. This picture shows the contents of a QTEE transport message:: + + +---------------------+ + | v + +-----------------+-------+-------+------+--------------------------+ + | qcom_tee_msg_ |object | buffer | | + | object_invoke | id | offset, size | | (inbound buffer) + +-----------------+-------+--------------+--------------------------+ + <---- header -----><---- arguments ------><- in/out buffer payload -> + + +-----------+ + | v + +-----------------+-------+-------+------+----------------------+ + | qcom_tee_msg_ |object | buffer | | + | callback | id | offset, size | | (outbound buffer) + +-----------------+-------+--------------+----------------------+ + +Each buffer is started with a header and array of arguments. + +QTEE Transport Message supports four types of arguments: + +- Input object is an object parameter to the current invocation or callback request +- Output object is an object parameter from the current invocation or callback request +- Input buffer is (offset, size) pair to the inbound or outbound region to store parameter to the current invocation or callback request +- Output buffer is (offset, size) pair to the inbound or outbound region to store parameter from the current invocation or callback request + +The QTEE driver offers the qcom_tee_object, which represents an object within both +QTEE and the kernel. To access any service in QTEE, client needs to invoke an +instance of this object. Any structure intended to represent a service for export +to QTEE should include an instance of qcom_tee_object:: + + struct driver_service { + struct qcom_tee_object object; + ... + }; + + #define to_driver_service_object(o) container_of((o), struct driver_service, object) + + static int driver_service_dispatch(struct qcom_tee_object *object, u32 op, + struct qcom_tee_arg *args) + { + struct driver_service *so = to_driver_service_object(object); + + switch(op) { + case OBJECT_OP1: + ... + break; + default: + return -EINVAL; + } + } + + static void driver_service_object_release(struct si_object *object) + { + struct driver_service *so = to_driver_service_object(object); + kfree(so); + } + + struct si_object_operations driver_service_ops = { + .release = driver_service_object_release; + .dispatch = driver_service_dispatch; + }; + + void service_init(void) + { + struct driver_service *so = kzalloc(sizeof(*so), GFP_KERNEL); + + /* Initialize so->object as a callback object. */ + qcom_tee_object_user_init(&so->object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT, + &driver_service_ops, "driver_service_object"); + + /* Invoke a QTEE object and pass/register 'so->object' with QTEE. */ + ... + } + module_init(service_init); + +The QTEE driver utilizes qcom_tee_object to encapsulate userspace objects. When a +callback request is made, it translates into calling the dispatch callback. For +userspace objects, this is converted into requests accessible to callback servers +and available through generic TEE API IOCTLs. + +Picture of the relationship between the different components in the QTEE +architecture:: + + User space Kernel Secure world + ~~~~~~~~~~ ~~~~~~ ~~~~~~~~~~~~ + +--------+ +----------+ +--------------+ + | Client | |callback | | Trusted | + +--------+ |server | | Application | + /\ +----------+ +--------------+ + || +----------+ /\ /\ + || |callback | || || + || |server | || / + || +----------+ || +--------------+ + / /\ || | TEE Internal | + +-------+ || || | API | + | TEE | || || +--------+--------+ +--------------+ + | Client| || || | TEE | QTEE | | QTEE | + | API | / / | subsys | driver | | Trusted OS | + +-------+----------------+----+-------+----+-------------+--------------+ + | Generic TEE API | | QTEE MSG | + | IOCTL (TEE_IOC_*) | | SMCCC (QCOM_SCM_SMCINVOKE_*) | + +-----------------------------+ +---------------------------------+ + +References +========== + +[1] https://docs.qualcomm.com/bundle/publicresource/topics/80-70015-11/qualcomm-... + +[2] http://infocenter.arm.com/help/topic/com.arm.doc.den0028a/index.html + +[3] drivers/firmware/qcom/qcom_scm.c + +[4] /local/mnt/workspace/Linux/drivers/tee/qcomtee/qcom_scm.c + +[5] drivers/tee/qcomtee/qcomtee_msg.h \ No newline at end of file
On 03/12/2024 05:19, Amirreza Zarrabi wrote:
+[1] https://docs.qualcomm.com/bundle/publicresource/topics/80-70015-11/qualcomm-...
+[2] http://infocenter.arm.com/help/topic/com.arm.doc.den0028a/index.html
+[3] drivers/firmware/qcom/qcom_scm.c
+[4] /local/mnt/workspace/Linux/drivers/tee/qcomtee/qcom_scm.c
+[5] drivers/tee/qcomtee/qcomtee_msg.h \ No newline at end of file
You have patch warnings. Check your patches before sending.
Best regards, Krzysztof
On 12/2/2024 8:19 PM, Amirreza Zarrabi wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
The first patch series was RFC and now you had removed the RFC. Can you please provide the reasons?
https://lwn.net/ml/all/20240702-qcom-tee-object-and-ioctls-v1-0-633c3ddf57ee...
I understand that you have now changed to tee framework but I want to check if we should continue with the version and increment here or start from [00]?
Based on our discussions, we implemented significant changes. We essentially rewrote most of the files and altered the overall direction, except for a couple of files. The changelog entry would have been extensive.
- Amir
On 12/3/2024 5:06 PM, Trilok Soni wrote:
On 12/2/2024 8:19 PM, Amirreza Zarrabi wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
The first patch series was RFC and now you had removed the RFC. Can you please provide the reasons?
https://lwn.net/ml/all/20240702-qcom-tee-object-and-ioctls-v1-0-633c3ddf57ee...
I understand that you have now changed to tee framework but I want to check if we should continue with the version and increment here or start from [00]?
On Wed, Dec 04, 2024 at 09:13:43AM +1100, Amirreza Zarrabi wrote:
Based on our discussions, we implemented significant changes. We essentially rewrote most of the files and altered the overall direction, except for a couple of files. The changelog entry would have been extensive.
At least some changelog should be provided, even if tells "reworked to use TEE framework, made it jump over the head and tie the shoelaces".
Also please don't top-post, this style is frowned upon in the mailing list discussions, it breaks the logic of reading.
- Amir
On 12/3/2024 5:06 PM, Trilok Soni wrote:
On 12/2/2024 8:19 PM, Amirreza Zarrabi wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
The first patch series was RFC and now you had removed the RFC. Can you please provide the reasons?
https://lwn.net/ml/all/20240702-qcom-tee-object-and-ioctls-v1-0-633c3ddf57ee...
I understand that you have now changed to tee framework but I want to check if we should continue with the version and increment here or start from [00]?
On 12/4/2024 10:43 AM, Dmitry Baryshkov wrote:
On Wed, Dec 04, 2024 at 09:13:43AM +1100, Amirreza Zarrabi wrote:
Based on our discussions, we implemented significant changes. We essentially rewrote most of the files and altered the overall direction, except for a couple of files. The changelog entry would have been extensive.
At least some changelog should be provided, even if tells "reworked to use TEE framework, made it jump over the head and tie the shoelaces".
Sure, I'll provide some changelog.
Also please don't top-post, this style is frowned upon in the mailing list discussions, it breaks the logic of reading.
;) Oops, that shouldn't have been sent out like this. My apologies. I'll ensure it doesn't happen again..
- Amir
- Amir
On 12/3/2024 5:06 PM, Trilok Soni wrote:
On 12/2/2024 8:19 PM, Amirreza Zarrabi wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
The first patch series was RFC and now you had removed the RFC. Can you please provide the reasons?
https://lwn.net/ml/all/20240702-qcom-tee-object-and-ioctls-v1-0-633c3ddf57ee...
I understand that you have now changed to tee framework but I want to check if we should continue with the version and increment here or start from [00]?
Hi Amirreza,
On Tue, 3 Dec 2024 at 09:50, Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
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.
Note: This patch series focuses on QTEE objects and userspace services.
Linux already provides a TEE subsystem, which is described in [1]. The tee subsystem provides a generic ioctl interface, TEE_IOC_INVOKE, which can be used by userspace to talk to a TEE backend driver. We extend the Linux 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
Thanks for proposing to extend the TEE subsystem. However, I couldn't see any user-space library or test application(s) to test this new IOCTL. Say for example how will someone with a board based on sm8650 will be able to test the QTEE interface? IIRC, last time we agreed off list that you will be pushing that alongside this patch-set.
-Sumit
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.
The details of QTEE Transport Message to communicate with QTEE is available in [PATCH 10/10] Documentation: tee: Add Qualcomm TEE driver.
This patch series has been tested for basic QTEE object invocations and callback requests, including loading a TA and requesting services form the TA. However, the test platform is currently being prepared for upstream availability and will soon be accessible to the community for further validation. I will share updates as the platform become available.
[1] https://www.kernel.org/doc/Documentation/tee.txt
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
Amirreza Zarrabi (10): tee: allow a driver to allocate a tee_device without a pool tee: add TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF tee: add TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF firmware: qcom: scm: add support for object invocation qcomtee: implement object invoke support qcomtee: add primordial object dt-bindings: arm: qcomtee: add QTEE driver devicetree binding for TEE subsystem tee: add Qualcomm TEE driver arm64: dts: qcom: sm8650: add support for QTEE Documentation: tee: Add Qualcomm TEE driver
.../devicetree/bindings/arm/firmware/qcom,tee.yaml | 34 + Documentation/tee/index.rst | 1 + Documentation/tee/qtee.rst | 143 ++++ arch/arm64/boot/dts/qcom/sm8650.dtsi | 4 + drivers/firmware/qcom/qcom_scm.c | 60 ++ drivers/firmware/qcom/qcom_scm.h | 7 + drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 9 + drivers/tee/qcomtee/async.c | 153 ++++ drivers/tee/qcomtee/call.c | 707 ++++++++++++++++ drivers/tee/qcomtee/core.c | 942 +++++++++++++++++++++ drivers/tee/qcomtee/primordial_obj.c | 63 ++ drivers/tee/qcomtee/qcom_scm.c | 36 + drivers/tee/qcomtee/qcomtee_msg.h | 217 +++++ drivers/tee/qcomtee/qcomtee_private.h | 218 +++++ drivers/tee/qcomtee/release.c | 66 ++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++ drivers/tee/tee_core.c | 113 ++- include/linux/firmware/qcom/qcom_scm.h | 9 + include/linux/firmware/qcom/qcom_tee.h | 284 +++++++ include/linux/tee_core.h | 4 + include/linux/tee_drv.h | 12 + include/uapi/linux/tee.h | 54 +- 25 files changed, 3765 insertions(+), 8 deletions(-)
base-commit: f486c8aa16b8172f63bddc70116a0c897a7f3f02 change-id: 20241202-qcom-tee-using-tee-ss-without-mem-obj-362c66340527
Best regards,
Amirreza Zarrabi quic_azarrabi@quicinc.com
Hi Sumit,
On 12/19/2024 1:25 AM, Sumit Garg wrote:
Hi Amirreza,
On Tue, 3 Dec 2024 at 09:50, Amirreza Zarrabi quic_azarrabi@quicinc.com wrote:
This patch series 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. Clients can invoke these operations on objects, which can generate results, including other objects. For example, an object can load a TA and return another object that represents the loaded TA, allowing access to its services.
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.
Note: This patch series focuses on QTEE objects and userspace services.
Linux already provides a TEE subsystem, which is described in [1]. The tee subsystem provides a generic ioctl interface, TEE_IOC_INVOKE, which can be used by userspace to talk to a TEE backend driver. We extend the Linux 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
Thanks for proposing to extend the TEE subsystem. However, I couldn't see any user-space library or test application(s) to test this new IOCTL. Say for example how will someone with a board based on sm8650 will be able to test the QTEE interface? IIRC, last time we agreed off list that you will be pushing that alongside this patch-set.
:) Indeed. The upcoming version of this patch series will include a library and a test application.
Best Regards, Amir
-Sumit
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.
The details of QTEE Transport Message to communicate with QTEE is available in [PATCH 10/10] Documentation: tee: Add Qualcomm TEE driver.
This patch series has been tested for basic QTEE object invocations and callback requests, including loading a TA and requesting services form the TA. However, the test platform is currently being prepared for upstream availability and will soon be accessible to the community for further validation. I will share updates as the platform become available.
[1] https://www.kernel.org/doc/Documentation/tee.txt
Signed-off-by: Amirreza Zarrabi quic_azarrabi@quicinc.com
Amirreza Zarrabi (10): tee: allow a driver to allocate a tee_device without a pool tee: add TEE_IOCTL_PARAM_ATTR_TYPE_MEMBUF tee: add TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF firmware: qcom: scm: add support for object invocation qcomtee: implement object invoke support qcomtee: add primordial object dt-bindings: arm: qcomtee: add QTEE driver devicetree binding for TEE subsystem tee: add Qualcomm TEE driver arm64: dts: qcom: sm8650: add support for QTEE Documentation: tee: Add Qualcomm TEE driver
.../devicetree/bindings/arm/firmware/qcom,tee.yaml | 34 + Documentation/tee/index.rst | 1 + Documentation/tee/qtee.rst | 143 ++++ arch/arm64/boot/dts/qcom/sm8650.dtsi | 4 + drivers/firmware/qcom/qcom_scm.c | 60 ++ drivers/firmware/qcom/qcom_scm.h | 7 + drivers/tee/Kconfig | 1 + drivers/tee/Makefile | 1 + drivers/tee/qcomtee/Kconfig | 10 + drivers/tee/qcomtee/Makefile | 9 + drivers/tee/qcomtee/async.c | 153 ++++ drivers/tee/qcomtee/call.c | 707 ++++++++++++++++ drivers/tee/qcomtee/core.c | 942 +++++++++++++++++++++ drivers/tee/qcomtee/primordial_obj.c | 63 ++ drivers/tee/qcomtee/qcom_scm.c | 36 + drivers/tee/qcomtee/qcomtee_msg.h | 217 +++++ drivers/tee/qcomtee/qcomtee_private.h | 218 +++++ drivers/tee/qcomtee/release.c | 66 ++ drivers/tee/qcomtee/user_obj.c | 625 ++++++++++++++ drivers/tee/tee_core.c | 113 ++- include/linux/firmware/qcom/qcom_scm.h | 9 + include/linux/firmware/qcom/qcom_tee.h | 284 +++++++ include/linux/tee_core.h | 4 + include/linux/tee_drv.h | 12 + include/uapi/linux/tee.h | 54 +- 25 files changed, 3765 insertions(+), 8 deletions(-)
base-commit: f486c8aa16b8172f63bddc70116a0c897a7f3f02 change-id: 20241202-qcom-tee-using-tee-ss-without-mem-obj-362c66340527
Best regards,
Amirreza Zarrabi quic_azarrabi@quicinc.com
op-tee@lists.trustedfirmware.org