Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee035711 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "soc: qcom: hgsl: Add fence support"

parents 9d9e1bf7 2387acc4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
# 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
+208 −41
Original line number Diff line number Diff line
@@ -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];
	if (ctxt == NULL) {
	read_unlock(&hgsl->ctxt_lock);
	if (ctxt == NULL) {
		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;
	}
+135 −0
Original line number Diff line number Diff line
/* 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_ */
+526 −0
Original line number Diff line number Diff line
// 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,
};
+72 −1

File changed.

Preview size limit exceeded, changes collapsed.