Re: [PATCH 08/10] tee: add Qualcomm TEE driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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:
> 
>   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.

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




[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux