// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2013-2019 TRUSTONIC LIMITED * All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include /* struct task_struct */ #include #include /* get_task_mm */ #include /* put_task_struct */ #include /* sockfd_lookup */ #include /* fput */ #include "public/mc_user.h" #include "public/mc_admin.h" #include "main.h" #include "mmu.h" #include "session.h" #include "client.h" /* Client/context */ struct tee_client { /* PID of task that opened the device, 0 if kernel */ pid_t pid; /* Command for task*/ char comm[TASK_COMM_LEN]; /* Number of references kept to this object */ struct kref kref; /* List of contiguous buffers allocated by mcMallocWsm for the client */ struct list_head cbufs; struct mutex cbufs_lock; /* lock for the cbufs list */ /* List of TA sessions opened by this client */ struct list_head sessions; struct list_head closing_sessions; struct mutex sessions_lock; /* sessions list + closing */ /* Client lock for quick WSMs and operations changes */ struct mutex quick_lock; /* Client lock for CWSMs release functions */ struct mutex cwsm_release_lock; /* List of WSMs for a client */ struct list_head cwsms; /* List of GP operation for a client */ struct list_head operations; /* The list entry to attach to "ctx.clients" list */ struct list_head list; /* Virtual Machine ID string of the client */ char vm_id[16]; }; /* Context */ static struct client_ctx { /* Clients list */ struct mutex clients_lock; struct list_head clients; /* Clients waiting for their last cbuf to be released */ struct mutex closing_clients_lock; struct list_head closing_clients; } client_ctx; /* Buffer shared with SWd at client level */ struct cwsm { /* Client this cbuf belongs to */ struct tee_client *client; /* Buffer info */ struct gp_shared_memory memref; /* MMU L2 table */ struct tee_mmu *mmu; /* Buffer SWd addr */ u32 sva; /* Number of references kept to this object */ struct kref kref; /* The list entry for the client to list its WSMs */ struct list_head list; }; /* * Contiguous buffer allocated to TLCs. * These buffers are used as world shared memory (wsm) to share with * secure world. */ struct cbuf { /* Client this cbuf belongs to */ struct tee_client *client; /* List element for client's list of cbuf's */ struct list_head list; /* Number of references kept to this buffer */ struct kref kref; /* virtual Kernel start address */ uintptr_t addr; /* virtual Userspace start address */ uintptr_t uaddr; /* physical start address */ phys_addr_t phys; /* 2^order = number of pages allocated */ unsigned int order; /* Length of memory mapped to user */ u32 len; /* Has been freed via the API */ bool api_freed; }; static inline void cbuf_get(struct cbuf *cbuf) { kref_get(&cbuf->kref); } /* Must only be called by tee_cbuf_put */ static void cbuf_release(struct kref *kref) { struct cbuf *cbuf = container_of(kref, struct cbuf, kref); struct tee_client *client = cbuf->client; /* Unlist from client */ list_del_init(&cbuf->list); /* Release client token */ client_put(client); /* Free */ free_pages(cbuf->addr, cbuf->order); mc_dev_devel( "freed cbuf %p: client %p addr %lx uaddr %lx len %u phys 0x%llx", cbuf, client, cbuf->addr, cbuf->uaddr, cbuf->len, (u64)cbuf->phys); kfree(cbuf); /* Decrement debug counter */ atomic_dec(&g_ctx.c_cbufs); } void tee_cbuf_put(struct cbuf *cbuf) { struct tee_client *client = cbuf->client; mutex_lock(&client->cbufs_lock); kref_put(&cbuf->kref, cbuf_release); mutex_unlock(&client->cbufs_lock); } /* * Map a kernel contiguous buffer to user space */ static int cbuf_map(struct vm_area_struct *vmarea, uintptr_t addr, u32 len, uintptr_t *uaddr) { int ret; if (!uaddr) return -EINVAL; if (!vmarea) return -EINVAL; if (!addr) return -EINVAL; if (len != (u32)(vmarea->vm_end - vmarea->vm_start)) { ret = -EINVAL; mc_dev_err(ret, "cbuf incompatible with vma"); return ret; } vmarea->vm_flags |= VM_IO; ret = remap_pfn_range(vmarea, vmarea->vm_start, page_to_pfn(virt_to_page(addr)), vmarea->vm_end - vmarea->vm_start, vmarea->vm_page_prot); if (ret) { *uaddr = 0; mc_dev_err(ret, "User mapping failed"); return ret; } *uaddr = vmarea->vm_start; return 0; } /* * Returns true if client is a kernel object. */ static inline bool client_is_kernel(struct tee_client *client) { return !client->pid; } static struct cwsm *cwsm_create(struct tee_client *client, struct tee_mmu *mmu, const struct gp_shared_memory *memref, struct gp_return *gp_ret) { struct cwsm *cwsm; u32 sva; int ret; cwsm = kzalloc(sizeof(*cwsm), GFP_KERNEL); if (!cwsm) return ERR_PTR(iwp_set_ret(-ENOMEM, gp_ret)); if (mmu) { cwsm->mmu = mmu; tee_mmu_get(cwsm->mmu); } else { struct mc_ioctl_buffer buf = { .va = (uintptr_t)memref->buffer, .len = memref->size, .flags = memref->flags, }; if (client_is_kernel(client)) { cwsm->mmu = tee_mmu_create(NULL, &buf); } else { struct mm_struct *mm = get_task_mm(current); if (!mm) { ret = -EPERM; mc_dev_err(ret, "can't get mm"); goto err_cwsm; } /* Build MMU table for buffer */ cwsm->mmu = tee_mmu_create(mm, &buf); mmput(mm); } if (IS_ERR(cwsm->mmu)) { ret = iwp_set_ret(PTR_ERR(cwsm->mmu), gp_ret); goto err_cwsm; } } ret = iwp_register_shared_mem(cwsm->mmu, &sva, gp_ret); if (ret) goto err_mmu; /* Get a token on the client */ client_get(client); cwsm->client = client; memcpy(&cwsm->memref, memref, sizeof(cwsm->memref)); cwsm->sva = sva; kref_init(&cwsm->kref); INIT_LIST_HEAD(&cwsm->list); /* Add buffer to list */ mutex_lock(&client->quick_lock); list_add_tail(&cwsm->list, &client->cwsms); mutex_unlock(&client->quick_lock); mc_dev_devel("created cwsm %p: client %p sva %x", cwsm, client, sva); /* Increment debug counter */ atomic_inc(&g_ctx.c_cwsms); return cwsm; err_mmu: tee_mmu_put(cwsm->mmu); err_cwsm: kfree(cwsm); return ERR_PTR(ret); } static inline void cwsm_get(struct cwsm *cwsm) { kref_get(&cwsm->kref); } /* Must only be called by cwsm_put */ static void cwsm_release(struct kref *kref) { struct cwsm *cwsm = container_of(kref, struct cwsm, kref); struct tee_client *client = cwsm->client; struct mcp_buffer_map map; /* Unlist from client */ list_del_init(&cwsm->list); /* Unmap buffer from SWd (errors ignored) */ tee_mmu_buffer(cwsm->mmu, &map); map.secure_va = cwsm->sva; iwp_release_shared_mem(&map); /* Release MMU */ tee_mmu_put(cwsm->mmu); /* Release client token */ client_put(client); /* Free */ mc_dev_devel("freed cwsm %p: client %p", cwsm, client); kfree(cwsm); /* Decrement debug counter */ atomic_dec(&g_ctx.c_cwsms); } static inline void cwsm_put(struct cwsm *cwsm) { struct tee_client *client = cwsm->client; mutex_lock(&client->quick_lock); kref_put(&cwsm->kref, cwsm_release); mutex_unlock(&client->quick_lock); } static inline struct cwsm *cwsm_find(struct tee_client *client, const struct gp_shared_memory *memref) { struct cwsm *cwsm = NULL, *candidate; mc_dev_devel("find shared mem for buf %llx size %llu flags %x", memref->buffer, memref->size, memref->flags); mutex_lock(&client->quick_lock); list_for_each_entry(candidate, &client->cwsms, list) { mc_dev_devel("candidate buf %llx size %llu flags %x", candidate->memref.buffer, candidate->memref.size, candidate->memref.flags); if (candidate->memref.buffer == memref->buffer && candidate->memref.size == memref->size && candidate->memref.flags == memref->flags) { cwsm = candidate; cwsm_get(cwsm); mc_dev_devel("match"); break; } } mutex_unlock(&client->quick_lock); return cwsm; } static inline struct cwsm *cwsm_find_by_sva(struct tee_client *client, u32 sva) { struct cwsm *cwsm = NULL, *candidate; mutex_lock(&client->quick_lock); list_for_each_entry(candidate, &client->cwsms, list) if (candidate->sva == sva) { cwsm = candidate; cwsm_get(cwsm); break; } mutex_unlock(&client->quick_lock); return cwsm; } /* * Returns the secure virtual address from a registered mem */ u32 client_get_cwsm_sva(struct tee_client *client, const struct gp_shared_memory *memref) { struct cwsm *cwsm = cwsm_find(client, memref); if (!cwsm) return 0; mc_dev_devel("found sva %x", cwsm->sva); return cwsm->sva; } void client_get(struct tee_client *client) { kref_get(&client->kref); } void client_put_cwsm_sva(struct tee_client *client, u32 sva) { struct cwsm *cwsm; mutex_lock(&client->cwsm_release_lock); cwsm = cwsm_find_by_sva(client, sva); if (!cwsm) goto end; /* Release reference taken by cwsm_find_by_sva */ cwsm_put(cwsm); cwsm_put(cwsm); end: mutex_unlock(&client->cwsm_release_lock); } /* * Allocate and initialize a client object */ struct tee_client *client_create(bool is_from_kernel, const char *vm_id) { struct tee_client *client; /* Allocate client structure */ client = kzalloc(sizeof(*client), GFP_KERNEL); if (!client) return NULL; /* Increment debug counter */ atomic_inc(&g_ctx.c_clients); /* initialize members */ client->pid = is_from_kernel ? 0 : current->pid; memcpy(client->comm, current->comm, sizeof(client->comm)); kref_init(&client->kref); INIT_LIST_HEAD(&client->cbufs); mutex_init(&client->cbufs_lock); INIT_LIST_HEAD(&client->sessions); INIT_LIST_HEAD(&client->closing_sessions); mutex_init(&client->sessions_lock); INIT_LIST_HEAD(&client->list); mutex_init(&client->quick_lock); mutex_init(&client->cwsm_release_lock); INIT_LIST_HEAD(&client->cwsms); INIT_LIST_HEAD(&client->operations); strlcpy(client->vm_id, vm_id, sizeof(client->vm_id)); /* Add client to list of clients */ mutex_lock(&client_ctx.clients_lock); list_add_tail(&client->list, &client_ctx.clients); mutex_unlock(&client_ctx.clients_lock); mc_dev_devel("created client %p", client); return client; } /* Must only be called by client_put */ static void client_release(struct kref *kref) { struct tee_client *client; client = container_of(kref, struct tee_client, kref); /* Client is closed, remove from closing list */ list_del(&client->list); mc_dev_devel("freed client %p", client); kfree(client); /* Decrement debug counter */ atomic_dec(&g_ctx.c_clients); } int client_put(struct tee_client *client) { int ret; mutex_lock(&client_ctx.closing_clients_lock); ret = kref_put(&client->kref, client_release); mutex_unlock(&client_ctx.closing_clients_lock); return ret; } /* * Set client "closing" state, only if it contains no session. * Once in "closing" state, system "close" can be called. * Return: 0 if this state could be set. */ bool client_has_sessions(struct tee_client *client) { bool ret; /* Check for sessions */ mutex_lock(&client->sessions_lock); ret = !list_empty(&client->sessions); mutex_unlock(&client->sessions_lock); mc_dev_devel("client %p, exit with %d", client, ret); return ret; } static inline void client_put_session(struct tee_client *client, struct tee_session *session) { /* Remove session from client's closing list */ mutex_lock(&client->sessions_lock); list_del(&session->list); mutex_unlock(&client->sessions_lock); /* Release the ref we took on creation */ session_put(session); } /* * At this point, nobody has access to the client anymore, so no new sessions * are being created. */ static void client_close_sessions(struct tee_client *client) { struct tee_session *session; mutex_lock(&client->sessions_lock); while (!list_empty(&client->sessions)) { session = list_first_entry(&client->sessions, struct tee_session, list); /* Move session to closing sessions list */ list_move(&session->list, &client->closing_sessions); /* Call session_close without lock */ mutex_unlock(&client->sessions_lock); if (!session_close(session)) client_put_session(client, session); mutex_lock(&client->sessions_lock); } mutex_unlock(&client->sessions_lock); } /* * At this point, nobody has access to the client anymore, so no new contiguous * buffers are being created. */ static void client_close_kernel_cbufs(struct tee_client *client) { /* Put buffers allocated and not freed via the kernel API */ if (!client_is_kernel(client)) return; /* Look for cbufs that the client has not freed and put them */ while (true) { struct cbuf *cbuf = NULL, *candidate; mutex_lock(&client->cbufs_lock); list_for_each_entry(candidate, &client->cbufs, list) { if (!candidate->api_freed) { candidate->api_freed = true; cbuf = candidate; break; } } mutex_unlock(&client->cbufs_lock); if (!cbuf) break; tee_cbuf_put(cbuf); } } /* Client is closing: make sure all CSMs are gone */ static void client_release_cwsms(struct tee_client *client) { /* Look for cbufs that the client has not freed and put them */ while (!list_empty(&client->cwsms)) { struct cwsm *cwsm; cwsm = list_first_entry(&client->cwsms, struct cwsm, list); cwsm_put(cwsm); } } /* Client is closing: make sure all cancelled operations are gone */ static void client_release_gp_operations(struct tee_client *client) { struct client_gp_operation *op, *nop; mutex_lock(&client->quick_lock); list_for_each_entry_safe(op, nop, &client->operations, list) { /* Only cancelled operations are kzalloc'd */ mc_dev_devel("flush cancelled operation %p for started %llu", op, op->started); if (op->cancelled) kfree(op); } mutex_unlock(&client->quick_lock); } /* * Release a client and the session+cbuf objects it contains. * @param client_t client * @return driver error code */ void client_close(struct tee_client *client) { /* Move client from active clients to closing clients for debug */ mutex_lock(&client_ctx.clients_lock); mutex_lock(&client_ctx.closing_clients_lock); list_move(&client->list, &client_ctx.closing_clients); mutex_unlock(&client_ctx.closing_clients_lock); mutex_unlock(&client_ctx.clients_lock); client_close_kernel_cbufs(client); /* Close all remaining sessions */ client_close_sessions(client); /* Release all cwsms, no need to lock as sessions are closed */ client_release_cwsms(client); client_release_gp_operations(client); client_put(client); mc_dev_devel("client %p closed", client); } /* * Clean all structures shared with the SWd (note: incomplete but unused) */ void client_cleanup(void) { struct tee_client *client; mutex_lock(&client_ctx.clients_lock); list_for_each_entry(client, &client_ctx.clients, list) { mutex_lock(&client->sessions_lock); while (!list_empty(&client->sessions)) { struct tee_session *session; session = list_first_entry(&client->sessions, struct tee_session, list); list_del(&session->list); session_mc_cleanup_session(session); } mutex_unlock(&client->sessions_lock); } mutex_unlock(&client_ctx.clients_lock); } /* * Get the vm id of the client passed in argument * @param client_t client * @return vm id of the client */ const char *client_vm_id(struct tee_client *client) { return client->vm_id; } /* * Open TA for given client. TA binary is provided by the daemon. * @param * @return driver error code */ int client_mc_open_session(struct tee_client *client, const struct mc_uuid_t *uuid, uintptr_t tci_va, size_t tci_len, u32 *session_id) { struct mcp_open_info info = { .type = TEE_MC_UUID, .uuid = uuid, .tci_va = tci_va, .tci_len = tci_len, .user = !client_is_kernel(client), }; int ret; ret = client_mc_open_common(client, &info, session_id); mc_dev_devel("session %x, exit with %d", *session_id, ret); return ret; } /* * Open TA for given client. TA binary is provided by the client. * @param * @return driver error code */ int client_mc_open_trustlet(struct tee_client *client, uintptr_t ta_va, size_t ta_len, uintptr_t tci_va, size_t tci_len, u32 *session_id) { struct mcp_open_info info = { .type = TEE_MC_TA, .va = ta_va, .len = ta_len, .tci_va = tci_va, .tci_len = tci_len, .user = !client_is_kernel(client), }; int ret; ret = client_mc_open_common(client, &info, session_id); mc_dev_devel("session %x, exit with %d", *session_id, ret); return ret; } /* * Opens a TA and add corresponding session object to given client * return: driver error code */ int client_mc_open_common(struct tee_client *client, struct mcp_open_info *info, u32 *session_id) { struct tee_session *session = NULL; int ret = 0; /* * Create session object with temp sid=0 BEFORE session is started, * otherwise if a GP TA is started and NWd session object allocation * fails, we cannot handle the potentially delayed GP closing. * Adding session to list must be done AFTER it is started (once we have * sid), therefore it cannot be done within session_create(). */ session = session_create(client, NULL); if (IS_ERR(session)) return PTR_ERR(session); ret = session_mc_open_session(session, info); if (ret) goto err; mutex_lock(&client->sessions_lock); /* Add session to client */ list_add_tail(&session->list, &client->sessions); /* Set session ID returned by SWd */ *session_id = session->mcp_session.sid; mutex_unlock(&client->sessions_lock); err: /* Close or free session on error */ if (ret == -ENODEV) { /* The session must enter the closing process... */ list_add_tail(&session->list, &client->closing_sessions); if (!session_close(session)) client_put_session(client, session); } else if (ret) { session_put(session); } return ret; } /* * Remove a session object from client and close corresponding TA * Return: true if session was found and closed */ int client_remove_session(struct tee_client *client, u32 session_id) { struct tee_session *session = NULL, *candidate; int ret; /* Move session from main list to closing list */ mutex_lock(&client->sessions_lock); list_for_each_entry(candidate, &client->sessions, list) { if (candidate->mcp_session.sid == session_id) { session = candidate; list_move(&session->list, &client->closing_sessions); break; } } mutex_unlock(&client->sessions_lock); if (!session) return -ENXIO; /* Close session */ ret = session_close(session); if (!ret) client_put_session(client, session); return ret; } /* * Find a session object and increment its reference counter. * Object cannot be freed until its counter reaches 0. * return: pointer to the object, NULL if not found. */ static struct tee_session *client_get_session(struct tee_client *client, u32 session_id) { struct tee_session *session = NULL, *candidate; mutex_lock(&client->sessions_lock); list_for_each_entry(candidate, &client->sessions, list) { if (candidate->mcp_session.sid == session_id) { session = candidate; session_get(session); break; } } mutex_unlock(&client->sessions_lock); if (!session) mc_dev_err(-ENXIO, "session %x not found", session_id); return session; } /* * Send a notification to TA * @return driver error code */ int client_notify_session(struct tee_client *client, u32 session_id) { struct tee_session *session; int ret; /* Find/get session */ session = client_get_session(client, session_id); if (!session) return -ENXIO; /* Send command to SWd */ ret = session_mc_notify(session); /* Put session */ session_put(session); mc_dev_devel("session %x, exit with %d", session_id, ret); return ret; } /* * Wait for a notification from TA * @return driver error code */ int client_waitnotif_session(struct tee_client *client, u32 session_id, s32 timeout) { struct tee_session *session; int ret; /* Find/get session */ session = client_get_session(client, session_id); if (!session) return -ENXIO; ret = session_mc_wait(session, timeout); /* Put session */ session_put(session); mc_dev_devel("session %x, exit with %d", session_id, ret); return ret; } /* * Read session exit/termination code */ int client_get_session_exitcode(struct tee_client *client, u32 session_id, s32 *err) { struct tee_session *session; int ret; /* Find/get session */ session = client_get_session(client, session_id); if (!session) return -ENXIO; /* Retrieve error */ ret = session_mc_get_err(session, err); /* Put session */ session_put(session); mc_dev_devel("session %x, exit code %d", session_id, *err); return ret; } /* Share a buffer with given TA in SWd */ int client_mc_map(struct tee_client *client, u32 session_id, struct tee_mmu *mmu, struct mc_ioctl_buffer *buf) { struct tee_session *session; int ret; /* Find/get session */ session = client_get_session(client, session_id); if (!session) return -ENXIO; /* Add buffer to the session */ ret = session_mc_map(session, mmu, buf); /* Put session */ session_put(session); mc_dev_devel("session %x, exit with %d", session_id, ret); return ret; } /* Stop sharing a buffer with SWd */ int client_mc_unmap(struct tee_client *client, u32 session_id, const struct mc_ioctl_buffer *buf) { struct tee_session *session; int ret; /* Find/get session */ session = client_get_session(client, session_id); if (!session) return -ENXIO; /* Remove buffer from session */ ret = session_mc_unmap(session, buf); /* Put session */ session_put(session); mc_dev_devel("session %x, exit with %d", session_id, ret); return ret; } int client_gp_initialize_context(struct tee_client *client, struct gp_return *gp_ret) { return iwp_set_ret(0, gp_ret); } int client_gp_register_shared_mem(struct tee_client *client, struct tee_mmu *mmu, u32 *sva, const struct gp_shared_memory *memref, struct gp_return *gp_ret) { struct cwsm *cwsm = NULL; if (memref->size > BUFFER_LENGTH_MAX) { mc_dev_err(-EINVAL, "buffer size %llu too big", memref->size); return -EINVAL; } if (!mmu) /* cwsm_find automatically takes a reference */ cwsm = cwsm_find(client, memref); if (!cwsm) cwsm = cwsm_create(client, mmu, memref, gp_ret); /* gp_ret set by callee */ if (IS_ERR(cwsm)) return PTR_ERR(cwsm); if (sva) *sva = cwsm->sva; return iwp_set_ret(0, gp_ret); } int client_gp_release_shared_mem(struct tee_client *client, const struct gp_shared_memory *memref) { struct cwsm *cwsm; int ret = 0; mutex_lock(&client->cwsm_release_lock); cwsm = cwsm_find(client, memref); if (!cwsm) { ret = -ENOENT; goto end; } /* Release reference taken by cwsm_find */ cwsm_put(cwsm); cwsm_put(cwsm); end: mutex_unlock(&client->cwsm_release_lock); return ret; } /* * Opens a TA and add corresponding session object to given client * return: driver error code */ int client_gp_open_session(struct tee_client *client, const struct mc_uuid_t *uuid, struct gp_operation *operation, const struct mc_identity *identity, struct gp_return *gp_ret, u32 *session_id) { struct tee_session *session = NULL; int ret = 0; /* * Create session object with temp sid=0 BEFORE session is started, * otherwise if a GP TA is started and NWd session object allocation * fails, we cannot handle the potentially delayed GP closing. * Adding session to list must be done AFTER it is started (once we have * sid), therefore it cannot be done within session_create(). */ session = session_create(client, identity); if (IS_ERR(session)) return iwp_set_ret(PTR_ERR(session), gp_ret); /* Open session */ ret = session_gp_open_session(session, uuid, operation, gp_ret); if (ret) goto end; mutex_lock(&client->sessions_lock); /* Add session to client */ list_add_tail(&session->list, &client->sessions); mutex_unlock(&client->sessions_lock); /* Set sid returned by SWd */ *session_id = session->iwp_session.sid; end: if (ret) session_put(session); mc_dev_devel("gp session %x, exit with %d", *session_id, ret); return ret; } int client_gp_open_session_domu(struct tee_client *client, const struct mc_uuid_t *uuid, u64 started, struct interworld_session *iws, struct tee_mmu **mmus, struct gp_return *gp_ret) { struct tee_session *session = NULL; int ret = 0; /* Don't pass NULL for identity as it would make a MC session */ session = session_create(client, ERR_PTR(-ENOENT)); if (IS_ERR(session)) return iwp_set_ret(PTR_ERR(session), gp_ret); /* Open session */ ret = session_gp_open_session_domu(session, uuid, started, iws, mmus, gp_ret); if (ret) goto end; mutex_lock(&client->sessions_lock); /* Add session to client */ list_add_tail(&session->list, &client->sessions); mutex_unlock(&client->sessions_lock); end: if (ret) session_put(session); mc_dev_devel("gp session %x, exit with %d", session->iwp_session.sid, ret); return ret; } int client_gp_close_session(struct tee_client *client, u32 session_id) { struct tee_session *session = NULL, *candidate; int ret = 0; /* Move session from main list to closing list */ mutex_lock(&client->sessions_lock); list_for_each_entry(candidate, &client->sessions, list) { if (candidate->iwp_session.sid == session_id) { session = candidate; list_move(&session->list, &client->closing_sessions); break; } } mutex_unlock(&client->sessions_lock); if (!session) return -ENXIO; ret = session_close(session); if (!ret) client_put_session(client, session); return ret; } /* * Send a command to the TA * @param * @return driver error code */ int client_gp_invoke_command(struct tee_client *client, u32 session_id, u32 command_id, struct gp_operation *operation, struct gp_return *gp_ret) { struct tee_session *session; int ret = 0; session = client_get_session(client, session_id); if (!session) return iwp_set_ret(-ENXIO, gp_ret); ret = session_gp_invoke_command(session, command_id, operation, gp_ret); /* Put session */ session_put(session); return ret; } int client_gp_invoke_command_domu(struct tee_client *client, u32 session_id, u64 started, struct interworld_session *iws, struct tee_mmu **mmus, struct gp_return *gp_ret) { struct tee_session *session; int ret = 0; session = client_get_session(client, session_id); if (!session) return iwp_set_ret(-ENXIO, gp_ret); ret = session_gp_invoke_command_domu(session, started, iws, mmus, gp_ret); /* Put session */ session_put(session); return ret; } void client_gp_request_cancellation(struct tee_client *client, u64 started) { struct client_gp_operation *op; u64 slot; bool found = false; /* Look for operation */ mutex_lock(&client->quick_lock); list_for_each_entry(op, &client->operations, list) if (op->started == started) { slot = op->slot; found = true; mc_dev_devel( "found no operation cancel for started %llu", started); break; } /* Operation not found: assume it is coming */ if (!found) { op = kzalloc(sizeof(*op), GFP_KERNEL); if (op) { op->started = started; op->cancelled = true; list_add_tail(&op->list, &client->operations); mc_dev_devel( "add cancelled operation %p for started %llu", op, op->started); } } mutex_unlock(&client->quick_lock); if (found) session_gp_request_cancellation(slot); } /* * This callback is called on remap */ static void cbuf_vm_open(struct vm_area_struct *vmarea) { struct cbuf *cbuf = vmarea->vm_private_data; cbuf_get(cbuf); } /* * This callback is called on unmap */ static void cbuf_vm_close(struct vm_area_struct *vmarea) { struct cbuf *cbuf = vmarea->vm_private_data; tee_cbuf_put(cbuf); } static const struct vm_operations_struct cbuf_vm_ops = { .open = cbuf_vm_open, .close = cbuf_vm_close, }; /* * Create a cbuf object and add it to client */ int client_cbuf_create(struct tee_client *client, u32 len, uintptr_t *addr, struct vm_area_struct *vmarea) { struct cbuf *cbuf = NULL; unsigned int order; int ret = 0; if (!client) return -EINVAL; if (!len) { mc_dev_err(-EINVAL, "buffer size 0 not supported"); return -EINVAL; } if (len > BUFFER_LENGTH_MAX) { mc_dev_err(-EINVAL, "buffer size %u too big", len); return -EINVAL; } order = get_order(len); if (order > MAX_ORDER) { ret = -ENOMEM; mc_dev_err(ret, "Buffer size too large"); return ret; } /* Allocate buffer descriptor structure */ cbuf = kzalloc(sizeof(*cbuf), GFP_KERNEL); if (!cbuf) return -ENOMEM; /* Increment debug counter */ atomic_inc(&g_ctx.c_cbufs); /* Allocate buffer */ cbuf->addr = __get_free_pages(GFP_USER | __GFP_ZERO, order); if (!cbuf->addr) { kfree(cbuf); /* Decrement debug counter */ atomic_dec(&g_ctx.c_cbufs); return -ENOMEM; } /* Map to user space if applicable */ if (!client_is_kernel(client)) { ret = cbuf_map(vmarea, cbuf->addr, len, &cbuf->uaddr); if (ret) { free_pages(cbuf->addr, order); kfree(cbuf); /* Decrement debug counter */ atomic_dec(&g_ctx.c_cbufs); return ret; } } /* Init descriptor members */ cbuf->client = client; cbuf->phys = virt_to_phys((void *)cbuf->addr); cbuf->len = len; cbuf->order = order; kref_init(&cbuf->kref); INIT_LIST_HEAD(&cbuf->list); /* Keep cbuf in VMA private data for refcounting (user-space clients) */ if (vmarea) { vmarea->vm_private_data = cbuf; vmarea->vm_ops = &cbuf_vm_ops; } /* Fill return parameter for k-api */ if (addr) *addr = cbuf->addr; /* Get a token on the client */ client_get(client); /* Add buffer to list */ mutex_lock(&client->cbufs_lock); list_add_tail(&cbuf->list, &client->cbufs); mutex_unlock(&client->cbufs_lock); mc_dev_devel( "created cbuf %p: client %p addr %lx uaddr %lx len %u phys 0x%llx", cbuf, client, cbuf->addr, cbuf->uaddr, cbuf->len, (u64)cbuf->phys); return ret; } /* * Find a contiguous buffer (cbuf) in the cbuf list of given client that * contains given address and take a reference on it. * Return pointer to the object, or NULL if not found. */ static struct cbuf *cbuf_get_by_addr(struct tee_client *client, uintptr_t addr) { struct cbuf *cbuf = NULL, *candidate; bool is_kernel = client_is_kernel(client); mutex_lock(&client->cbufs_lock); list_for_each_entry(candidate, &client->cbufs, list) { /* Compare to kernel VA or user VA depending on client type */ uintptr_t start = is_kernel ? candidate->addr : candidate->uaddr; uintptr_t end = start + candidate->len; /* Check that (user) cbuf has not been unmapped */ if (!start) break; if (addr >= start && addr < end) { cbuf = candidate; break; } } if (cbuf) cbuf_get(cbuf); mutex_unlock(&client->cbufs_lock); return cbuf; } /* * Remove a cbuf object from client, and mark it for freeing. * Freeing will happen once all current references are released. * * Note: this function could be subject to the same race condition as * client_gp_release_shared_mem() and client_put_cwsm_sva(), but it is trusted * as it can only be called by kernel drivers. So no lock around * cbuf_get_by_addr() and the two tee_cbuf_put(). */ int client_cbuf_free(struct tee_client *client, uintptr_t addr) { struct cbuf *cbuf = cbuf_get_by_addr(client, addr); if (!cbuf) { mc_dev_err(-EINVAL, "cbuf %lu not found", addr); return -EINVAL; } /* Release reference taken by cbuf_get_by_addr */ tee_cbuf_put(cbuf); mutex_lock(&client->cbufs_lock); cbuf->api_freed = true; mutex_unlock(&client->cbufs_lock); tee_cbuf_put(cbuf); return 0; } bool client_gp_operation_add(struct tee_client *client, struct client_gp_operation *operation) { struct client_gp_operation *op; bool found = false; mutex_lock(&client->quick_lock); list_for_each_entry(op, &client->operations, list) if (op->started == operation->started && op->cancelled) { found = true; break; } if (found) { list_del(&op->list); mc_dev_devel("found cancelled operation %p for started %llu", op, op->started); kfree(op); } else { list_add_tail(&operation->list, &client->operations); mc_dev_devel("add operation for started %llu", operation->started); } mutex_unlock(&client->quick_lock); return !found; } void client_gp_operation_remove(struct tee_client *client, struct client_gp_operation *operation) { mutex_lock(&client->quick_lock); list_del(&operation->list); mutex_unlock(&client->quick_lock); } struct tee_mmu *client_mmu_create(struct tee_client *client, const struct mc_ioctl_buffer *buf_in, struct cbuf **cbuf_p) { /* Check if buffer is contained in a cbuf */ struct mc_ioctl_buffer buf = *buf_in; struct cbuf *cbuf = cbuf_get_by_addr(client, buf.va); struct mm_struct *mm = NULL; struct tee_mmu *mmu; *cbuf_p = cbuf; if (cbuf) { uintptr_t offset; if (client_is_kernel(client)) { offset = buf.va - cbuf->addr; } else { offset = buf.va - cbuf->uaddr; /* Update va to point to kernel address */ buf.va = cbuf->addr + offset; } if ((offset + buf.len) > cbuf->len) { mc_dev_err(-EINVAL, "crosses cbuf boundary"); tee_cbuf_put(cbuf); return ERR_PTR(-EINVAL); } } else if (!client_is_kernel(client)) { mm = get_task_mm(current); if (!mm) { mc_dev_err(-EPERM, "can't get mm"); return ERR_PTR(-EPERM); } } /* Build MMU table for buffer */ mmu = tee_mmu_create(mm, &buf); if (mm) mmput(mm); if (IS_ERR_OR_NULL(mmu) && cbuf) tee_cbuf_put(cbuf); return mmu; } void client_init(void) { INIT_LIST_HEAD(&client_ctx.clients); mutex_init(&client_ctx.clients_lock); INIT_LIST_HEAD(&client_ctx.closing_clients); mutex_init(&client_ctx.closing_clients_lock); } static inline int cbuf_debug_structs(struct kasnprintf_buf *buf, struct cbuf *cbuf) { return kasnprintf(buf, "\tcbuf %pK [%d]: addr %pK uaddr %pK len %u\n", cbuf, kref_read(&cbuf->kref), (void *)cbuf->addr, (void *)cbuf->uaddr, cbuf->len); } static inline int cwsm_debug_structs(struct kasnprintf_buf *buf, struct cwsm *cwsm) { return kasnprintf(buf, "\tcwsm %pK [%d]: buf %pK len %llu flags 0x%x\n", cwsm, kref_read(&cwsm->kref), (void *)(uintptr_t)cwsm->memref.buffer, cwsm->memref.size, cwsm->memref.flags); } static int client_debug_structs(struct kasnprintf_buf *buf, struct tee_client *client, bool is_closing) { struct cbuf *cbuf; struct cwsm *cwsm; struct tee_session *session; int ret; if (client->pid) ret = kasnprintf(buf, "client %pK [%d]: %s (%d)%s\n", client, kref_read(&client->kref), client->comm, client->pid, is_closing ? " " : ""); else ret = kasnprintf(buf, "client %pK [%d]: [kernel]%s\n", client, kref_read(&client->kref), is_closing ? " " : ""); if (ret < 0) return ret; /* Buffers */ mutex_lock(&client->cbufs_lock); if (list_empty(&client->cbufs)) goto done_cbufs; list_for_each_entry(cbuf, &client->cbufs, list) { ret = cbuf_debug_structs(buf, cbuf); if (ret < 0) goto done_cbufs; } done_cbufs: mutex_unlock(&client->cbufs_lock); if (ret < 0) return ret; /* WMSs */ mutex_lock(&client->quick_lock); if (list_empty(&client->cwsms)) goto done_cwsms; list_for_each_entry(cwsm, &client->cwsms, list) { ret = cwsm_debug_structs(buf, cwsm); if (ret < 0) goto done_cwsms; } done_cwsms: mutex_unlock(&client->quick_lock); if (ret < 0) return ret; /* Sessions */ mutex_lock(&client->sessions_lock); list_for_each_entry(session, &client->sessions, list) { ret = session_debug_structs(buf, session, false); if (ret < 0) goto done_sessions; } list_for_each_entry(session, &client->closing_sessions, list) { ret = session_debug_structs(buf, session, true); if (ret < 0) goto done_sessions; } done_sessions: mutex_unlock(&client->sessions_lock); if (ret < 0) return ret; return 0; } int clients_debug_structs(struct kasnprintf_buf *buf) { struct tee_client *client; ssize_t ret = 0; mutex_lock(&client_ctx.clients_lock); list_for_each_entry(client, &client_ctx.clients, list) { ret = client_debug_structs(buf, client, false); if (ret < 0) break; } mutex_unlock(&client_ctx.clients_lock); if (ret < 0) return ret; mutex_lock(&client_ctx.closing_clients_lock); list_for_each_entry(client, &client_ctx.closing_clients, list) { ret = client_debug_structs(buf, client, true); if (ret < 0) break; } mutex_unlock(&client_ctx.closing_clients_lock); return ret; }