From 2387acc46e8840ac4f250be5e76707208d095ce6 Mon Sep 17 00:00:00 2001 From: Kasin Li Date: Mon, 13 Jul 2020 21:31:43 +0800 Subject: [PATCH] soc: qcom: hgsl: Add fence support Add fence support to user gsl, hsync is for DoorBell commands and isync is for syncing between processes. Change-Id: I3093bfe6d49d0b64f2d20a8208a3b2934c9c7b3a Signed-off-by: Kasin Li --- drivers/soc/qcom/hgsl/Makefile | 2 +- drivers/soc/qcom/hgsl/hgsl.c | 249 +++++++++++--- drivers/soc/qcom/hgsl/hgsl.h | 135 ++++++++ drivers/soc/qcom/hgsl/hgsl_sync.c | 526 ++++++++++++++++++++++++++++++ include/uapi/linux/hgsl.h | 73 ++++- 5 files changed, 942 insertions(+), 43 deletions(-) create mode 100644 drivers/soc/qcom/hgsl/hgsl.h create mode 100644 drivers/soc/qcom/hgsl/hgsl_sync.c diff --git a/drivers/soc/qcom/hgsl/Makefile b/drivers/soc/qcom/hgsl/Makefile index 4968d1004de0..cb09691ad7e9 100644 --- a/drivers/soc/qcom/hgsl/Makefile +++ b/drivers/soc/qcom/hgsl/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_QCOM_HGSL) += hgsl.o +obj-$(CONFIG_QCOM_HGSL) += hgsl.o hgsl_sync.o obj-$(CONFIG_QCOM_HGSL_TCSR_SIGNAL) += hgsl_tcsr.o diff --git a/drivers/soc/qcom/hgsl/hgsl.c b/drivers/soc/qcom/hgsl/hgsl.c index c0159812e18e..0d5a6cad2616 100644 --- a/drivers/soc/qcom/hgsl/hgsl.c +++ b/drivers/soc/qcom/hgsl/hgsl.c @@ -23,11 +23,11 @@ #include #include #include -#include #include #include #include +#include "hgsl.h" #include "hgsl_tcsr.h" #define HGSL_DEVICE_NAME "hgsl" @@ -39,7 +39,6 @@ #define IORESOURCE_HWINF "hgsl_reg_hwinf" #define IORESOURCE_GMUCX "hgsl_reg_gmucx" - /* Set-up profiling packets as needed by scope */ #define CMDBATCH_PROFILING 0x00000010 @@ -266,20 +265,6 @@ struct doorbell_queue { struct mutex lock; }; -struct hgsl_context { - uint32_t context_id; - struct dma_buf *shadow_dma; - void *shadow_vbase; - uint32_t shadow_sop_off; - uint32_t shadow_eop_off; - wait_queue_head_t wait_q; - pid_t pid; - bool dbq_assigned; - - bool in_destroy; - struct kref kref; -}; - struct hgsl_active_wait { struct list_head head; struct hgsl_context *ctxt; @@ -319,12 +304,6 @@ struct qcom_hgsl { struct hw_version *ver; }; -struct hgsl_priv { - struct qcom_hgsl *dev; - uint32_t dbq_idx; - pid_t pid; -}; - static int hgsl_reg_map(struct platform_device *pdev, char *res_name, struct reg *reg); @@ -698,28 +677,49 @@ static int hgsl_dbq_assign(struct file *filep, unsigned long arg) return dbq->state; } -static inline bool _timestamp_retired(struct hgsl_context *ctxt, - unsigned int timestamp) +static inline uint32_t get_context_timestamp(struct hgsl_context *ctxt) { - unsigned int ts = *(unsigned int *)(ctxt->shadow_vbase + + unsigned int ts; + + ts = *(unsigned int *)(ctxt->shadow_vbase + ctxt->shadow_eop_off); /* ensure read is done before comparison */ rmb(); - return (ts >= timestamp); + return ts; } -static irqreturn_t hgsl_tcsr_isr(struct device *dev, uint32_t status) +static inline bool _timestamp_retired(struct hgsl_context *ctxt, + unsigned int timestamp) { - struct platform_device *pdev = to_platform_device(dev); - struct qcom_hgsl *hgsl = platform_get_drvdata(pdev); + return (get_context_timestamp(ctxt) >= timestamp); +} - if ((status & GLB_DB_DEST_TS_RETIRE_IRQ_MASK) == 0) - return IRQ_NONE; +static inline void _destroy_context(struct kref *kref); +static void _signal_contexts(struct qcom_hgsl *hgsl) +{ + struct hgsl_context *ctxt; + int i; + uint32_t ts; - queue_work(hgsl->wq, &hgsl->ts_retire_work); + for (i = 0; i < HGSL_CONTEXT_NUM; i++) { + read_lock(&hgsl->ctxt_lock); + ctxt = hgsl->contexts[i]; + read_unlock(&hgsl->ctxt_lock); + + if (ctxt == NULL) + continue; + + kref_get(&ctxt->kref); + ts = get_context_timestamp(ctxt); + if (ts != ctxt->last_ts) { + hgsl_hsync_timeline_signal(ctxt->timeline, ts); + ctxt->last_ts = ts; + } + kref_put(&ctxt->kref, _destroy_context); + + } - return IRQ_HANDLED; } static void ts_retire_worker(struct work_struct *work) @@ -734,6 +734,21 @@ static void ts_retire_worker(struct work_struct *work) wake_up_all(&wait->ctxt->wait_q); } spin_unlock(&hgsl->active_wait_lock); + + _signal_contexts(hgsl); +} + +static irqreturn_t hgsl_tcsr_isr(struct device *dev, uint32_t status) +{ + struct platform_device *pdev = to_platform_device(dev); + struct qcom_hgsl *hgsl = platform_get_drvdata(pdev); + + if ((status & GLB_DB_DEST_TS_RETIRE_IRQ_MASK) == 0) + return IRQ_NONE; + + queue_work(hgsl->wq, &hgsl->ts_retire_work); + + return IRQ_HANDLED; } static int hgsl_init_global_db(struct qcom_hgsl *hgsl, @@ -952,7 +967,12 @@ static int hgsl_context_create(struct file *filep, unsigned long arg) struct dma_buf *dmabuf; void *vbase; struct hgsl_context *ctxt; - int ret; + int ret = 0; + + if (!hgsl->contexts) { + dev_err(hgsl->dev, "DBQ not initialized propertily\n"); + return -ENODEV; + } if (!is_global_db(hgsl->tcsr_idx)) { dev_err(hgsl->dev, "Global doorbell not supported for this process\n"); @@ -1000,11 +1020,12 @@ static int hgsl_context_create(struct file *filep, unsigned long arg) write_lock(&hgsl->ctxt_lock); if (hgsl->contexts[param.context_id] != NULL) { + write_unlock(&hgsl->ctxt_lock); dev_err(hgsl->dev, "context id %d already created\n", param.context_id); ret = -EBUSY; - goto err_unlock; + goto err_free; } hgsl->contexts[param.context_id] = ctxt; @@ -1017,10 +1038,18 @@ static int hgsl_context_create(struct file *filep, unsigned long arg) kref_init(&ctxt->kref); write_unlock(&hgsl->ctxt_lock); + + ret = hgsl_hsync_timeline_create(ctxt); + if (ret < 0) { + dev_err(hgsl->dev, + "hsync timeline failed for context %d\n", + param.context_id); + goto err_free; + } + return 0; -err_unlock: - write_unlock(&hgsl->ctxt_lock); +err_free: kfree(ctxt); err_dma_unmap: dma_buf_vunmap(dmabuf, vbase); @@ -1075,6 +1104,8 @@ static int hgsl_context_destroy(struct file *filep, unsigned long arg, write_unlock(&hgsl->ctxt_lock); + hgsl_hsync_timeline_put(ctxt->timeline); + kref_put(&ctxt->kref, _destroy_context); return 0; } @@ -1106,14 +1137,13 @@ static int hgsl_wait_timestamp(struct file *filep, unsigned long arg) read_lock(&hgsl->ctxt_lock); ctxt = hgsl->contexts[param.context_id]; + read_unlock(&hgsl->ctxt_lock); if (ctxt == NULL) { - read_unlock(&hgsl->ctxt_lock); dev_err(hgsl->dev, "context id %d is not created\n", param.context_id); return -EINVAL; } - read_unlock(&hgsl->ctxt_lock); if (_timestamp_retired(ctxt, timestamp)) return 0; @@ -1161,9 +1191,10 @@ static int hgsl_dbq_release(struct file *filep, unsigned long arg, struct qcom_hgsl *hgsl = priv->dev; struct doorbell_queue *dbq; struct hgsl_dbq_release_info rel_info; + int ret = 0; if (!force_cleanup) - copy_from_user(&rel_info, USRPTR(arg), + ret = copy_from_user(&rel_info, USRPTR(arg), sizeof(rel_info)); else rel_info = *(struct hgsl_dbq_release_info *)arg; @@ -1183,7 +1214,7 @@ static int hgsl_dbq_release(struct file *filep, unsigned long arg, rel_info.ref_count = (dbq->state == DB_STATE_Q_INIT_DONE) ? 1 : 0; if (!force_cleanup) - copy_to_user(USRPTR(arg), &rel_info, sizeof(rel_info)); + ret = copy_to_user(USRPTR(arg), &rel_info, sizeof(rel_info)); return priv->dbq_idx; } @@ -1197,6 +1228,9 @@ static int hgsl_open(struct inode *inodep, struct file *filep) if (!priv) return -ENOMEM; + idr_init(&priv->isync_timeline_idr); + spin_lock_init(&priv->isync_timeline_lock); + priv->dev = hgsl; filep->private_data = priv; return 0; @@ -1247,6 +1281,120 @@ static ssize_t hgsl_read(struct file *filep, char __user *buf, size_t count, buff, strlen(buff) + 1); } +static int hgsl_ioctl_hsync_fence_create(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + struct qcom_hgsl *hgsl = priv->dev; + struct hgsl_hsync_fence_create param; + struct hgsl_context *ctxt; + int ret = 0; + + copy_from_user(¶m, USRPTR(arg), sizeof(param)); + + read_lock(&hgsl->ctxt_lock); + ctxt = hgsl->contexts[param.context_id]; + read_unlock(&hgsl->ctxt_lock); + + if (ctxt == NULL) { + dev_err(hgsl->dev, + "context id %d is not created\n", + param.context_id); + return -EINVAL; + } + + kref_get(&ctxt->kref); + + param.fence_fd = hgsl_hsync_fence_create_fd(ctxt, param.timestamp); + if (param.fence_fd < 0) { + ret = param.fence_fd; + goto out; + } + copy_to_user(USRPTR(arg), ¶m, sizeof(param)); +out: + kref_put(&ctxt->kref, _destroy_context); + + return ret; +} + +static int hgsl_ioctl_isync_timeline_create(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + uint32_t param = 0; + int ret = 0; + + ret = hgsl_isync_timeline_create(priv, ¶m); + if (ret == 0) + copy_to_user(USRPTR(arg), ¶m, sizeof(param)); + + return ret; +} + +static int hgsl_ioctl_isync_timeline_destroy(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + uint32_t param = 0; + int ret = 0; + + copy_from_user(¶m, USRPTR(arg), sizeof(param)); + ret = hgsl_isync_timeline_destroy(priv, param); + + return ret; +} + +static int hgsl_ioctl_isync_fence_create(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + struct hgsl_isync_create_fence param; + int ret = 0; + int fence = 0; + + copy_from_user(¶m, USRPTR(arg), sizeof(param)); + + ret = hgsl_isync_fence_create(priv, param.timeline_id, + param.ts, &fence); + + if (ret == 0) { + param.fence_id = fence; + copy_to_user(USRPTR(arg), ¶m, sizeof(param)); + } + + return ret; +} + +static int hgsl_ioctl_isync_fence_signal(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + struct hgsl_isync_signal_fence param; + int ret = 0; + + copy_from_user(¶m, USRPTR(arg), sizeof(param)); + + ret = hgsl_isync_fence_signal(priv, param.timeline_id, + param.fence_id); + + return ret; +} + +static int hgsl_ioctl_isync_forward(struct file *filep, + unsigned long arg) +{ + struct hgsl_priv *priv = filep->private_data; + struct hgsl_isync_forward param; + int ret = 0; + + copy_from_user(¶m, USRPTR(arg), sizeof(param)); + + ret = hgsl_isync_forward(priv, param.timeline_id, + param.ts); + + return ret; +} + static long hgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { int ret; @@ -1276,6 +1424,25 @@ static long hgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) case HGSL_IOCTL_WAIT_TIMESTAMP: ret = hgsl_wait_timestamp(filep, arg); break; + case HGSL_IOCTL_HSYNC_FENCE_CREATE: + ret = hgsl_ioctl_hsync_fence_create(filep, arg); + break; + case HGSL_IOCTL_ISYNC_TIMELINE_CREATE: + ret = hgsl_ioctl_isync_timeline_create(filep, arg); + break; + case HGSL_IOCTL_ISYNC_TIMELINE_DESTROY: + ret = hgsl_ioctl_isync_timeline_destroy(filep, arg); + break; + case HGSL_IOCTL_ISYNC_FENCE_CREATE: + ret = hgsl_ioctl_isync_fence_create(filep, arg); + break; + case HGSL_IOCTL_ISYNC_FENCE_SIGNAL: + ret = hgsl_ioctl_isync_fence_signal(filep, arg); + break; + case HGSL_IOCTL_ISYNC_FORWARD: + ret = hgsl_ioctl_isync_forward(filep, arg); + break; + default: ret = -ENOIOCTLCMD; } diff --git a/drivers/soc/qcom/hgsl/hgsl.h b/drivers/soc/qcom/hgsl/hgsl.h new file mode 100644 index 000000000000..bc2217f78e85 --- /dev/null +++ b/drivers/soc/qcom/hgsl/hgsl.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __HGSL_H_ +#define __HGSL_H_ + +#include +#include +#include +#include + +#define HGSL_TIMELINE_NAME_LEN 64 + + +struct qcom_hgsl; +struct hgsl_hsync_timeline; + +/** + * HGSL context define + **/ +struct hgsl_context { + uint32_t context_id; + struct dma_buf *shadow_dma; + void *shadow_vbase; + uint32_t shadow_sop_off; + uint32_t shadow_eop_off; + wait_queue_head_t wait_q; + pid_t pid; + bool dbq_assigned; + + bool in_destroy; + struct kref kref; + + uint32_t last_ts; + struct hgsl_hsync_timeline *timeline; +}; + +struct hgsl_priv { + struct qcom_hgsl *dev; + uint32_t dbq_idx; + pid_t pid; + + struct idr isync_timeline_idr; + spinlock_t isync_timeline_lock; +}; + + +static inline bool hgsl_ts_ge(uint32_t a, uint32_t b) +{ + return a >= b; +} + +/** + * struct hgsl_hsync_timeline - A sync timeline attached under each hgsl context + * @kref: Refcount to keep the struct alive + * @name: String to describe this timeline + * @fence_context: Used by the fence driver to identify fences belonging to + * this context + * @child_list_head: List head for all fences on this timeline + * @lock: Spinlock to protect this timeline + * @last_ts: Last timestamp when signaling fences + */ +struct hgsl_hsync_timeline { + struct kref kref; + struct hgsl_context *context; + + char name[HGSL_TIMELINE_NAME_LEN]; + u64 fence_context; + + spinlock_t lock; + struct list_head fence_list; + unsigned int last_ts; +}; + +/** + * struct hgsl_hsync_fence - A struct containing a fence and other data + * associated with it + * @fence: The fence struct + * @sync_file: Pointer to the sync file + * @parent: Pointer to the hgsl sync timeline this fence is on + * @child_list: List of fences on the same timeline + * @context_id: hgsl context id + * @ts: Context timestamp that this fence is associated with + */ +struct hgsl_hsync_fence { + struct dma_fence fence; + struct sync_file *sync_file; + struct hgsl_hsync_timeline *timeline; + struct list_head child_list; + u32 context_id; + unsigned int ts; +}; + +struct hgsl_isync_timeline { + struct kref kref; + char name[HGSL_TIMELINE_NAME_LEN]; + int id; + struct hgsl_priv *priv; + struct list_head fence_list; + spinlock_t lock; + u32 last_ts; +}; + +struct hgsl_isync_fence { + struct dma_fence fence; + struct hgsl_isync_timeline *timeline; + struct list_head child_list; + u32 ts; +}; + +/* Fence for commands. */ +struct hgsl_hsync_fence *hgsl_hsync_fence_create( + struct hgsl_context *context, + uint32_t ts); +int hgsl_hsync_fence_create_fd(struct hgsl_context *context, + uint32_t ts); +int hgsl_hsync_timeline_create(struct hgsl_context *context); +void hgsl_hsync_timeline_signal(struct hgsl_hsync_timeline *timeline, + unsigned int ts); +void hgsl_hsync_timeline_put(struct hgsl_hsync_timeline *timeline); + +/* Fence for process sync. */ +int hgsl_isync_timeline_create(struct hgsl_priv *priv, + uint32_t *timeline_id); +int hgsl_isync_timeline_destroy(struct hgsl_priv *priv, uint32_t id); +int hgsl_isync_fence_create(struct hgsl_priv *priv, uint32_t timeline_id, + uint32_t ts, int *fence); +int hgsl_isync_fence_signal(struct hgsl_priv *priv, uint32_t timeline_id, + int fence_fd); +int hgsl_isync_forward(struct hgsl_priv *priv, uint32_t timeline_id, + uint32_t ts); + +#endif /* __HGSL_H_ */ diff --git a/drivers/soc/qcom/hgsl/hgsl_sync.c b/drivers/soc/qcom/hgsl/hgsl_sync.c new file mode 100644 index 000000000000..1fc3e3d16c31 --- /dev/null +++ b/drivers/soc/qcom/hgsl/hgsl_sync.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hgsl.h" + +static const struct dma_fence_ops hgsl_hsync_fence_ops; +static const struct dma_fence_ops hgsl_isync_fence_ops; + + +int hgsl_hsync_fence_create_fd(struct hgsl_context *context, + uint32_t ts) +{ + int fence_fd; + struct hgsl_hsync_fence *fence; + + fence_fd = get_unused_fd_flags(0); + if (fence_fd < 0) + return fence_fd; + + fence = hgsl_hsync_fence_create(context, ts); + if (fence == NULL) { + put_unused_fd(fence_fd); + return -ENOMEM; + } + + fd_install(fence_fd, fence->sync_file->file); + + return fence_fd; +} + +struct hgsl_hsync_fence *hgsl_hsync_fence_create( + struct hgsl_context *context, + uint32_t ts) +{ + unsigned long flags; + struct hgsl_hsync_timeline *timeline = context->timeline; + struct hgsl_hsync_fence *fence; + + if (!kref_get_unless_zero(&timeline->kref)) + return NULL; + + fence = kzalloc(sizeof(*fence), GFP_KERNEL); + if (fence == NULL) { + hgsl_hsync_timeline_put(timeline); + return NULL; + } + + fence->timeline = timeline; + fence->ts = ts; + + dma_fence_init(&fence->fence, &hgsl_hsync_fence_ops, + &timeline->lock, timeline->fence_context, ts); + + fence->sync_file = sync_file_create(&fence->fence); + if (fence->sync_file == NULL) { + hgsl_hsync_timeline_put(timeline); + kfree(fence); + return NULL; + } + + spin_lock_irqsave(&timeline->lock, flags); + list_add_tail(&fence->child_list, &timeline->fence_list); + spin_unlock_irqrestore(&timeline->lock, flags); + + return fence; +} + +void hgsl_hsync_timeline_signal(struct hgsl_hsync_timeline *timeline, + unsigned int ts) +{ + struct hgsl_hsync_fence *cur, *next; + unsigned long flags; + + if (!kref_get_unless_zero(&timeline->kref)) + return; + + if (!hgsl_ts_ge(ts, timeline->last_ts)) + return; + + spin_lock_irqsave(&timeline->lock, flags); + list_for_each_entry_safe(cur, next, &timeline->fence_list, + child_list) { + if (hgsl_ts_ge(ts, cur->ts)) { + dma_fence_signal_locked(&cur->fence); + list_del_init(&cur->child_list); + dma_fence_put(&cur->fence); + } + } + spin_unlock_irqrestore(&timeline->lock, flags); + + timeline->last_ts = ts; + hgsl_hsync_timeline_put(timeline); +} + +int hgsl_hsync_timeline_create(struct hgsl_context *context) +{ + struct hgsl_hsync_timeline *timeline; + + timeline = kzalloc(sizeof(*timeline), GFP_KERNEL); + if (!timeline) + return -ENOMEM; + + snprintf(timeline->name, HGSL_TIMELINE_NAME_LEN, + "timeline_%s_%d", + current->comm, current->pid); + + kref_init(&timeline->kref); + timeline->fence_context = dma_fence_context_alloc(1); + INIT_LIST_HEAD(&timeline->fence_list); + spin_lock_init(&timeline->lock); + timeline->context = context; + + context->timeline = timeline; + + return 0; +} + +static void hgsl_hsync_timeline_destroy(struct kref *kref) +{ + struct hgsl_hsync_timeline *timeline = + container_of(kref, struct hgsl_hsync_timeline, kref); + + kfree(timeline); +} + +void hgsl_hsync_timeline_put(struct hgsl_hsync_timeline *timeline) +{ + if (timeline) + kref_put(&timeline->kref, hgsl_hsync_timeline_destroy); +} + +static const char *hgsl_hsync_get_driver_name(struct dma_fence *base) +{ + return "hgsl-timeline"; +} + +static const char *hgsl_hsync_get_timeline_name(struct dma_fence *base) +{ + struct hgsl_hsync_fence *fence = + container_of(base, struct hgsl_hsync_fence, fence); + struct hgsl_hsync_timeline *timeline = fence->timeline; + + return timeline->name; +} + +static bool hgsl_hsync_enable_signaling(struct dma_fence *base) +{ + return true; +} + +static bool hgsl_hsync_has_signaled(struct dma_fence *base) +{ + struct hgsl_hsync_fence *fence = + container_of(base, struct hgsl_hsync_fence, fence); + struct hgsl_hsync_timeline *timeline = fence->timeline; + + return hgsl_ts_ge(timeline->last_ts, fence->ts); +} + +static void hgsl_hsync_fence_release(struct dma_fence *base) +{ + struct hgsl_hsync_fence *fence = + container_of(base, struct hgsl_hsync_fence, fence); + struct hgsl_hsync_timeline *timeline = fence->timeline; + + if (timeline) + hgsl_hsync_timeline_put(timeline); + kfree(fence); +} + +static void hgsl_hsync_fence_value_str(struct dma_fence *base, + char *str, int size) +{ + struct hgsl_hsync_fence *fence = + container_of(base, struct hgsl_hsync_fence, fence); + + snprintf(str, size, "%u", fence->ts); +} + +static void hgsl_hsync_timeline_value_str(struct dma_fence *base, + char *str, int size) +{ + struct hgsl_hsync_fence *fence = + container_of(base, struct hgsl_hsync_fence, fence); + struct hgsl_hsync_timeline *timeline = fence->timeline; + + if (!kref_get_unless_zero(&timeline->kref)) + return; + + snprintf(str, size, "Last retired TS:%u", timeline->last_ts); + + hgsl_hsync_timeline_put(timeline); +} + +static const struct dma_fence_ops hgsl_hsync_fence_ops = { + .get_driver_name = hgsl_hsync_get_driver_name, + .get_timeline_name = hgsl_hsync_get_timeline_name, + .enable_signaling = hgsl_hsync_enable_signaling, + .signaled = hgsl_hsync_has_signaled, + .wait = dma_fence_default_wait, + .release = hgsl_hsync_fence_release, + + .fence_value_str = hgsl_hsync_fence_value_str, + .timeline_value_str = hgsl_hsync_timeline_value_str, +}; + +static void hgsl_isync_timeline_release(struct kref *kref) +{ + struct hgsl_isync_timeline *timeline = container_of(kref, + struct hgsl_isync_timeline, + kref); + + kfree(timeline); +} + +static struct hgsl_isync_timeline * +hgsl_isync_timeline_get(struct hgsl_priv *priv, int id) +{ + int ret = 0; + struct hgsl_isync_timeline *timeline = NULL; + + spin_lock(&priv->isync_timeline_lock); + + timeline = idr_find(&priv->isync_timeline_idr, id); + if (timeline) + ret = kref_get_unless_zero(&timeline->kref); + + spin_unlock(&priv->isync_timeline_lock); + + if (!ret) + timeline = NULL; + + return timeline; +} + +static void hgsl_isync_timeline_put(struct hgsl_isync_timeline *timeline) +{ + if (timeline) + kref_put(&timeline->kref, hgsl_isync_timeline_release); +} + +int hgsl_isync_timeline_create(struct hgsl_priv *priv, + uint32_t *timeline_id) +{ + struct hgsl_isync_timeline *timeline; + int ret = -EINVAL; + uint32_t idr; + + if (timeline_id == NULL) + return -EINVAL; + + timeline = kzalloc(sizeof(*timeline), GFP_KERNEL); + if (timeline == NULL) + return -ENOMEM; + + kref_init(&timeline->kref); + INIT_LIST_HEAD(&timeline->fence_list); + spin_lock_init(&timeline->lock); + + idr_preload(GFP_KERNEL); + spin_lock(&priv->isync_timeline_lock); + idr = idr_alloc(&priv->isync_timeline_idr, timeline, 1, 0, GFP_NOWAIT); + spin_unlock(&priv->isync_timeline_lock); + idr_preload_end(); + + if (idr > 0) { + timeline->id = idr; + *timeline_id = idr; + ret = 0; + } else + kfree(timeline); + + return ret; +} + +int hgsl_isync_fence_create(struct hgsl_priv *priv, uint32_t timeline_id, + uint32_t ts, int *fence_fd) +{ + struct hgsl_isync_timeline *timeline = NULL; + struct hgsl_isync_fence *fence = NULL; + struct sync_file *sync_file = NULL; + int ret = 0; + + if (fence_fd == NULL) + return -EINVAL; + + timeline = hgsl_isync_timeline_get(priv, timeline_id); + if (timeline == NULL) { + ret = -EINVAL; + goto out; + } + + fence = kzalloc(sizeof(*fence), GFP_KERNEL); + if (fence == NULL) { + ret = -ENOMEM; + goto out; + } + + fence->timeline = timeline; + + /* set a minimal ts if user don't set it */ + if (ts == 0) + ts = 1; + + fence->ts = ts; + + dma_fence_init(&fence->fence, &hgsl_isync_fence_ops, + &timeline->lock, + dma_fence_context_alloc(1), + ts); + + sync_file = sync_file_create(&fence->fence); + + if (sync_file == NULL) { + ret = -ENOMEM; + goto out; + } + + *fence_fd = get_unused_fd_flags(0); + if (*fence_fd < 0) { + ret = -EBADF; + goto out; + } + + fd_install(*fence_fd, sync_file->file); + + spin_lock(&timeline->lock); + list_add_tail(&fence->child_list, &timeline->fence_list); + spin_unlock(&timeline->lock); + +out: + if (ret) { + if (sync_file) + fput(sync_file->file); + + if (fence) + dma_fence_put(&fence->fence); + + if (timeline) + hgsl_isync_timeline_put(timeline); + } + + return ret; +} + +static void hgsl_isync_timeline_cleanup(struct hgsl_priv *priv, + struct hgsl_isync_timeline *timeline) +{ + struct hgsl_isync_fence *cur, *next; + + spin_lock(&timeline->lock); + list_for_each_entry_safe(cur, next, &timeline->fence_list, + child_list) { + dma_fence_signal_locked(&cur->fence); + list_del_init(&cur->child_list); + } + + spin_unlock(&timeline->lock); + + hgsl_isync_timeline_put(timeline); +} + +int hgsl_isync_timeline_destroy(struct hgsl_priv *priv, uint32_t id) +{ + struct hgsl_isync_timeline *timeline; + + spin_lock(&priv->isync_timeline_lock); + timeline = idr_find(&priv->isync_timeline_idr, id); + + if (timeline == NULL) { + spin_unlock(&priv->isync_timeline_lock); + return -EINVAL; + } + + if (timeline->id > 0) { + idr_remove(&priv->isync_timeline_idr, timeline->id); + timeline->id = 0; + } + spin_unlock(&priv->isync_timeline_lock); + + hgsl_isync_timeline_cleanup(priv, timeline); + + return 0; +} + +static int _isync_timeline_signal( + struct hgsl_isync_timeline *timeline, + struct dma_fence *fence) +{ + struct hgsl_isync_fence *cur, *next; + int ret = -EINVAL; + + spin_lock(&timeline->lock); + + list_for_each_entry_safe(cur, next, &timeline->fence_list, + child_list) { + if (fence == &cur->fence) { + dma_fence_signal_locked(fence); + list_del_init(&cur->child_list); + ret = 0; + break; + } + } + spin_unlock(&timeline->lock); + return ret; +} + +int hgsl_isync_fence_signal(struct hgsl_priv *priv, uint32_t timeline_id, + int fence_fd) +{ + struct hgsl_isync_timeline *timeline; + struct dma_fence *fence = NULL; + int ret = -EINVAL; + + timeline = hgsl_isync_timeline_get(priv, timeline_id); + if (timeline == NULL) { + ret = -EINVAL; + goto out; + } + + fence = sync_file_get_fence(fence_fd); + if (fence == NULL) { + ret = -EBADF; + goto out; + } + + ret = _isync_timeline_signal(timeline, fence); +out: + if (fence) + dma_fence_put(fence); + + if (timeline) + hgsl_isync_timeline_put(timeline); + return ret; +} + +int hgsl_isync_forward(struct hgsl_priv *priv, uint32_t timeline_id, + uint32_t ts) +{ + struct hgsl_isync_timeline *timeline; + struct hgsl_isync_fence *cur, *next; + + timeline = hgsl_isync_timeline_get(priv, timeline_id); + if (timeline == NULL) + return -EINVAL; + + if (!hgsl_ts_ge(ts, timeline->last_ts)) + goto out; + + spin_lock(&timeline->lock); + + list_for_each_entry_safe(cur, next, &timeline->fence_list, + child_list) { + if (hgsl_ts_ge(ts, cur->ts)) { + dma_fence_signal_locked(&cur->fence); + list_del_init(&cur->child_list); + } + } + spin_unlock(&timeline->lock); + timeline->last_ts = ts; +out: + if (timeline) + hgsl_isync_timeline_put(timeline); + return 0; +} + +static const char *hgsl_isync_get_driver_name(struct dma_fence *base) +{ + return "hgsl-isync-timeline"; +} + +static const char *hgsl_isync_get_timeline_name(struct dma_fence *base) +{ + struct hgsl_isync_fence *fence = + container_of(base, + struct hgsl_isync_fence, + fence); + + struct hgsl_isync_timeline *timeline = fence->timeline; + + return timeline->name; +} + +static bool hgsl_isync_enable_signaling(struct dma_fence *base) +{ + return true; +} + +static void hgsl_isync_fence_release(struct dma_fence *base) +{ + struct hgsl_isync_fence *fence = container_of(base, + struct hgsl_isync_fence, + fence); + + _isync_timeline_signal(fence->timeline, base); + hgsl_isync_timeline_put(fence->timeline); + + kfree(fence); +} + +static void hgsl_isync_fence_value_str(struct dma_fence *base, + char *str, int size) +{ + snprintf(str, size, "%llu", base->context); +} + +static const struct dma_fence_ops hgsl_isync_fence_ops = { + .get_driver_name = hgsl_isync_get_driver_name, + .get_timeline_name = hgsl_isync_get_timeline_name, + .enable_signaling = hgsl_isync_enable_signaling, + .wait = dma_fence_default_wait, + .release = hgsl_isync_fence_release, + + .fence_value_str = hgsl_isync_fence_value_str, +}; + diff --git a/include/uapi/linux/hgsl.h b/include/uapi/linux/hgsl.h index fda6b1aa6dd0..13f2b2602978 100644 --- a/include/uapi/linux/hgsl.h +++ b/include/uapi/linux/hgsl.h @@ -130,7 +130,7 @@ struct hgsl_ctxt_create_info { #define HGSL_IOCTL_CTXT_DESTROY HGSL_IOW(0x11, __u32) /** - * struct hgsl_wait_ts_info - wait a time step to be retired + * struct hgsl_wait_ts_info - wait a timestamp to be retired * @timestamp: The user timestamp to wait * @timeout: Expiry timeout */ @@ -144,4 +144,75 @@ struct hgsl_wait_ts_info { #define HGSL_IOCTL_WAIT_TIMESTAMP \ HGSL_IOW(0x12, struct hgsl_wait_ts_info) + +/** + * struct hgsl_hsync_fence_create - wait a h-sync fence + * @timestamp: The user timestamp attached to the fence + * @context_id; The conext to create fence + * @fence_fd: File descriptor of the new created fence + */ +struct hgsl_hsync_fence_create { + __u32 timestamp; + __u32 padding; + __s32 context_id; + __s32 fence_fd; +}; + +#define HGSL_IOCTL_HSYNC_FENCE_CREATE \ + HGSL_IOW(0x13, struct hgsl_hsync_fence_create) + +/** + * Create an i-fence timeline - param is id of the new timeline + */ +#define HGSL_IOCTL_ISYNC_TIMELINE_CREATE \ + HGSL_IOW(0x14, __u32) + +/** + * Destroy an i-fence timeline - param is id of timeline to be released + */ +#define HGSL_IOCTL_ISYNC_TIMELINE_DESTROY \ + HGSL_IOW(0x15, __u32) + +/** + * struct hgsl_isync_create_fence - wait an i-sync fence + * @timeline_id: The timestamp for the new fence + * @fence_id: id of new created fence + * @ts: option, should set it if want to use isync forward + */ +struct hgsl_isync_create_fence { + __u32 timeline_id; + __s32 fence_id; + __u32 ts; + __u32 padding; +}; +#define HGSL_IOCTL_ISYNC_FENCE_CREATE \ + HGSL_IOW(0x16, \ + struct hgsl_isync_create_fence) + +/** + * struct hgsl_isync_signal_fence - signal an i-sync fence + * @timeline_id: The timestamp for current fence + * @fence_id: id of fence to be signalled + */ +struct hgsl_isync_signal_fence { + __u32 timeline_id; + __s32 fence_id; +}; +#define HGSL_IOCTL_ISYNC_FENCE_SIGNAL \ + HGSL_IOW(0x17, \ + struct hgsl_isync_signal_fence) + + +/** + * struct hgsl_isync_signal_fence - signal an i-sync fence + * @timeline_id: The timestamp for current fence + * @fence_id: id of fence to be signalled + */ +struct hgsl_isync_forward { + __u32 timeline_id; + __s32 ts; +}; +#define HGSL_IOCTL_ISYNC_FORWARD \ + HGSL_IOW(0x18, \ + struct hgsl_isync_forward) #endif /* _UAPI_MSM_HGSL_H */