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 <donglil@codeaurora.org>
This commit is contained in:
Kasin Li 2020-07-13 21:31:43 +08:00 • committed by Gerrit - the friendly Code Review server
commit 2387acc46e
5 changed files with 942 additions and 43 deletions

View file

@ -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

View file

@ -23,11 +23,11 @@
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/regmap.h>
#include <linux/uaccess.h>
#include <uapi/linux/hgsl.h>
#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(&param, 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), &param, 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, &param);
if (ret == 0)
copy_to_user(USRPTR(arg), &param, 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(&param, 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(&param, 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), &param, 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(&param, 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(&param, 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;
}

View file

@ -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 <linux/types.h>
#include <linux/dma-buf.h>
#include <linux/spinlock.h>
#include <linux/sync_file.h>
#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_ */

View file

@ -0,0 +1,526 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/types.h>
#include <linux/dma-buf.h>
#include <linux/dma-fence.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/regmap.h>
#include <linux/uaccess.h>
#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,
};

View file

@ -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 */