From 4f5d895ef82a2f9969cd50a40708935e181a0e5d Mon Sep 17 00:00:00 2001 From: Sumukh Hallymysore Ravindra Date: Mon, 7 Oct 2019 20:24:58 -0700 Subject: [PATCH] msm: synx: Porting synx driver to 5.x kernel Synx driver manages synchronization and dependency handling across heterogeneous cores. Modified kernel APIs with support for kernel clients. Changes to synx handle creation, synx object handling and cleanup. Support for building synx driver as loadable module. Change-Id: I2d70d753872467895ef4cf5a85326b7a92310986 Signed-off-by: Sumukh Hallymysore Ravindra --- drivers/media/platform/Kconfig | 2 + drivers/media/platform/Makefile | 2 + drivers/media/platform/msm/Kconfig | 3 + drivers/media/platform/msm/Makefile | 3 + drivers/media/platform/msm/synx/Kconfig | 11 + drivers/media/platform/msm/synx/Makefile | 5 + drivers/media/platform/msm/synx/synx.c | 1771 +++++++++++++++++ drivers/media/platform/msm/synx/synx_api.h | 344 ++++ .../media/platform/msm/synx/synx_debugfs.c | 211 ++ .../media/platform/msm/synx/synx_debugfs.h | 29 + .../media/platform/msm/synx/synx_private.h | 303 +++ drivers/media/platform/msm/synx/synx_util.c | 992 +++++++++ drivers/media/platform/msm/synx/synx_util.h | 365 ++++ 13 files changed, 4041 insertions(+) create mode 100644 drivers/media/platform/msm/Kconfig create mode 100644 drivers/media/platform/msm/Makefile create mode 100644 drivers/media/platform/msm/synx/Kconfig create mode 100644 drivers/media/platform/msm/synx/Makefile create mode 100644 drivers/media/platform/msm/synx/synx.c create mode 100644 drivers/media/platform/msm/synx/synx_api.h create mode 100644 drivers/media/platform/msm/synx/synx_debugfs.c create mode 100644 drivers/media/platform/msm/synx/synx_debugfs.h create mode 100644 drivers/media/platform/msm/synx/synx_private.h create mode 100644 drivers/media/platform/msm/synx/synx_util.c create mode 100644 drivers/media/platform/msm/synx/synx_util.h diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index f1f61419fd29..ca3cb4fb3900 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -686,3 +686,5 @@ config VIDEO_RCAR_DRIF will be called rcar_drif. endif # SDR_PLATFORM_DRIVERS + +source "drivers/media/platform/msm/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 6ee7eb0d36f4..5204445615a3 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -101,3 +101,5 @@ obj-y += meson/ obj-y += cros-ec-cec/ obj-y += sunxi/ + +obj-y += msm/ diff --git a/drivers/media/platform/msm/Kconfig b/drivers/media/platform/msm/Kconfig new file mode 100644 index 000000000000..423d7f1d7e59 --- /dev/null +++ b/drivers/media/platform/msm/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/media/platform/msm/synx/Kconfig" diff --git a/drivers/media/platform/msm/Makefile b/drivers/media/platform/msm/Makefile new file mode 100644 index 000000000000..b0ef6078267e --- /dev/null +++ b/drivers/media/platform/msm/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MSM_GLOBAL_SYNX) += synx/ diff --git a/drivers/media/platform/msm/synx/Kconfig b/drivers/media/platform/msm/synx/Kconfig new file mode 100644 index 000000000000..c6cb58fa2ac0 --- /dev/null +++ b/drivers/media/platform/msm/synx/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig MSM_GLOBAL_SYNX + tristate "Qualcomm Technologies, Inc. Global Synchronization Framework" + depends on ARCH_QCOM + help + Say M here to enable global synchronization module for + Qualcomm Technologies, Inc. chipsets. + Enabling this adds support for global synchronization across + heterogeneous cores. + diff --git a/drivers/media/platform/msm/synx/Makefile b/drivers/media/platform/msm/synx/Makefile new file mode 100644 index 000000000000..0435b083a21f --- /dev/null +++ b/drivers/media/platform/msm/synx/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MSM_GLOBAL_SYNX) += synx-driver.o + +synx-driver-y := synx.o synx_util.o synx_debugfs.o diff --git a/drivers/media/platform/msm/synx/synx.c b/drivers/media/platform/msm/synx/synx.c new file mode 100644 index 000000000000..19e49c91de75 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx.c @@ -0,0 +1,1771 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ +#define pr_fmt(fmt) "synx: " fmt + +#include +#include +#include +#include +#include + +#include "synx_api.h" +#include "synx_util.h" +#include "synx_debugfs.h" + +struct synx_device *synx_dev; + +void synx_external_callback(s32 sync_obj, int status, void *data) +{ + struct synx_coredata *synx_obj; + struct synx_client *client = NULL; + struct synx_external_data *bind_data = data; + + if (!bind_data) { + pr_err("invalid payload from sync external obj %d\n", + sync_obj); + return; + } + + client = synx_get_client(bind_data->session_id); + if (!client) + goto free; + + synx_obj = synx_util_acquire_object(client, bind_data->h_synx); + if (!synx_obj) { + pr_err("[sess: %u] invalid callback from external obj %d handle %d\n", + client->id, sync_obj, bind_data->h_synx); + goto fail; + } + + pr_debug("[sess: %u] external callback from %d on handle %d\n", + client->id, sync_obj, bind_data->h_synx); + if (synx_signal_core(synx_obj, status, true, sync_obj)) + pr_err("[sess: %u] signal callback failed for handle %d\n", + client->id, bind_data->h_synx); + + synx_util_release_object(client, bind_data->h_synx); +fail: + synx_put_client(client); +free: + kfree(bind_data); +} +EXPORT_SYMBOL(synx_external_callback); + +bool synx_fence_enable_signaling(struct dma_fence *fence) +{ + return true; +} + +const char *synx_fence_driver_name(struct dma_fence *fence) +{ + return "Global Synx driver"; +} + +void synx_fence_release(struct dma_fence *fence) +{ + struct synx_coredata *synx_obj = + container_of(fence, struct synx_coredata, fence); + + synx_util_object_destroy(synx_obj); +} +EXPORT_SYMBOL(synx_fence_release); + +static struct dma_fence_ops synx_fence_ops = { + .wait = dma_fence_default_wait, + .enable_signaling = synx_fence_enable_signaling, + .get_driver_name = synx_fence_driver_name, + .get_timeline_name = synx_fence_driver_name, + .release = synx_fence_release, +}; + +int synx_create(struct synx_session session_id, + struct synx_create_params *params) +{ + int rc; + long h_synx; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter create from pid %d\n", + session_id.client_id, current->pid); + + if (!params || !params->h_synx) { + pr_err("[sess: %u] invalid create arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (!synx_obj) { + rc = -ENOMEM; + goto fail; + } + + rc = synx_util_init_coredata(synx_obj, params, &synx_fence_ops); + if (rc) { + pr_err("[sess: %u] error initializing synx obj\n", + client->id); + goto clean_up; + } + + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc < 0) { + pr_err("[sess: %u] unable to init new handle\n", + client->id); + goto clean_up; + } + + rc = synx_util_activate(synx_obj); + if (rc) { + pr_err("[sess: %u] unable to activate handle %ld\n", + client->id, h_synx); + goto clear_bit; + return -EINVAL; + } + + *params->h_synx = h_synx; + pr_debug("[sess: %u] new synx obj with handle %ld\n", + client->id, h_synx); + synx_put_client(client); + return 0; + +clear_bit: + clear_bit(h_synx, client->bitmap); +clean_up: + kfree(synx_obj); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_create); + +int synx_signal_core(struct synx_coredata *synx_obj, + u32 status, + bool cb_signal, + s32 ext_sync_id) +{ + int rc, ret; + u32 i = 0; + u32 idx = 0; + s32 sync_id; + u32 type; + struct dma_fence *fence; + struct synx_external_data *data = NULL; + struct synx_bind_desc bind_descs[SYNX_MAX_NUM_BINDINGS]; + struct bind_operations *bind_ops = NULL; + + if (!synx_obj) + return -EINVAL; + + if (status < SYNX_STATE_SIGNALED_SUCCESS) { + pr_err("signaling with undefined status = %d\n", status); + return -EINVAL; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("signaling a composite synx object\n"); + return -EINVAL; + } + + spin_lock_bh(&synx_obj->lock); + + if (synx_util_get_object_status_locked(synx_obj) != SYNX_STATE_ACTIVE) { + spin_unlock_bh(&synx_obj->lock); + return -EALREADY; + } + + fence = synx_util_get_fence(synx_obj); + /* set fence error to model {signal w/ error} */ + if (status != SYNX_STATE_SIGNALED_SUCCESS) + dma_fence_set_error(fence, -status); + + rc = dma_fence_signal_locked(fence); + if (rc) { + pr_err("signaling object failed with err: %d\n", rc); + if (status == SYNX_STATE_SIGNALED_SUCCESS) { + status = SYNX_STATE_SIGNALED_ERROR; + dma_fence_set_error(fence, -status); + } + } + + synx_util_callback_dispatch(synx_obj, status); + + /* + * signal the external bound sync obj/s even if fence signal fails, + * w/ error signal state (set above) to prevent deadlock + */ + if (synx_obj->num_bound_synxs > 0) { + memset(bind_descs, 0, + sizeof(struct synx_bind_desc) * SYNX_MAX_NUM_BINDINGS); + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + /* signal invoked by external sync obj */ + if (cb_signal && + (ext_sync_id == + synx_obj->bound_synxs[i].external_desc.id[0])) { + pr_debug("skipping signaling inbound sync: %d\n", + ext_sync_id); + memset(&synx_obj->bound_synxs[i], 0, + sizeof(struct synx_bind_desc)); + continue; + } + memcpy(&bind_descs[idx++], + &synx_obj->bound_synxs[i], + sizeof(struct synx_bind_desc)); + /* clear the memory, its been backed up above */ + memset(&synx_obj->bound_synxs[i], 0, + sizeof(struct synx_bind_desc)); + } + synx_obj->num_bound_synxs = 0; + } + spin_unlock_bh(&synx_obj->lock); + + for (i = 0; i < idx; i++) { + sync_id = bind_descs[i].external_desc.id[0]; + data = bind_descs[i].external_data; + type = bind_descs[i].external_desc.type; + bind_ops = synx_util_get_bind_ops(type); + if (!bind_ops) { + pr_err("invalid bind ops for type: %u\n", type); + kfree(data); + continue; + } + /* + * we are already signaled, so don't want to + * recursively be signaled + */ + ret = bind_ops->deregister_callback( + synx_external_callback, data, sync_id); + if (ret < 0) + pr_err("deregistration fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + pr_debug("signal external sync: %d, type: %u, status: %u\n", + sync_id, type, status); + /* optional function to enable external signaling */ + if (bind_ops->enable_signaling) { + ret = bind_ops->enable_signaling(sync_id); + if (ret < 0) { + pr_err("enabling fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + continue; + } + } + ret = bind_ops->signal(sync_id, status); + if (ret < 0) + pr_err("signaling fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + /* + * release the memory allocated for external data. + * It is safe to release this memory as external cb + * has been already deregistered before this. + */ + kfree(data); + } + + return rc; +} + +int synx_signal(struct synx_session session_id, s32 h_synx, u32 status) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter signal from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + rc = synx_signal_core(synx_obj, status, false, 0); + if (rc) + pr_err("[sess: %u] signaling failed for handle %d with err: %d\n", + client->id, h_synx, rc); + synx_util_release_object(client, h_synx); + +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit signal with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_signal); + +static int synx_match_payload(struct synx_client_cb *cb, + struct synx_kernel_payload *payload, + bool is_kernel_cb) +{ + int rc = 0; + + if (!cb || !payload) + return -EINVAL; + + if (is_kernel_cb) { + struct synx_kernel_payload *cb_payload = &cb->kernel_cb; + + if ((cb_payload->cb_func == payload->cb_func) && + (cb_payload->data == payload->data)) { + if (payload->cancel_cb_func) { + cb_payload->cb_func = + payload->cancel_cb_func; + rc = 1; + } else { + rc = 2; + pr_debug("kernel cb deregistration success\n"); + } + } + } else { + struct synx_user_payload *cb_payload = &cb->user_cb; + struct synx_user_payload *user_payload = payload->data; + + if ((cb_payload->data[0] == user_payload->data[0]) && + (cb_payload->data[1] == user_payload->data[1])) { + if (user_payload->data[2]) { + memcpy(cb_payload->data, + user_payload->data, + SYNX_PAYLOAD_WORDS * sizeof(__u64)); + rc = 1; + } else { + rc = 2; + pr_debug("user cb deregistration success\n"); + } + } + } + + return rc; +} + +void synx_default_user_callback(s32 h_synx, + int status, void *data) +{ + struct synx_user_payload *payload = data; + struct synx_client *client = payload->client; + struct synx_client_cb *cb = + container_of(payload, struct synx_client_cb, user_cb); + + if (client) { + payload->status = status; + pr_debug("user cb queued for handle %d\n", h_synx); + mutex_lock(&client->event_q_lock); + list_add_tail(&cb->node, &client->event_q); + mutex_unlock(&client->event_q_lock); + wake_up_all(&client->event_wq); + } else { + pr_err("%s: invalid client session\n", __func__); + } +} + +int synx_register_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata) +{ + int rc = 0; + u32 idx; + u32 status; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_cb_data *synx_cb; + struct synx_kernel_payload payload; + + pr_debug("[sess: %u] Enter register cb from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("cannot register cb with composite synx object\n"); + goto clear; + } + + synx_cb = kzalloc(sizeof(*synx_cb), GFP_ATOMIC); + if (!synx_cb) { + rc = -ENOMEM; + goto clear; + } + + if (cb_func == synx_default_user_callback) { + /* userspace callback index allocated already */ + idx = *((u32 *)userdata); + if (idx < SYNX_MAX_OBJS) { + /* data used in default callback func */ + userdata = &client->cb_table[idx].user_cb; + } else { + pr_err("%s: [sess: %u] invalid cb index\n", + __func__, client->id); + kfree(synx_cb); + rc = -EINVAL; + goto clear; + } + } else { + /* obtain a free index from client cb table */ + rc = synx_util_alloc_cb_entry(client, &idx); + if (rc) { + pr_err("[sess :%u] error allocating cb entry\n", + client->id); + kfree(synx_cb); + goto clear; + } + } + + payload.h_synx = h_synx; + payload.cb_func = cb_func; + payload.data = userdata; + /* update the cb entry with kernel cb data */ + rc = synx_util_update_cb_entry(client, &payload, idx); + if (rc) { + pr_err("[sess :%u] error allocating cb entry\n", + client->id); + kfree(synx_cb); + clear_bit(idx, client->cb_bitmap); + goto clear; + } + + spin_lock_bh(&synx_obj->lock); + status = synx_util_get_object_status_locked(synx_obj); + synx_cb->session_id = session_id; + synx_cb->idx = idx; + INIT_WORK(&synx_cb->cb_dispatch, synx_util_cb_dispatch); + + /* add callback if object still ACTIVE, dispatch if SIGNALED */ + if (status == SYNX_STATE_ACTIVE) { + pr_debug("[sess: %u] callback added\n", client->id); + list_add_tail(&synx_cb->node, &synx_obj->reg_cbs_list); + } else { + synx_cb->status = status; + pr_debug("[sess: %u] callback queued\n", client->id); + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + } + spin_unlock_bh(&synx_obj->lock); + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit register cb with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_register_callback); + +int synx_deregister_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata, + synx_callback cancel_cb_func) +{ + int rc = 0, ret = 0; + u32 status; + bool is_kernel_cb = true; + bool match_found = false; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_kernel_payload payload; + struct synx_cb_data *synx_cb, *synx_cb_temp; + struct synx_client_cb *cb_payload; + + pr_debug("[sess: %u] Enter deregister cb from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("cannot deregister cb with composite synx object\n"); + goto clear; + } + + if (cb_func == synx_default_user_callback) + is_kernel_cb = false; + + payload.h_synx = h_synx; + payload.cb_func = cb_func; + payload.data = userdata; + payload.cancel_cb_func = cancel_cb_func; + + spin_lock_bh(&synx_obj->lock); + status = synx_util_get_object_status_locked(synx_obj); + if (status != SYNX_STATE_ACTIVE) { + pr_err("handle %d already signaled. cannot deregister cb/s\n", + h_synx); + rc = -EINVAL; + spin_unlock_bh(&synx_obj->lock); + goto clear; + } + + status = SYNX_CALLBACK_RESULT_CANCELED; + /* remove all cb payloads mayching the deregister call */ + list_for_each_entry_safe(synx_cb, synx_cb_temp, + &synx_obj->reg_cbs_list, node) { + if (synx_cb->session_id.client_id != client->id) { + continue; + } else if (synx_cb->idx >= SYNX_MAX_OBJS) { + /* + * this should not happen. Even if it does, + * the allocated memory will be cleaned up + * when object is destroyed, preventing any + * memory leaks. + */ + pr_err("[sess: %u] invalid callback data\n", + session_id.client_id); + continue; + } + + cb_payload = &client->cb_table[synx_cb->idx]; + ret = synx_match_payload(cb_payload, &payload, is_kernel_cb); + switch (ret) { + case 1: + /* queue the cancel cb work */ + list_del_init(&synx_cb->node); + synx_cb->status = status; + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + match_found = true; + break; + case 2: + /* no cancellation cb */ + list_del_init(&synx_cb->node); + kfree(synx_cb); + match_found = true; + break; + default: + break; + } + } + spin_unlock_bh(&synx_obj->lock); + + if (!match_found) + rc = -EINVAL; + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + return rc; + + pr_debug("[sess: %u] exit deregister cb with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_deregister_callback); + +int synx_merge(struct synx_session session_id, + s32 *h_synxs, + u32 num_objs, + s32 *h_synx_merged) +{ + int rc; + long h_synx; + u32 count = 0; + struct synx_client *client; + struct dma_fence **fences = NULL; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter merge from pid %d\n", + session_id.client_id, current->pid); + + if (!h_synxs || !h_synx_merged) { + pr_err("%s: [sess: %u] invalid arguments\n", + __func__, session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + rc = synx_util_validate_merge(client, h_synxs, + num_objs, &fences, &count); + if (rc < 0) { + pr_err("[sess: %u] merge validation failed\n", + client->id); + rc = -EINVAL; + goto fail; + } + + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (!synx_obj) { + rc = -ENOMEM; + goto fail; + } + + rc = synx_util_init_group_coredata(synx_obj, fences, count); + if (rc) { + pr_err("[sess: %u] error initializing merge synx obj\n", + client->id); + goto clean_up; + } + + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc) { + pr_err("[sess: %u] unable to init merge handle %ld\n", + client->id, h_synx); + goto clean_up; + } + + rc = synx_util_activate(synx_obj); + if (rc) { + pr_err("[sess: %u] unable to activate merge handle %ld\n", + client->id, h_synx); + goto clear_bit; + } + + *h_synx_merged = h_synx; + synx_put_client(client); + pr_debug("[sess: %u] exit merge with status %d\n", + session_id.client_id, rc); + return 0; + +clear_bit: + clear_bit(h_synx, client->bitmap); +clean_up: + kfree(synx_obj); +fail: + synx_util_merge_error(client, h_synxs, count); + if (num_objs <= count) + kfree(fences); + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_merge); + +int synx_release(struct synx_session session_id, s32 h_synx) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter release from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + /* release the reference obtained at synx creation */ + synx_util_release_object(client, h_synx); + synx_util_release_object(client, h_synx); + +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit release with status %d\n", + session_id.client_id, rc); + + return rc; +} +EXPORT_SYMBOL(synx_release); + +int synx_wait(struct synx_session session_id, s32 h_synx, u64 timeout_ms) +{ + int rc = 0; + unsigned long timeleft; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + pr_debug("[sess: %u] Enter wait from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + fence = synx_util_get_fence(synx_obj); + timeleft = dma_fence_wait_timeout(fence, (bool) 0, + msecs_to_jiffies(timeout_ms)); + if (timeleft <= 0) { + pr_err("[sess: %u] wait timeout for handle %d\n", + client->id, h_synx); + rc = -ETIMEDOUT; + goto clear; + } + + rc = synx_util_get_object_status(synx_obj); + /* remap the state if signaled successfully */ + if (rc == SYNX_STATE_SIGNALED_SUCCESS) + rc = 0; + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit wait with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_wait); + +int synx_bind(struct synx_session session_id, + s32 h_synx, + struct synx_external_desc external_sync) +{ + int rc = 0; + u32 i; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_external_data *data = NULL; + struct bind_operations *bind_ops = NULL; + + pr_debug("[sess: %u] Enter bind from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("[sess: %u] cannot bind to merged handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto clear; + } + + bind_ops = synx_util_get_bind_ops(external_sync.type); + if (!bind_ops) { + pr_err("[sess: %u] invalid bind ops for %u\n", + client->id, external_sync.type); + rc = -EINVAL; + goto clear; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + rc = -ENOMEM; + goto clear; + } + + spin_lock_bh(&synx_obj->lock); + if (synx_util_get_object_status_locked(synx_obj) != SYNX_STATE_ACTIVE) { + pr_err("[sess: %u] bind prohibited to inactive handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto free; + } + + if (synx_obj->num_bound_synxs >= SYNX_MAX_NUM_BINDINGS) { + pr_err("[sess: %u] max bindings reached for handle %d\n", + client->id, h_synx); + rc = -ENOMEM; + goto free; + } + + /* don't bind external sync obj is already done */ + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + if (external_sync.id[0] == + synx_obj->bound_synxs[i].external_desc.id[0]) { + pr_err("[sess: %u] duplicate binding for external sync %d\n", + client->id, external_sync.id[0]); + rc = -EALREADY; + goto free; + } + } + + /* data passed to external callback */ + data->h_synx = h_synx; + data->session_id = session_id; + rc = bind_ops->register_callback(synx_external_callback, + data, external_sync.id[0]); + if (rc) { + pr_err("[sess: %u] callback registration failed for %d\n", + client->id, external_sync.id[0]); + goto free; + } + + memcpy(&synx_obj->bound_synxs[synx_obj->num_bound_synxs], + &external_sync, sizeof(struct synx_external_desc)); + synx_obj->bound_synxs[synx_obj->num_bound_synxs].external_data = data; + synx_obj->num_bound_synxs++; + spin_unlock_bh(&synx_obj->lock); + + synx_util_release_object(client, h_synx); + synx_put_client(client); + pr_debug("[sess: %u] exit bind\n", + session_id.client_id); + return 0; + +free: + spin_unlock_bh(&synx_obj->lock); + kfree(data); +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_bind); + +int synx_get_status(struct synx_session session_id, s32 h_synx) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter get_status from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = SYNX_STATE_INVALID; + goto fail; + } + + rc = synx_util_get_object_status(synx_obj); + pr_debug("[sess: %u] synx object handle %d status %d\n", + client->id, h_synx, rc); + + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit get_status with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_get_status); + +int synx_addrefcount(struct synx_session session_id, s32 h_synx, s32 count) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter addrefcount from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if ((count < 0) || (count > SYNX_MAX_REF_COUNTS)) { + pr_err("[sess: %u] invalid addrefcount for handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto clear; + } + + while (count--) + synx_util_acquire_object(client, h_synx); + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit addrefcount with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_addrefcount); + +int synx_import(struct synx_session session_id, + struct synx_import_params *params) +{ + int rc = 0; + long h_synx; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter import from pid %d\n", + session_id.client_id, current->pid); + + if (!params || !params->new_h_synx) { + pr_err("[sess: %u] invalid import arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_import_object(params); + if (!synx_obj) { + pr_err("[sess: %u] invalid import handle %d with key %u\n", + client->id, params->h_synx, params->secure_key); + rc = -EINVAL; + goto fail; + } + + /* + * just need to init handle for importing client, the reference + * in synx object has already been accounted for during export + */ + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc) { + pr_err("[sess: %u] unable to init handle\n", + client->id); + goto clean_up; + } + + *params->new_h_synx = h_synx; + synx_put_client(client); + + pr_debug("[sess: %u] exit import\n", session_id.client_id); + return 0; + +clean_up: + synx_util_put_object(synx_obj); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_import); + +int synx_export(struct synx_session session_id, + struct synx_export_params *params) +{ + int rc = 0; + struct synx_client *client; + + if (!params || !params->secure_key) { + pr_err("[sess: %u] invalid export arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + rc = synx_util_export_object(client, params); + if (rc) + pr_err("[sess: %u] handle export failed %d\n", + client->id, params->h_synx); + + synx_put_client(client); + pr_debug("[sess: %u] exit export with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_export); + +static int synx_handle_create(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int result; + struct synx_info synx_create_info; + struct synx_create_params params; + + if (k_ioctl->size != sizeof(synx_create_info)) + return -EINVAL; + + if (copy_from_user(&synx_create_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = &synx_create_info.synx_obj; + params.name = synx_create_info.name; + result = synx_create(session_id, ¶ms); + + if (!result) + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_create_info, + k_ioctl->size)) + return -EFAULT; + + return result; +} + +static int synx_handle_getstatus(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_signal synx_status; + + if (k_ioctl->size != sizeof(synx_status)) + return -EINVAL; + + if (copy_from_user(&synx_status, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + synx_status.synx_state = + synx_get_status(session_id, synx_status.synx_obj); + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_status, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_import(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_id_info id_info; + struct synx_import_params params; + + if (k_ioctl->size != sizeof(id_info)) + return -EINVAL; + + if (copy_from_user(&id_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = id_info.synx_obj; + params.secure_key = id_info.secure_key; + params.new_h_synx = &id_info.new_synx_obj; + if (synx_import(session_id, ¶ms)) + return -EINVAL; + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &id_info, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_export(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_id_info id_info; + struct synx_export_params params; + + if (k_ioctl->size != sizeof(id_info)) + return -EINVAL; + + if (copy_from_user(&id_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = id_info.synx_obj; + params.secure_key = &id_info.secure_key; + if (synx_export(session_id, ¶ms)) + return -EINVAL; + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &id_info, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_signal(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_signal synx_signal_info; + + if (k_ioctl->size != sizeof(synx_signal_info)) + return -EINVAL; + + if (copy_from_user(&synx_signal_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + return synx_signal(session_id, synx_signal_info.synx_obj, + synx_signal_info.synx_state); +} + +static int synx_handle_merge(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + s32 *h_synxs; + u32 num_objs; + u32 size; + int result; + struct synx_merge synx_merge_info; + + if (k_ioctl->size != sizeof(synx_merge_info)) + return -EINVAL; + + if (copy_from_user(&synx_merge_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + if (synx_merge_info.num_objs >= SYNX_MAX_OBJS) + return -EINVAL; + + num_objs = synx_merge_info.num_objs; + size = sizeof(u32) * num_objs; + h_synxs = kcalloc(synx_merge_info.num_objs, + sizeof(*h_synxs), GFP_KERNEL); + if (!h_synxs) + return -ENOMEM; + + if (copy_from_user(h_synxs, + u64_to_user_ptr(synx_merge_info.synx_objs), + sizeof(u32) * synx_merge_info.num_objs)) { + kfree(h_synxs); + return -EFAULT; + } + + result = synx_merge(session_id, h_synxs, + num_objs, &synx_merge_info.merged); + + if (!result) + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_merge_info, + k_ioctl->size)) { + kfree(h_synxs); + return -EFAULT; + } + + kfree(h_synxs); + return result; +} + +static int synx_handle_wait(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_wait synx_wait_info; + + if (k_ioctl->size != sizeof(synx_wait_info)) + return -EINVAL; + + if (copy_from_user(&synx_wait_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_wait(session_id, synx_wait_info.synx_obj, + synx_wait_info.timeout_ms); + + return 0; +} + +static int synx_handle_register_user_payload( + struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int rc = 0; + u32 idx; + struct synx_userpayload_info user_data; + struct synx_client *client; + struct synx_client_cb *cb_data; + + if (k_ioctl->size != sizeof(user_data)) + return -EINVAL; + + if (copy_from_user(&user_data, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + /* obtain a free index from the cb table */ + rc = synx_util_alloc_cb_entry(client, &idx); + if (rc) { + pr_err("%s: [sess :%u] error allocating cb entry\n", + __func__, client->id); + goto fail; + } + + cb_data = &client->cb_table[idx]; + cb_data->user_cb.client = client; + cb_data->user_cb.h_synx = user_data.synx_obj; + memcpy(&cb_data->user_cb.data, user_data.payload, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + + rc = synx_register_callback(session_id, user_data.synx_obj, + synx_default_user_callback, &idx); + if (rc) { + pr_err("[sess: %u] user cb registration failed for handle %d\n", + session_id.client_id, user_data.synx_obj); + clear_bit(idx, client->cb_bitmap); + } + +fail: + synx_put_client(client); + return rc; +} + +static int synx_handle_deregister_user_payload( + struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int rc = 0; + struct synx_userpayload_info user_data; + struct synx_user_payload payload; + + if (k_ioctl->size != sizeof(user_data)) + return -EINVAL; + + if (copy_from_user(&user_data, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + payload.h_synx = user_data.synx_obj; + memcpy(&payload.data, user_data.payload, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + + rc = synx_deregister_callback(session_id, + payload.h_synx, synx_default_user_callback, + &payload, NULL); + if (rc) + pr_err("[sess: %u] callback deregistration failed for handle %d\n", + session_id.client_id, payload.h_synx); + + return rc; +} + +static int synx_handle_bind(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_bind synx_bind_info; + + if (k_ioctl->size != sizeof(synx_bind_info)) + return -EINVAL; + + if (copy_from_user(&synx_bind_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_bind(session_id, synx_bind_info.synx_obj, + synx_bind_info.ext_sync_desc); + + return k_ioctl->result; +} + +static int synx_handle_addrefcount(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_addrefcount addrefcount_info; + + if (k_ioctl->size != sizeof(addrefcount_info)) + return -EINVAL; + + if (copy_from_user(&addrefcount_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_addrefcount(session_id, + addrefcount_info.synx_obj, + addrefcount_info.count); + + return k_ioctl->result; +} + +static int synx_handle_release(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_info info; + + if (k_ioctl->size != sizeof(info)) + return -EINVAL; + + if (copy_from_user(&info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + return synx_release(session_id, info.synx_obj); +} + +static long synx_ioctl(struct file *filep, + unsigned int cmd, + unsigned long arg) +{ + s32 rc = 0; + struct synx_private_ioctl_arg k_ioctl; + struct synx_session *session = filep->private_data; + + if (cmd != SYNX_PRIVATE_IOCTL_CMD) { + pr_err("invalid ioctl cmd\n"); + return -ENOIOCTLCMD; + } + + if (copy_from_user(&k_ioctl, + (struct synx_private_ioctl_arg *)arg, + sizeof(k_ioctl))) { + pr_err("invalid ioctl args\n"); + return -EFAULT; + } + + if (!k_ioctl.ioctl_ptr) + return -EINVAL; + + pr_debug("[sess: %u] Enter cmd %u from pid %d\n", + session->client_id, k_ioctl.id, current->pid); + + switch (k_ioctl.id) { + case SYNX_CREATE: + rc = synx_handle_create(&k_ioctl, *session); + break; + case SYNX_RELEASE: + rc = synx_handle_release(&k_ioctl, *session); + break; + case SYNX_REGISTER_PAYLOAD: + rc = synx_handle_register_user_payload(&k_ioctl, + *session); + break; + case SYNX_DEREGISTER_PAYLOAD: + rc = synx_handle_deregister_user_payload(&k_ioctl, + *session); + break; + case SYNX_SIGNAL: + rc = synx_handle_signal(&k_ioctl, *session); + break; + case SYNX_MERGE: + rc = synx_handle_merge(&k_ioctl, *session); + break; + case SYNX_WAIT: + rc = synx_handle_wait(&k_ioctl, *session); + if (copy_to_user((void *)arg, + &k_ioctl, + sizeof(k_ioctl))) { + pr_err("invalid ioctl args\n"); + rc = -EFAULT; + } + break; + case SYNX_BIND: + rc = synx_handle_bind(&k_ioctl, *session); + break; + case SYNX_ADDREFCOUNT: + rc = synx_handle_addrefcount(&k_ioctl, *session); + break; + case SYNX_GETSTATUS: + rc = synx_handle_getstatus(&k_ioctl, *session); + break; + case SYNX_IMPORT: + rc = synx_handle_import(&k_ioctl, *session); + break; + case SYNX_EXPORT: + rc = synx_handle_export(&k_ioctl, *session); + break; + default: + rc = -EINVAL; + } + + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + + return rc; +} + +static ssize_t synx_read(struct file *filep, + char __user *buf, size_t size, loff_t *f_pos) +{ + ssize_t rc = 0; + u32 idx; + struct synx_client *client = NULL; + struct synx_client_cb *cb; + struct synx_session *session = filep->private_data; + struct synx_userpayload_info data; + + pr_debug("[sess: %u] Enter from pid %d\n", + session->client_id, current->pid); + + if (size != sizeof(struct synx_userpayload_info)) { + pr_err("invalid read size\n"); + return -EINVAL; + } + + client = synx_get_client(*session); + if (!client) + return -EINVAL; + + mutex_lock(&client->event_q_lock); + cb = list_first_entry_or_null(&client->event_q, + struct synx_client_cb, node); + if (!cb) { + mutex_unlock(&client->event_q_lock); + rc = -EINVAL; + goto fail; + } + list_del_init(&cb->node); + mutex_unlock(&client->event_q_lock); + + rc = size; + data.synx_obj = cb->user_cb.h_synx; + data.reserved = cb->user_cb.status; + memcpy(data.payload, &cb->user_cb.data, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + if (copy_to_user(buf, + &data, + sizeof(struct synx_userpayload_info))) { + pr_err("couldn't copy user callback data\n"); + rc = -EFAULT; + } + + idx = cb->idx; + memset(cb, 0, sizeof(*cb)); + clear_bit(idx, client->cb_bitmap); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + + return rc; +} + +static unsigned int synx_poll(struct file *filep, + struct poll_table_struct *poll_table) +{ + int rc = 0; + struct synx_client *client; + struct synx_session *session = filep->private_data; + + pr_debug("[sess: %u] Enter from pid %d\n", + session->client_id, current->pid); + + client = synx_get_client(*session); + if (!client) { + pr_err("invalid session in poll\n"); + return 0; + } + + poll_wait(filep, &client->event_wq, poll_table); + mutex_lock(&client->event_q_lock); + if (!list_empty(&client->event_q)) + rc = POLLPRI; + mutex_unlock(&client->event_q_lock); + + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + synx_put_client(client); + + return rc; +} + +int synx_initialize(struct synx_session *session_id, + struct synx_initialization_params *params) +{ + u32 i; + long idx; + bool bit; + struct synx_client *client; + struct synx_client_metadata *client_metadata; + + if (!session_id || !params) + return -EINVAL; + + do { + idx = find_first_zero_bit(synx_dev->bitmap, SYNX_MAX_CLIENTS); + if (idx >= SYNX_MAX_CLIENTS) { + pr_err("maximum client limit reached\n"); + return -ENOMEM; + } + bit = test_and_set_bit(idx, synx_dev->bitmap); + } while (bit); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + clear_bit(idx, synx_dev->bitmap); + return -ENOMEM; + } + + if (params->name) + strlcpy(client->name, params->name, sizeof(client->name)); + client->device = synx_dev; + client->id = idx; + mutex_init(&client->event_q_lock); + for (i = 0; i < SYNX_MAX_OBJS; i++) + mutex_init(&client->synx_table_lock[i]); + INIT_LIST_HEAD(&client->event_q); + init_waitqueue_head(&client->event_wq); + /* zero handle not allowed */ + set_bit(0, client->bitmap); + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[idx]; + client_metadata->client = client; + kref_init(&client_metadata->refcount); + mutex_unlock(&synx_dev->dev_table_lock); + + session_id->client_id = idx; + pr_debug("index location %ld allocated for client %s\n", + idx, params->name); + + return 0; +} +EXPORT_SYMBOL(synx_initialize); + +int synx_uninitialize(struct synx_session session_id) +{ + struct synx_client *client; + + client = synx_get_client(session_id); + if (!client) { + pr_err("%s: invalid session id %u\n", + __func__, session_id.client_id); + return -EINVAL; + } + + /* release reference obtained in current function */ + synx_put_client(client); + /* release reference obtained during initialize */ + synx_put_client(client); + return 0; +} +EXPORT_SYMBOL(synx_uninitialize); + +static int synx_open(struct inode *inode, struct file *filep) +{ + int rc = 0; + struct synx_session *session; + char name[SYNX_OBJ_NAME_LEN]; + struct synx_initialization_params params; + + pr_debug("Enter pid: %d\n", current->pid); + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return -ENOMEM; + + scnprintf(name, SYNX_OBJ_NAME_LEN, "umd-client-%d", current->pid); + params.name = name; + rc = synx_initialize(session, ¶ms); + if (rc) { + kfree(session); + } else { + filep->private_data = session; + pr_debug("[sess: %u] allocated new session for pid: %d\n", + session->client_id, current->pid); + } + + return rc; +} + +static int synx_close(struct inode *inode, struct file *filep) +{ + int rc = 0; + struct synx_session *session = filep->private_data; + + pr_debug("[sess: %u] Enter pid: %d\n", + session->client_id, current->pid); + + rc = synx_uninitialize(*session); + kfree(session); + + pr_debug("exit with status %d\n", rc); + + return rc; +} + +static const struct file_operations synx_fops = { + .owner = THIS_MODULE, + .open = synx_open, + .read = synx_read, + .release = synx_close, + .poll = synx_poll, + .unlocked_ioctl = synx_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = synx_ioctl, +#endif +}; + +int synx_register_ops(const struct synx_register_params *params) +{ + s32 rc = 0; + struct synx_registered_ops *client_ops; + + if (!params || !params->name || + !synx_util_is_valid_bind_type(params->type) || + !params->ops.register_callback || + !params->ops.deregister_callback || + !params->ops.signal) { + pr_err("invalid register params\n"); + return -EINVAL; + } + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[params->type]; + if (!client_ops->valid) { + client_ops->valid = true; + memcpy(&client_ops->ops, ¶ms->ops, + sizeof(client_ops->ops)); + strlcpy(client_ops->name, params->name, + sizeof(client_ops->name)); + client_ops->type = params->type; + pr_info("registered bind ops type %u for %s\n", + params->type, params->name); + } else { + pr_info("client already registered for type %u by %s\n", + client_ops->type, client_ops->name); + rc = -EALREADY; + } + mutex_unlock(&synx_dev->vtbl_lock); + + return rc; +} +EXPORT_SYMBOL(synx_register_ops); + +int synx_deregister_ops(const struct synx_register_params *params) +{ + struct synx_registered_ops *client_ops; + + if (!params || !params->name || + !synx_util_is_valid_bind_type(params->type)) { + pr_err("invalid params\n"); + return -EINVAL; + } + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[params->type]; + memset(client_ops, 0, sizeof(*client_ops)); + pr_info("deregistered bind ops for %s\n", + params->name); + mutex_unlock(&synx_dev->vtbl_lock); + + return 0; +} +EXPORT_SYMBOL(synx_deregister_ops); + +static int __init synx_init(void) +{ + int rc; + + pr_info("synx device initialization start\n"); + + synx_dev = kzalloc(sizeof(*synx_dev), GFP_KERNEL); + if (!synx_dev) + return -ENOMEM; + + rc = alloc_chrdev_region(&synx_dev->dev, 0, 1, SYNX_DEVICE_NAME); + if (rc < 0) { + pr_err("region allocation failed\n"); + goto alloc_fail; + } + + cdev_init(&synx_dev->cdev, &synx_fops); + synx_dev->cdev.owner = THIS_MODULE; + rc = cdev_add(&synx_dev->cdev, synx_dev->dev, 1); + if (rc < 0) { + pr_err("device registation failed\n"); + goto reg_fail; + } + + synx_dev->class = class_create(THIS_MODULE, SYNX_DEVICE_NAME); + device_create(synx_dev->class, NULL, synx_dev->dev, + NULL, SYNX_DEVICE_NAME); + + synx_dev->work_queue = alloc_workqueue(SYNX_WORKQUEUE_NAME, + WQ_HIGHPRI | WQ_UNBOUND, SYNX_WORKQUEUE_THREADS); + if (!synx_dev->work_queue) { + pr_err("high priority work queue creation failed\n"); + rc = -EINVAL; + goto fail; + } + + mutex_init(&synx_dev->dev_table_lock); + mutex_init(&synx_dev->vtbl_lock); + synx_dev->dma_context = dma_fence_context_alloc(1); + /* zero session id not allowed */ + set_bit(0, synx_dev->bitmap); + synx_dev->debugfs_root = synx_init_debugfs_dir(synx_dev); + + pr_info("synx device initialization success\n"); + + return 0; + +fail: + device_destroy(synx_dev->class, synx_dev->dev); + class_destroy(synx_dev->class); +reg_fail: + unregister_chrdev_region(synx_dev->dev, 1); +alloc_fail: + kfree(synx_dev); + return rc; +} + +static void __exit synx_exit(void) +{ + struct error_node *err_node, *err_node_tmp; + + device_destroy(synx_dev->class, synx_dev->dev); + class_destroy(synx_dev->class); + cdev_del(&synx_dev->cdev); + unregister_chrdev_region(synx_dev->dev, 1); + synx_remove_debugfs_dir(synx_dev); + /* release uncleared error nodes */ + list_for_each_entry_safe( + err_node, err_node_tmp, + &synx_dev->error_list, + node) { + list_del(&err_node->node); + kfree(err_node); + } + mutex_destroy(&synx_dev->dev_table_lock); + mutex_destroy(&synx_dev->error_lock); + kfree(synx_dev); +} + +module_init(synx_init); +module_exit(synx_exit); + +MODULE_DESCRIPTION("Global Synx Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/msm/synx/synx_api.h b/drivers/media/platform/msm/synx/synx_api.h new file mode 100644 index 000000000000..4a27da0b932e --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_api.h @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_API_H__ +#define __SYNX_API_H__ + +#include +#include + +typedef void (*synx_callback)(s32 sync_obj, int status, void *data); + +/** + * struct bind_operations - Function pointers that need to be defined + * to achieve bind functionality for external fence with synx obj + * + * @register_callback : Function to register with external sync object + * @deregister_callback : Function to deregister with external sync object + * @enable_signaling : Function to enable the signaling on the external + * sync object (optional) + * @signal : Function to signal the external sync object + */ +struct bind_operations { + int (*register_callback)(synx_callback cb_func, + void *userdata, s32 sync_obj); + int (*deregister_callback)(synx_callback cb_func, + void *userdata, s32 sync_obj); + int (*enable_signaling)(s32 sync_obj); + int (*signal)(s32 sync_obj, u32 status); +}; + +/** + * struct synx_register_params - External registration parameters + * + * @ops : Bind operations struct + * @name : External client name + * Only first 64 bytes are accepted, rest will be ignored + * @type : Synx external client type + */ +struct synx_register_params { + struct bind_operations ops; + char *name; + u32 type; +}; + +/** + * struct synx_session - Client session handle + * + * @client_id : Client id + */ +struct synx_session { + u32 client_id; +}; + +/** + * struct synx_initialization_params - Session params + * + * @name : Client session name + * Only first 64 bytes are accepted, rest will be ignored + */ +struct synx_initialization_params { + const char *name; +}; + +/** + * struct synx_create_params - Synx creation parameters + * + * @h_synx : Pointer to synx object handle (filled by function) + * @name : Optional parameter associating a name with the synx + * object for debug purposes + * Only first 64 bytes are accepted, + * rest will be ignored + */ +struct synx_create_params { + s32 *h_synx; + const char *name; +}; + +/** + * struct synx_export_params - Synx export parameters + * + * @h_synx : Synx object handle to export + * @secure_key : Pointer to Key generated for authentication + * (filled by the function) + */ +struct synx_export_params { + s32 h_synx; + u32 *secure_key; +}; + +/** + * struct synx_import_params - Synx import parameters + * + * @h_synx : Synx object handle to import + * @secure_key : Key to verify authenticity + * @new_h_synx : Pointer to newly created synx object + * (filled by the function) + * This handle should be used by importing + * process for all synx api operations + */ +struct synx_import_params { + s32 h_synx; + u32 secure_key; + s32 *new_h_synx; +}; + +/* Kernel APIs */ + +/* @brief: Register operations for external synchronization + * + * Register with synx for enabling external synchronization through bind + * + * @param params : Pointer to register params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params are invalid. + * -ENOMEM will be returned if bind ops cannot be registered due to + * insufficient memory. + * -EALREADY will be returned if type already in use + */ +int synx_register_ops(const struct synx_register_params *params); + +/** + * @brief: De-register external synchronization operations + * + * @param params : Pointer to register params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if record not found. + */ +int synx_deregister_ops(const struct synx_register_params *params); + +/** + * @brief: Initializes a new client session + * + * Create a new client and provides access to the synx framework + * + * @param session_id : Pointer to client session id (filled by function) + * @param params : Pointer to session init params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params is not valid. + * -ENOMEM will be returned if the kernel can't allocate space for + * new client + */ +int synx_initialize(struct synx_session *session_id, + struct synx_initialization_params *params); + +/** + * @brief: Destroys the client session + * + * The client session is destroyed and all synx objects owned + * by the client are cleaned up. + * + * @param session_id : Client session id + * + * @return Status of operation. Zero in case of success. + */ +int synx_uninitialize(struct synx_session session_id); + +/** + * @brief: Creates a synx object + * + * Creates a new synx obj and returns the handle to client + * + * @param session_id : Client session id + * @param params : Pointer to create params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params were invalid. + * -ENOMEM will be returned if the kernel can't allocate space for + * synx object. + */ +int synx_create(struct synx_session session_id, + struct synx_create_params *params); + +/** + * @brief: Registers a callback with a synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param cb_func : Pointer to callback to be registered + * @param userdata : Opaque pointer passed back with callback + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if userdata is invalid. + * -ENOMEM will be returned if cb_func is invalid. + * + */ +int synx_register_callback(struct synx_session session_id, + s32 h_synx, synx_callback cb_func, void *userdata); + +/** + * @brief: De-registers a callback with a synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param cb_func : Pointer to callback to be de-registered + * @param userdata : Opaque pointer passed back with callback + * @param cancel_cb_func : Pointer to callback to ack de-registration + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if userdata is invalid. + * -ENOMEM will be returned if cb_func is invalid. + */ +int synx_deregister_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata, + synx_callback cancel_cb_func); + +/** + * @brief: Signals a synx object with the status argument. + * + * This function will signal the synx object referenced by h_synx + * and invoke any external binding synx objs. + * The status parameter will indicate whether the entity + * performing the signaling wants to convey an error case or a success case. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param status : Status of signaling. + * Value : SYNX_STATE_SIGNALED_SUCCESS or + * SYNX_STATE_SIGNALED_ERROR. + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_signal(struct synx_session session_id, s32 h_synx, u32 status); + +/** + * @brief: Merges multiple synx objects + * + * This function will merge multiple synx objects into a synx group. + * + * @param session_id : Client session id + * @param h_synxs : Pointer to a block of synx handles to be merged + * @param num_objs : Number of synx objs in the block + * @param h_merged_obj : Merged synx object handle (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_merge(struct synx_session session_id, + s32 *h_synxs, u32 num_objs, s32 *h_merged_obj); + +/** + * @brief: Waits for a synx object synchronously + * + * Does a wait on the synx object identified by h_synx for a maximum + * of timeout_ms milliseconds. Must not be called from interrupt context as + * this API can sleep. Should be called from process context only. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle to be waited upon + * @param timeout_ms : Timeout in ms + * + * @return 0 upon success, -EINVAL if synx object is in bad state or arguments + * are invalid, -ETIMEDOUT if wait times out. + */ +int synx_wait(struct synx_session session_id, s32 h_synx, u64 timeout_ms); + +/** + * @brief: Binds synx object with external sync + * + * Binding synx objects with supported external sync object will unify them + * in a way that if one of them signals, the other ones is signaled as well + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param external_sync : External synx descriptor to bind to object + * + * @return 0 upon success, -EINVAL if synx object is in bad state or arguments + * are invalid. + */ +int synx_bind(struct synx_session session_id, s32 h_synx, + struct synx_external_desc external_sync); + +/** + * @brief: Returns the status of the synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * + * @return Status of the synx object + */ +int synx_get_status(struct synx_session session_id, s32 h_synx); + +/** + * @brief: Adds to the reference count of a synx object + * + * When a synx object is created, the refcount will be 1. Every + * additional refcount requires separate release of object handle. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param count : Count to add to the refcount + * + * @return 0 upon success, -EINVAL if synx object is in bad state + */ +int synx_addrefcount(struct synx_session session_id, + s32 h_synx, s32 count); + +/** + * @brief: Imports (looks up) synx object from given handle and key + * + * The given ID should have been exported by another client + * and provided with key. + * + * @param session_id : Client session id + * @param params : Pointer to import params + * + * @return 0 upon success, -EINVAL if synx object is bad state + */ +int synx_import(struct synx_session session_id, + struct synx_import_params *params); + +/** + * @brief: Exports a synx object and returns a key + * + * The synx object handle and given key may be passed to other + * clients to be imported. + * + * @param session_id : Client session id + * @param params : Pointer to export params + * + * @return 0 upon success, -EINVAL if the ID is bad + */ +int synx_export(struct synx_session session_id, + struct synx_export_params *params); + +/** + * @brief: Release the synx object + + * Decrements refcount of a synx object by 1, and destroys it + * if becomes 0 + * + * @param session_id : Client session id + * @param h_synx : Synx object handle to be destroyed + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_release(struct synx_session session_id, s32 h_synx); + +#endif /* __SYNX_API_H__ */ diff --git a/drivers/media/platform/msm/synx/synx_debugfs.c b/drivers/media/platform/msm/synx/synx_debugfs.c new file mode 100644 index 000000000000..c4cc3a96c719 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_debugfs.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "synx_api.h" +#include "synx_debugfs.h" +#include "synx_util.h" + +#define MAX_DBG_BUF_SIZE (36 * SYNX_MAX_OBJS) + +struct dentry *my_direc; +static const char delim[] = ","; +int columns = NAME_COLUMN | ID_COLUMN | + BOUND_COLUMN | STATE_COLUMN | ERROR_CODES; + +void populate_bound_rows( + struct synx_coredata *row, + char *cur, + char *end) +{ + int j; + + for (j = 0; j < row->num_bound_synxs; + j++) { + cur += scnprintf(cur, end - cur, + "\n\tID: %d", + row->bound_synxs[j].external_desc.id[0]); + } +} +static ssize_t synx_table_read(struct file *file, + char *buf, + size_t count, + loff_t *ppos) +{ + + struct synx_device *dev = file->private_data; + struct error_node *err_node, *err_node_tmp; + struct synx_client *client; + struct synx_coredata *row; + char *dbuf, *cur, *end; + + int i = 0; + int j = 0; + int status = SYNX_STATE_INVALID; + ssize_t len = 0; + + dbuf = kzalloc(MAX_DBG_BUF_SIZE, GFP_KERNEL); + if (!dbuf) + return -ENOMEM; + + cur = dbuf; + end = cur + MAX_DBG_BUF_SIZE; + if (columns & NAME_COLUMN) + cur += scnprintf(cur, end - cur, "| Name |"); + if (columns & ID_COLUMN) + cur += scnprintf(cur, end - cur, "| ID |"); + if (columns & BOUND_COLUMN) + cur += scnprintf(cur, end - cur, "| Bound |"); + if (columns & STATE_COLUMN) + cur += scnprintf(cur, end - cur, "| Status |"); + cur += scnprintf(cur, end - cur, "\n"); + + for (j = 0; j < SYNX_MAX_CLIENTS; j++) { + struct synx_session session; + + session.client_id = j; + client = synx_get_client(session); + if (!client) + continue; + + cur += scnprintf(cur, end - cur, + "=============== session %08u ===============\n", + client->id); + for (i = 0; i < SYNX_MAX_OBJS; i++) { + row = synx_util_acquire_object(client, i); + if (!row) + continue; + + spin_lock_bh(&row->lock); + if (columns & NAME_COLUMN) + cur += scnprintf(cur, end - cur, + "|%10s|", row->name); + if (columns & ID_COLUMN) + cur += scnprintf(cur, end - cur, + "|%10d|", i); + if (columns & BOUND_COLUMN) + cur += scnprintf(cur, end - cur, + "|%11d|", row->num_bound_synxs); + if (columns & STATE_COLUMN) { + status = + synx_util_get_object_status_locked(row); + cur += scnprintf(cur, end - cur, + "|%10d|", status); + } + if ((columns & BOUND_COLUMN) && + (row->num_bound_synxs > 0)) { + cur += scnprintf( + cur, end - cur, "\nBound synx: "); + populate_bound_rows(row, + cur, + end); + } + spin_unlock_bh(&row->lock); + cur += scnprintf(cur, end - cur, "\n"); + synx_util_release_object(client, i); + } + cur += scnprintf(cur, end - cur, + "\n================================================\n\n"); + synx_put_client(client); + } + + if (columns & ERROR_CODES && !list_empty( + &dev->error_list)) { + cur += scnprintf( + cur, end - cur, "\nError(s): "); + + mutex_lock(&dev->error_lock); + list_for_each_entry_safe( + err_node, err_node_tmp, + &dev->error_list, + node) { + if (err_node->timestamp != NULL) { + cur += scnprintf(cur, end - cur, + "\n\tTime: %s - ID: %d - Code: %d", + err_node->timestamp, + err_node->h_synx, + err_node->error_code); + } + list_del(&err_node->node); + kfree(err_node); + } + mutex_unlock(&dev->error_lock); + } + + len = simple_read_from_buffer(buf, count, ppos, + dbuf, cur - dbuf); + kfree(dbuf); + return len; +} + +static ssize_t synx_table_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + char *ptr; + char *kbuffer = kzalloc(48, GFP_KERNEL); + int stat = -1; + + if (!kbuffer) + return -ENOMEM; + stat = copy_from_user(kbuffer, buf, 48); + if (stat != 0) { + kfree(kbuffer); + return -EFAULT; + } + while ((ptr = strsep(&kbuffer, delim)) != NULL) { + ptr += '\0'; + if (strcmp(ptr, "bound\n") == 0) + columns = columns ^ BOUND_COLUMN; + else if (strcmp(ptr, "name\n") == 0) + columns = columns ^ NAME_COLUMN; + else if (strcmp(ptr, "synxid\n") == 0) + columns = columns ^ ID_COLUMN; + else if (strcmp(ptr, "status\n") == 0) + columns = columns ^ STATE_COLUMN; + else if (strcmp(ptr, "errors\n") == 0) + columns = columns ^ ERROR_CODES; + } + kfree(kbuffer); + return count; +} + +static const struct file_operations synx_table_fops = { + .owner = THIS_MODULE, + .read = synx_table_read, + .write = synx_table_write, + .open = simple_open, +}; + +struct dentry *synx_init_debugfs_dir(struct synx_device *dev) +{ + struct dentry *dir = NULL; + + dir = debugfs_create_dir("synx_debug", NULL); + + if (!dir) { + pr_debug("Failed to create debugfs for synx\n"); + return NULL; + } + if (!debugfs_create_file("synx_table", + 0644, dir, dev, &synx_table_fops)) { + pr_debug("Failed to create debugfs file for synx\n"); + return NULL; + } + mutex_init(&dev->error_lock); + INIT_LIST_HEAD(&dev->error_list); + return dir; +} + +void synx_remove_debugfs_dir(struct synx_device *dev) +{ + debugfs_remove_recursive(dev->debugfs_root); +} diff --git a/drivers/media/platform/msm/synx/synx_debugfs.h b/drivers/media/platform/msm/synx/synx_debugfs.h new file mode 100644 index 000000000000..4d5091ddf8f1 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_debugfs.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include +#include + +#include "synx_private.h" + +/** + * @brief: Initializes debugfs + * + * @param dev : Pointer to synx device structure + */ +struct dentry *synx_init_debugfs_dir(struct synx_device *dev); + +/** + * @brief: Removes debugfs + * + * @param dev : Pointer to synx device structure + */ +void synx_remove_debugfs_dir(struct synx_device *dev); + +#define NAME_COLUMN 0x0001 +#define ID_COLUMN 0x0002 +#define BOUND_COLUMN 0x0004 +#define STATE_COLUMN 0x0008 +#define ERROR_CODES 0x8000 diff --git a/drivers/media/platform/msm/synx/synx_private.h b/drivers/media/platform/msm/synx/synx_private.h new file mode 100644 index 000000000000..283227016e06 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_private.h @@ -0,0 +1,303 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_PRIVATE_H__ +#define __SYNX_PRIVATE_H__ + +#include +#include +#include +#include +#include + +#define SYNX_NAME "synx" +#define SYNX_WORKQUEUE_NAME "hiprio_synx_work_queue" +#define SYNX_WORKQUEUE_THREADS 1 +#define SYNX_MAX_NUM_BINDINGS 8 +#define SYNX_DEVICE_NAME "synx_device" + +#define SYNX_CLIENT_HANDLE_SHIFT 8 +#define SYNX_SECURE_KEY_SHIFT 16 + +#define MAX_TIMESTAMP_SIZE 32 +#define SYNX_OBJ_NAME_LEN 64 +#define SYNX_MAX_CLIENTS (1UL< +#include + +#include "synx_api.h" +#include "synx_util.h" + +extern void synx_external_callback(s32 sync_obj, int status, void *data); + +bool synx_util_is_valid_bind_type(u32 type) +{ + if (type < SYNX_MAX_BIND_TYPES) + return true; + + return false; +} + +u32 synx_util_get_object_type(struct synx_coredata *synx_obj) +{ + if (!synx_obj) + return 0; + + return synx_obj->type; +} + +bool synx_util_is_merged_object(struct synx_coredata *synx_obj) +{ + if (synx_util_get_object_type(synx_obj) + & SYNX_OBJ_TYPE_MERGED) + return true; + return false; +} + +struct dma_fence *synx_util_get_fence(struct synx_coredata *synx_obj) +{ + if (!synx_obj) + return NULL; + + if (synx_util_is_merged_object(synx_obj)) + return synx_obj->merged_fence; + + return &synx_obj->fence; +} + +void synx_util_get_object(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return; + + fence = synx_util_get_fence(synx_obj); + dma_fence_get(fence); +} + +void synx_util_put_object(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return; + + fence = synx_util_get_fence(synx_obj); + dma_fence_put(fence); +} + +int synx_util_init_coredata(struct synx_coredata *synx_obj, + struct synx_create_params *params, + struct dma_fence_ops *ops) +{ + if (!synx_obj || !params || !ops) { + pr_err("invalid arguments\n"); + return -EINVAL; + } + + spin_lock_init(&synx_obj->lock); + dma_fence_init(&synx_obj->fence, ops, + &synx_obj->lock, synx_dev->dma_context, 1); + + synx_obj->type = SYNX_OBJ_TYPE_LOCAL; + INIT_LIST_HEAD(&synx_obj->reg_cbs_list); + if (params->name) + strlcpy(synx_obj->name, params->name, sizeof(synx_obj->name)); + + return 0; +} + +int synx_util_init_group_coredata(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 num_objs) +{ + struct dma_fence_array *array; + + if (!synx_obj) + return -EINVAL; + + array = dma_fence_array_create(num_objs, fences, + synx_dev->dma_context, 1, false); + if (!array) + return -EINVAL; + + synx_obj->merged_fence = &array->base; + synx_obj->type = SYNX_OBJ_TYPE_LOCAL | SYNX_OBJ_TYPE_MERGED; + INIT_LIST_HEAD(&synx_obj->reg_cbs_list); + + return 0; +} + +void synx_util_object_destroy(struct synx_coredata *synx_obj) +{ + int rc; + u32 i; + s32 sync_id; + u32 type; + struct synx_cb_data *synx_cb, *synx_cb_temp; + struct synx_bind_desc *bind_desc; + struct bind_operations *bind_ops; + struct synx_external_data *data; + + /* clear all the undispatched callbacks */ + list_for_each_entry_safe(synx_cb, + synx_cb_temp, &synx_obj->reg_cbs_list, node) { + pr_err("[sess: %u] cleaning up callback\n", + synx_cb->session_id.client_id); + list_del_init(&synx_cb->node); + kfree(synx_cb); + } + + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + bind_desc = &synx_obj->bound_synxs[i]; + sync_id = bind_desc->external_desc.id[0]; + type = bind_desc->external_desc.type; + data = bind_desc->external_data; + bind_ops = synx_util_get_bind_ops(type); + rc = bind_ops->deregister_callback( + synx_external_callback, data, sync_id); + if (rc < 0) + pr_err("de-registration fail id: %d, type: %u, err: %d\n", + sync_id, type, rc); + /* + * release the memory allocated for external data. + * It is safe to release this memory as external cb + * has been already deregistered before this. + * even if deregistration fails, it implies the + * external fence might have been cleaned up already + * or in some bad state. Have to release cb data + * at this point or else will lead to memory leak. + */ + kfree(data); + } + + kfree(synx_obj); + pr_debug("cleaned up synx object fence\n"); +} + +int synx_util_init_handle(struct synx_client *client, + struct synx_coredata *synx_obj, + long *h_synx) +{ + bool bit; + long idx; + struct synx_handle_coredata *synx_data; + + if (!client || !synx_obj) + return -EINVAL; + + do { + idx = find_first_zero_bit(client->bitmap, SYNX_MAX_OBJS); + if (idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] free index not available\n", + client->id); + return -ENOMEM; + } + bit = test_and_set_bit(idx, client->bitmap); + } while (bit); + + synx_data = &client->synx_table[idx]; + synx_data->client = client; + synx_data->id = idx; + synx_data->synx_obj = synx_obj; + kref_init(&synx_data->internal_refcount); + + *h_synx = idx; + return 0; +} + +int synx_util_activate(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return -EINVAL; + + fence = synx_util_get_fence(synx_obj); + /* move synx to ACTIVE state and register cb for merged object */ + dma_fence_enable_sw_signaling(fence); + return 0; +} + +static u32 synx_util_get_references(struct synx_coredata *synx_obj) +{ + u32 count = 0; + u32 i = 0; + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + + fence = synx_util_get_fence(synx_obj); + /* obtain dma fence reference */ + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return 0; + + for (i = 0; i < array->num_fences; i++) + dma_fence_get(array->fences[i]); + + count = array->num_fences; + } else { + dma_fence_get(fence); + count = 1; + } + + return count; +} + +static void synx_util_put_references(struct synx_coredata *synx_obj) +{ + u32 i = 0; + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + + fence = synx_util_get_fence(synx_obj); + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return; + + for (i = 0; i < array->num_fences; i++) + dma_fence_put(array->fences[i]); + } else { + dma_fence_put(fence); + } +} + +static u32 synx_util_add_fence(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 idx) +{ + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + u32 i = 0; + + fence = synx_util_get_fence(synx_obj); + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return 0; + + for (i = 0; i < array->num_fences; i++) + fences[idx+i] = array->fences[i]; + + return array->num_fences; + } + + fences[idx] = fence; + return 1; +} + +static u32 synx_util_remove_duplicates(struct dma_fence **arr, u32 num) +{ + int i, j; + u32 wr_idx = 1; + + if (!arr) { + pr_err("invalid input array\n"); + return 0; + } + + for (i = 1; i < num; i++) { + for (j = 0; j < wr_idx ; j++) { + if (arr[i] == arr[j]) { + /* release reference obtained for duplicate */ + pr_debug("releasing duplicate reference\n"); + dma_fence_put(arr[i]); + break; + } + } + if (j == wr_idx) + arr[wr_idx++] = arr[i]; + } + + return wr_idx; +} + +s32 synx_util_merge_error(struct synx_client *client, + s32 *h_synxs, + u32 num_objs) +{ + u32 i = 0; + struct synx_coredata *synx_obj; + + if (!client || !h_synxs) + return -EINVAL; + + for (i = 0; i < num_objs; i++) { + synx_obj = synx_util_acquire_object(client, h_synxs[i]); + if (!synx_obj) { + pr_err("[sess: %u] invalid handle %d in merge cleanup\n", + client->id, h_synxs[i]); + continue; + } + /* release all references obtained during merge validatation */ + synx_util_put_references(synx_obj); + synx_util_release_object(client, h_synxs[i]); + } + + return 0; +} + +int synx_util_validate_merge(struct synx_client *client, + s32 *h_synxs, + u32 num_objs, + struct dma_fence ***fence_list, + u32 *fence_cnt) +{ + u32 count = 0; + u32 i = 0; + struct synx_coredata **synx_objs = NULL; + struct dma_fence **fences = NULL; + + if (num_objs <= 1) { + pr_err("single object merge is not allowed\n"); + return -EINVAL; + } + + synx_objs = kcalloc(num_objs, sizeof(*synx_objs), GFP_KERNEL); + if (!synx_objs) + return -ENOMEM; + + for (i = 0; i < num_objs; i++) { + synx_objs[i] = synx_util_acquire_object(client, h_synxs[i]); + if (!synx_objs) { + pr_err("[sess: %u] invalid handle %d in merge list\n", + client->id, h_synxs[i]); + *fence_cnt = i; + return -EINVAL; + } + count += synx_util_get_references(synx_objs[i]); + } + + fences = kcalloc(count, sizeof(*fences), GFP_KERNEL); + if (!fences) { + *fence_cnt = num_objs; + return -ENOMEM; + } + + /* memory will be released later in the invoking function */ + *fence_list = fences; + count = 0; + + for (i = 0; i < num_objs; i++) { + count += synx_util_add_fence(synx_objs[i], fences, count); + /* release the reference obtained earlier in the function */ + synx_util_release_object(client, h_synxs[i]); + } + + *fence_cnt = synx_util_remove_duplicates(fences, count); + kfree(synx_objs); + return 0; +} + +static u32 __fence_state(struct dma_fence *fence, bool locked) +{ + s32 status; + u32 state = SYNX_STATE_INVALID; + + if (!fence) { + pr_err("invalid dma fence addr\n"); + return SYNX_STATE_INVALID; + } + + if (locked) + status = dma_fence_get_status_locked(fence); + else + status = dma_fence_get_status(fence); + + /* convert fence status to synx state */ + switch (status) { + case 0: + state = SYNX_STATE_ACTIVE; + break; + case 1: + state = SYNX_STATE_SIGNALED_SUCCESS; + break; + case -SYNX_STATE_SIGNALED_CANCEL: + state = SYNX_STATE_SIGNALED_CANCEL; + break; + case -SYNX_STATE_FORCED_RELEASE: + state = SYNX_STATE_FORCED_RELEASE; + break; + case -SYNX_STATE_SIGNALED_ERROR: + default: + state = SYNX_STATE_SIGNALED_ERROR; + } + + return state; +} + +static u32 __fence_group_state(struct dma_fence *fence, bool locked) +{ + u32 i = 0; + u32 state = SYNX_STATE_INVALID; + struct dma_fence_array *array = NULL; + u32 intr, actv_cnt, sig_cnt, err_cnt; + + if (!fence) { + pr_err("invalid dma fence addr\n"); + return SYNX_STATE_INVALID; + } + + actv_cnt = sig_cnt = err_cnt = 0; + array = to_dma_fence_array(fence); + if (!array) + return SYNX_STATE_INVALID; + + for (i = 0; i < array->num_fences; i++) { + intr = __fence_state(array->fences[i], locked); + switch (intr) { + case SYNX_STATE_ACTIVE: + actv_cnt++; + break; + case SYNX_STATE_SIGNALED_SUCCESS: + sig_cnt++; + break; + default: + err_cnt++; + } + } + + pr_debug("group cnt stats act:%u, sig: %u, err: %u\n", + actv_cnt, sig_cnt, err_cnt); + + if (err_cnt) + state = SYNX_STATE_SIGNALED_ERROR; + else if (actv_cnt) + state = SYNX_STATE_ACTIVE; + else if (sig_cnt == array->num_fences) + state = SYNX_STATE_SIGNALED_SUCCESS; + + return state; +} + +/* + * WARN: Should not hold the synx spinlock when invoking + * this function. Use synx_fence_state_locked instead + */ +u32 synx_util_get_object_status(struct synx_coredata *synx_obj) +{ + u32 state; + struct dma_fence *fence; + + if (!synx_obj) + return SYNX_STATE_INVALID; + + fence = synx_util_get_fence(synx_obj); + if (synx_util_is_merged_object(synx_obj)) + state = __fence_group_state(fence, false); + else + state = __fence_state(fence, false); + + return state; +} + +/* use this for status check when holding on to metadata spinlock */ +u32 synx_util_get_object_status_locked(struct synx_coredata *synx_obj) +{ + u32 state; + struct dma_fence *fence; + + if (!synx_obj) + return SYNX_STATE_INVALID; + + fence = synx_util_get_fence(synx_obj); + if (synx_util_is_merged_object(synx_obj)) + state = __fence_group_state(fence, true); + else + state = __fence_state(fence, true); + + return state; +} + +struct synx_handle_coredata *synx_util_obtain_handle( + struct synx_client *client, + s32 h_synx) +{ + if (!client) { + pr_err("invalid session argument\n"); + return NULL; + } + + if (h_synx < 0 || h_synx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] invalid handle %d access\n", + client->id, h_synx); + return NULL; + } + + return &client->synx_table[h_synx]; +} + +struct synx_coredata *synx_util_acquire_object( + struct synx_client *client, s32 h_synx) +{ + struct synx_coredata *synx_obj = NULL; + struct synx_handle_coredata *synx_data = + synx_util_obtain_handle(client, h_synx); + + if (!synx_data) + return NULL; + + mutex_lock(&client->synx_table_lock[h_synx]); + synx_obj = synx_data->synx_obj; + if (synx_obj) { + synx_util_get_object(synx_obj); + kref_get(&synx_data->internal_refcount); + pr_debug("[sess: %u] acquired synx object for handle %d\n", + client->id, h_synx); + } + mutex_unlock(&client->synx_table_lock[h_synx]); + + return synx_obj; +} + +static void synx_util_destroy_handle(struct synx_handle_coredata *synx_data) +{ + long idx = synx_data->id; + struct synx_client *client = synx_data->client; + + if (synx_util_is_merged_object(synx_data->synx_obj)) { + pr_debug("cleaned up synx object fence\n"); + kfree(synx_data->synx_obj); + } + + memset(synx_data, 0, sizeof(*synx_data)); + clear_bit(idx, client->bitmap); + pr_debug("[sess: %u] handle %d destroyed\n", + client->id, idx); +} + +void synx_util_destroy_import_handle(struct kref *kref) +{ + struct synx_handle_coredata *synx_data = + container_of(kref, struct synx_handle_coredata, + import_refcount); + + pr_debug("[sess: %u] import handle cleanup for %d\n", + synx_data->client->id, synx_data->id); + + /* in case of pending internal references, abort clean up */ + if (kref_read(&synx_data->internal_refcount)) + return; + + synx_util_destroy_handle(synx_data); +} + +void synx_util_destroy_internal_handle(struct kref *kref) +{ + struct synx_handle_coredata *synx_data = + container_of(kref, struct synx_handle_coredata, + internal_refcount); + + pr_debug("[sess: %u] internal handle cleanup for %d\n", + synx_data->client->id, synx_data->id); + + /* in case of pending imports, abort clean up */ + if (kref_read(&synx_data->import_refcount)) + return; + + synx_util_destroy_handle(synx_data); +} + +void synx_util_release_object(struct synx_client *client, s32 h_synx) +{ + struct synx_handle_coredata *synx_data = + synx_util_obtain_handle(client, h_synx); + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + if (!synx_data) + return; + + mutex_lock(&client->synx_table_lock[h_synx]); + synx_obj = synx_data->synx_obj; + if (synx_obj) { + fence = synx_util_get_fence(synx_obj); + kref_put(&synx_data->internal_refcount, + synx_util_destroy_internal_handle); + dma_fence_put(fence); + } + mutex_unlock(&client->synx_table_lock[h_synx]); +} + +struct bind_operations *synx_util_get_bind_ops(u32 type) +{ + struct synx_registered_ops *client_ops; + + if (!synx_util_is_valid_bind_type(type)) + return NULL; + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[type]; + if (!client_ops->valid) { + mutex_unlock(&synx_dev->vtbl_lock); + return NULL; + } + mutex_unlock(&synx_dev->vtbl_lock); + + return &client_ops->ops; +} + +int synx_util_alloc_cb_entry(struct synx_client *client, + u32 *cb_idx) +{ + bool bit; + long idx; + struct synx_client_cb *cb; + + if (!client || !cb_idx) + return -EINVAL; + + do { + idx = find_first_zero_bit(client->cb_bitmap, SYNX_MAX_OBJS); + if (idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] free cb index not available\n", + client->id); + return -ENOMEM; + } + bit = test_and_set_bit(idx, client->cb_bitmap); + } while (bit); + + cb = &client->cb_table[idx]; + memset(cb, 0, sizeof(*cb)); + cb->idx = idx; + *cb_idx = idx; + pr_debug("[sess: %u] allocated cb index %u\n", client->id, idx); + return 0; +} + +int synx_util_update_cb_entry(struct synx_client *client, + void *data, + u32 cb_idx) +{ + struct synx_client_cb *cb; + + if (!client || !data || (cb_idx >= SYNX_MAX_OBJS)) + return -EINVAL; + + cb = &client->cb_table[cb_idx]; + cb->is_valid = true; + memcpy(&cb->kernel_cb, data, + sizeof(cb->kernel_cb)); + + pr_debug("[sess: %u] updated cb index %u\n", client->id, cb_idx); + return 0; +} + +void synx_util_callback_dispatch(struct synx_coredata *synx_obj, u32 status) +{ + struct synx_cb_data *synx_cb, *synx_cb_temp; + + if (!synx_obj) { + pr_err("invalid arguments\n"); + return; + } + + list_for_each_entry_safe(synx_cb, + synx_cb_temp, &synx_obj->reg_cbs_list, node) { + synx_cb->status = status; + list_del_init(&synx_cb->node); + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + pr_debug("dispatched callback\n"); + } +} + +void synx_util_cb_dispatch(struct work_struct *cb_dispatch) +{ + struct synx_cb_data *synx_cb = + container_of(cb_dispatch, struct synx_cb_data, cb_dispatch); + struct synx_client *client; + struct synx_client_cb *cb; + struct synx_kernel_payload *payload; + + client = synx_get_client(synx_cb->session_id); + if (!client) { + pr_err("invalid session data %u in cb payload\n", + synx_cb->session_id); + goto free; + } + + if (synx_cb->idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] invalid cb index\n", + client->id); + goto fail; + } + + cb = &client->cb_table[synx_cb->idx]; + if (!cb->is_valid) { + pr_err("invalid cb payload\n"); + goto fail; + } + + payload = &cb->kernel_cb; + pr_debug("[sess: %u] kernel cb dispatch for handle %d\n", + client->id, payload->h_synx); + + /* dispatch kernel callback */ + payload->cb_func(payload->h_synx, + synx_cb->status, + payload->data); + +fail: + synx_put_client(client); +free: + pr_debug("released synx_cb_data memory\n"); + kfree(synx_cb); +} + +struct synx_coredata *synx_util_import_object(struct synx_import_params *params) +{ + u16 key; + s32 h_synx; + u32 secure_key; + struct synx_session ex_session_id; + struct synx_client *ex_client; + struct synx_handle_coredata *synx_data; + struct synx_coredata *synx_obj = NULL; + + if (!params) + return NULL; + + h_synx = params->h_synx; + secure_key = params->secure_key; + + ex_session_id.client_id = secure_key & SYNX_CLIENT_HANDLE_MASK; + key = (secure_key >> SYNX_CLIENT_HANDLE_SHIFT) & SYNX_SECURE_KEY_MASK; + + /* get the client exporting the synx handle */ + ex_client = synx_get_client(ex_session_id); + if (!ex_client) { + pr_err("sess: %u invalid import handle %d and/or key %u\n", + ex_session_id.client_id, h_synx, secure_key); + return NULL; + } + + synx_data = synx_util_obtain_handle(ex_client, h_synx); + if (!synx_data) { + pr_err("[sess: %u] invalid import handle %d\n", + ex_client->id, h_synx); + goto fail; + } + + mutex_lock(&ex_client->synx_table_lock[h_synx]); + /* need to check whether import is accounted for in import_refcount */ + if (synx_data->synx_obj && + key && (key == synx_data->key) && + kref_read(&synx_data->import_refcount)) { + if (synx_util_is_merged_object(synx_data->synx_obj)) { + /* + * need to copy the synx coredata for merged object + * as we need to clean up the coredata on release + * since fence array cannot be registered with + * synx_fence_release function. + */ + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (synx_obj) + memcpy(synx_obj, synx_data->synx_obj, + sizeof(*synx_obj)); + } else { + synx_obj = synx_data->synx_obj; + } + /* release the reference obtained during export */ + kref_put(&synx_data->import_refcount, + synx_util_destroy_import_handle); + pr_debug("sess: %u handle %d import successful\n", + ex_client->id, h_synx); + } + mutex_unlock(&ex_client->synx_table_lock[h_synx]); + +fail: + synx_put_client(ex_client); + return synx_obj; +} + +int synx_util_export_object(struct synx_client *client, + struct synx_export_params *params) +{ + int rc = 0; + u16 key = 0; + s32 h_synx; + struct synx_handle_coredata *synx_data; + + if (!params || !params->secure_key) + return -EINVAL; + + h_synx = params->h_synx; + synx_data = synx_util_obtain_handle(client, h_synx); + if (!synx_data) { + pr_err("[sess: %u] invalid export handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + mutex_lock(&client->synx_table_lock[h_synx]); + /* need to check whether import is accounted for in import_refcount */ + if (synx_data->synx_obj) { + /* generate the key on the first export of the handle */ + if (!synx_data->key) { + kref_init(&synx_data->import_refcount); + while (!key) + get_random_bytes(&key, sizeof(key)); + synx_data->key = key; + pr_debug("[sess: %u] import refcount initialized\n", + client->id); + } else { + kref_get(&synx_data->import_refcount); + key = synx_data->key; + } + /* + * to make sure the synx is not lost if the process + * dies or synx is released before any other process + * gets a chance to import it. + * The assumption is that an import will match this + * and account for the extra reference. Otherwise, + * this will be released upon client uninit. + */ + synx_util_get_object(synx_data->synx_obj); + + /* encode the key and the client id to user provided variable */ + *params->secure_key = key; + *params->secure_key <<= SYNX_CLIENT_HANDLE_SHIFT; + *params->secure_key |= client->id; + + pr_debug("[sess: %u] handle %d export successful with key %u\n", + client->id, params->h_synx, *params->secure_key); + } else { + pr_err("[sess: %u] invalid export object %d\n", + client->id, h_synx); + rc = -EINVAL; + } + mutex_unlock(&client->synx_table_lock[h_synx]); + +fail: + return rc; +} + +struct synx_client *synx_get_client(struct synx_session session_id) +{ + struct synx_client_metadata *client_metadata; + struct synx_client *client; + u32 id = session_id.client_id; + + if (id >= SYNX_MAX_CLIENTS) { + pr_err("%s: invalid session handle %u from pid: %d\n", + __func__, id, current->pid); + return NULL; + } + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[id]; + client = client_metadata->client; + if (client) + kref_get(&client_metadata->refcount); + else + pr_err("session %u not available, pid: %d\n", + id, current->pid); + mutex_unlock(&synx_dev->dev_table_lock); + + return client; +} + +static void synx_client_destroy(struct kref *kref) +{ + u32 i; + struct synx_client_metadata *client_metadata = + container_of(kref, struct synx_client_metadata, refcount); + struct synx_client *client = client_metadata->client; + struct synx_handle_coredata *synx_data; + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + /* TODO:can create a thread and handle it */ + + /* go over all the remaining synx obj handles and clear them */ + for (i = 0; i < SYNX_MAX_OBJS; i++) { + synx_data = &client->synx_table[i]; + synx_obj = synx_data->synx_obj; + /* + * cleanup unreleased references by the client + * Note: it is only safe to access synx_obj if + * there are refcounts (internal, import) + * remaining in the current handle, as it + * gurantees corresponding reference to fence. + */ + if (synx_obj) { + fence = synx_util_get_fence(synx_obj); + while (kref_read(&synx_data->internal_refcount)) { + kref_put(&synx_data->internal_refcount, + synx_util_destroy_internal_handle); + dma_fence_put(fence); + } + while (kref_read(&synx_data->import_refcount)) { + kref_put(&synx_data->import_refcount, + synx_util_destroy_import_handle); + dma_fence_put(fence); + } + } + mutex_destroy(&client->synx_table_lock[i]); + } + mutex_destroy(&client->event_q_lock); + memset(client_metadata, 0, sizeof(*client_metadata)); + clear_bit(client->id, synx_dev->bitmap); + + pr_debug("[sess: %u] session destroyed %s\n", + client->id, client->name); + kfree(client); +} + +void synx_put_client(struct synx_client *client) +{ + struct synx_client_metadata *client_metadata; + + if (!client) { + pr_err("%s: invalid client ptr\n", __func__); + return; + } + + if (client->id >= SYNX_MAX_CLIENTS) { + pr_err("%s: session id %u invalid from pid: %d\n", + __func__, client->id, current->pid); + return; + } + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[client->id]; + if (client_metadata->client == client) + /* should not reference client after this call */ + kref_put(&client_metadata->refcount, synx_client_destroy); + else + pr_err("%s: invalid session %u from pid: %d\n", + __func__, client->id, current->pid); + mutex_unlock(&synx_dev->dev_table_lock); +} + +void synx_util_generate_timestamp(char *timestamp, size_t size) +{ + struct timespec64 tv; + struct tm tm; + + ktime_get_real_ts64(&tv); + time64_to_tm(tv.tv_sec, 0, &tm); + snprintf(timestamp, size, "%02d-%02d %02d:%02d:%02d", + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); +} + +void synx_util_log_error(u32 client_id, s32 h_synx, s32 err) +{ + struct error_node *err_node; + + if (!synx_dev->debugfs_root) + return; + + err_node = kzalloc(sizeof(*err_node), GFP_KERNEL); + if (!err_node) + return; + + err_node->client_id = client_id; + err_node->error_code = err; + err_node->h_synx = h_synx; + synx_util_generate_timestamp(err_node->timestamp, + sizeof(err_node->timestamp)); + mutex_lock(&synx_dev->error_lock); + list_add(&err_node->node, + &synx_dev->error_list); + mutex_unlock(&synx_dev->error_lock); +} diff --git a/drivers/media/platform/msm/synx/synx_util.h b/drivers/media/platform/msm/synx/synx_util.h new file mode 100644 index 000000000000..39706dd5de98 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_util.h @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_UTIL_H__ +#define __SYNX_UTIL_H__ + +#include "synx_private.h" + +extern struct synx_device *synx_dev; + +/** + * @brief: Function to check if the external obj type is valid + * + * @param type : External sync object type + * + * @return True if valid. False otherwise + */ +bool synx_util_is_valid_bind_type(u32 type); + +/** + * @brief: Function to get the synx object type + * + * @param synx_obj : Pointer to synx object + * + * @return Synx object type. Zero if invalid + */ +u32 synx_util_get_object_type(struct synx_coredata *synx_obj); + +/** + * @brief: Function to check if the synx object is a composite (merged) + * + * @param synx_obj : Pointer to synx object + * + * @return True if merged object. False otherwise + */ +bool synx_util_is_merged_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to get the dma fence backing the synx object + * + * @param synx_obj : Pointer to synx object + * + * @return Dma fence. NULL if synx_obj is invalid + */ +struct dma_fence *synx_util_get_fence( + struct synx_coredata *synx_obj); + +/** + * @brief: Function to acquire reference to synx object + * + * This acquires additional reference to the synx object and should be + * released using synx_util_put_object + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_get_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to release reference to synx object + * + * This releases reference to the synx object and should be + * matched with synx_util_get_object + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_put_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to initialize synx object + * + * It also initializes the dma fence. This should be called only + * for individual objects. + * + * @param synx_obj : Pointer to synx object + * @param params : Pointer to create params + * @param ops : Dma fence ops required for fence initialization + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_coredata(struct synx_coredata *synx_obj, + struct synx_create_params *params, + struct dma_fence_ops *ops); + +/** + * @brief: Function to initialize a merged synx object. + * + * It also initializes dma fence array. + * + * @param synx_obj : Pointer to synx object + * @param fences : Array of fence objects which will merged + * or grouped together to a fence array + * @param num_objs : Number of fence objects in the array + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_group_coredata(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 num_objs); + +/** + * @brief: Function to cleanup the synx object + * + * This function will be invoked when the dma fence ref count reaches 0. + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_object_destroy(struct synx_coredata *synx_obj); + +/** + * @brief: Function to allocate synx_client_cb entry from cb table + * + * @param client : Pointer to client session info + * @param cb_idx : Allocated index (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_alloc_cb_entry(struct synx_client *client, + u32 *cb_idx); + +/** + * @brief: Function to allocate synx_client_cb entry from cb table + * + * @param client : Pointer to client session info + * @param data : Opaque payload data + * @param cb_idx : Callback table index + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_update_cb_entry(struct synx_client *client, + void *data, + u32 cb_idx); + +/** + * @brief: Function to initialize synx object handle for the client + * + * @param client : Pointer to client session info + * @param synx_obj : Pointer to synx object + * @param h_synx : Pointer to synx object handle (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_handle(struct synx_client *client, + struct synx_coredata *synx_obj, + long *h_synx); + +/** + * @brief: Function to activate the synx object. Moves the synx from INVALID + * state to ACTIVE state. + * + * @param synx_obj : Pointer to synx object + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_activate(struct synx_coredata *synx_obj); + +/** + * @brief: Function to queue all the registered callbacks by clients for + * the synx object + * + * @param synx_obj : Pointer to synx object + * @param state : State of the object + */ +void synx_util_callback_dispatch(struct synx_coredata *synx_obj, u32 state); + +/** + * @brief: Function to dispatch client registered callback + * + * @param cb_dispatch : Pointer to work_struct that needs to be dispatched + */ +void synx_util_cb_dispatch(struct work_struct *cb_dispatch); + +/** + * @brief: Function to handle error during merging of synx objects + * + * Removes the references added on the dma fence objects + * + * @param client : Pointer to client session + * @param h_synxs : Array of synx object handles to merge + * @param num_objs : Number of synx obects in the array + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +s32 synx_util_merge_error(struct synx_client *client, + s32 *h_synxs, + u32 num_objs); + +/** + * @brief: Function to validate synx merge arguments. + * + * It obtains the necessary references to the fence objects + * and also removes duplicates (if any). + * + * @param client : Pointer to client session + * @param h_synxs : Array of synx object handles to merge + * @param num_objs : Number of synx objects in the array + * @param fences : Address to a list of dma fence* array + * @param fence_cnt : Number of fence objects in the fences array + * (filled by the function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_validate_merge(struct synx_client *client, + s32 *h_synxs, + u32 num_objs, + struct dma_fence ***fences, + u32 *fence_cnt); + +/** + * @brief: Function to obtain the state of synx object + * + * The lock associated with the synx obj should not be + * held when invoking this function. + * Use synx_util_get_object_status_locked for state enquiry + * when holding lock + * + * @param synx_obj : Pointer to synx object + * + * @return Status of the synx object. + */ +u32 synx_util_get_object_status(struct synx_coredata *synx_obj); + +/** + * @brief: Function to obtain the state of synx object + * + * The lock associated with the synx obj should be held + * when invoking this function. + * Use synx_util_get_object_status for state enquiry + * when not holding lock + * + * @param synx_obj : Pointer to synx object + * + * @return Status of the synx object. + */ +u32 synx_util_get_object_status_locked(struct synx_coredata *synx_obj); + +/** + * @brief: Function to obtain the synx object metadata for the client + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + * + * @return Pointer to synx object metadata for the client. + * NULL if invalid synx handle. + */ +struct synx_handle_coredata *synx_util_obtain_handle( + struct synx_client *client, s32 h_synx); + +/** + * @brief: Function to acquire the synx object + * + * This function increments the reference count of the dma fence. + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + * + * @return Pointer to synx object. NULL if invalid synx handle. + */ +struct synx_coredata *synx_util_acquire_object( + struct synx_client *client, s32 h_synx); + +/** + * @brief: Function to release the synx object + * + * This function decrements the reference count of the dma fence and + * implicitly calls up the synx object cleanup if count reaches 0. + * Synx object should not be derefrenced in the function after + * calling this. + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + */ +void synx_util_release_object(struct synx_client *client, s32 h_synx); + +/** + * @brief: Function called implicitly when import refcount reaches zero + * + * @param kref : &synx_handle_coredata.import_refcount + */ +void synx_util_destroy_import_handle(struct kref *kref); + +/** + * @brief: Function called implicitly when internal refcount reaches zero + * + * @param kref : &synx_handle_coredata.internal_refcount + */ +void synx_util_destroy_internal_handle(struct kref *kref); + +/** + * @brief: Function to get the bind operations registered by external driver + * + * @param type : External sync type + * + * @return Bind operations. NULL if type is invalid + */ +struct bind_operations *synx_util_get_bind_ops(u32 type); + +/** + * @brief: Function to share the synx object to use by other clients + * + * Shares the synx object by acquiring additional reference to dma fence + * + * @param client : Pointer to client session + * @param params : Pointer to export params struct + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_export_object(struct synx_client *client, + struct synx_export_params *params); + +/** + * @brief: Function to import the synx object for use by the client + * + * Obtains the synx object corresponding to the importing synx handle + * + * @param params : Pointer to import params struct + * + * @return Synx object pointer on success. NULL if import failed or invalid + */ +struct synx_coredata *synx_util_import_object( + struct synx_import_params *params); + +/** + * @brief: Function to acquire the synx client session + * + * This increments the reference count of the client in device client_table + * + * @param session_id : Client session id + * + * @return Pointer to synx client. NULL if invalid session id. + */ +struct synx_client *synx_get_client(struct synx_session session_id); + +/** + * @brief: Function to release the synx client session + * + * This decrements the reference count of the client in device client_table and + * invokes the client session cleanup if reference reaches zero. + * + * @param client : Pointer to client session structure + */ +void synx_put_client(struct synx_client *client); + +/** + * @brief: Function to generate timestamp + * + * @param timestamp : Timestamp string (filled by function) + * @param size : Timestamp string size + */ +void synx_util_generate_timestamp(char *timestamp, size_t size); + +/** + * @brief: Function to log framework errors + * + * This saves the error in a global list and can be accessed using debugfs. + * Needs to be added where we wish to trace the error. + * + * @param id : Client session id + * @param h_synx : Synx object handle + * @param err : Error code + */ +void synx_util_log_error(u32 id, s32 h_synx, s32 err); + +#endif /* __SYNX_UTIL_H__ */