From 96df4ea52944b66f9f7d2e9cc46e7f1657aa931f Mon Sep 17 00:00:00 2001 From: Cho KyongHo Date: Sun, 23 Apr 2017 16:34:36 +0900 Subject: [PATCH] [COMMON] g2d: add request the job and schedule This patch implements the life cycle of a task to G2D. A task has a completion object to signal the user's request to let it know if its requested task is completed. All tasks are pushed to a workqueue that pushes tasks to H/W. It is required for the tasks that have fences to wait because G2D driver waits fences asynchronously Because the callback function called when a fence is signaled is invoked under irq disabled. For the simplicity, tasks without fences are also pushed to the workqueue because it does not harm the responsibility to the users seriously. Change-Id: I3c7ac1cc18212c3ea4b9a22d78a9236ee8491567 Signed-off-by: hyesoo.yu Signed-off-by: Cho KyongHo --- drivers/gpu/exynos/g2d/g2d.h | 3 + drivers/gpu/exynos/g2d/g2d_drv.c | 23 +++++++ drivers/gpu/exynos/g2d/g2d_task.c | 108 ++++++++++++++++++++++++++++++ drivers/gpu/exynos/g2d/g2d_task.h | 48 ++++++++++--- drivers/gpu/exynos/g2d/g2d_uapi.h | 28 ++++++++ 5 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 drivers/gpu/exynos/g2d/g2d_uapi.h diff --git a/drivers/gpu/exynos/g2d/g2d.h b/drivers/gpu/exynos/g2d/g2d.h index d5e74f8b270d..1b2afe12e55a 100644 --- a/drivers/gpu/exynos/g2d/g2d.h +++ b/drivers/gpu/exynos/g2d/g2d.h @@ -32,6 +32,7 @@ struct g2d_device { struct list_head tasks_free; struct list_head tasks_prepared; struct list_head tasks_active; + struct workqueue_struct *schedule_workq; }; struct g2d_context { @@ -44,4 +45,6 @@ struct g2d_context { #define g2d_job_full(id, job_mask) ((id & job_mask) == job_mask) #define g2d_job_empty(id, job_mask) ((id & job_mask) == 0) +int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task); + #endif /* __EXYNOS_G2D_H__ */ diff --git a/drivers/gpu/exynos/g2d/g2d_drv.c b/drivers/gpu/exynos/g2d/g2d_drv.c index 49b2b6e11822..8112b158bc42 100644 --- a/drivers/gpu/exynos/g2d/g2d_drv.c +++ b/drivers/gpu/exynos/g2d/g2d_drv.c @@ -26,9 +26,15 @@ #include "g2d.h" #include "g2d_regs.h" #include "g2d_task.h" +#include "g2d_uapi.h" #define MODULE_NAME "exynos-g2d" +int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task) +{ + return 0; +} + static irqreturn_t g2d_irq_handler(int irq, void *priv) { return IRQ_HANDLED; @@ -85,6 +91,23 @@ static int g2d_release(struct inode *inode, struct file *filp) static long g2d_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { + struct g2d_context *ctx = filp->private_data; + struct g2d_device *g2d_dev = ctx->g2d_dev; + + switch (cmd) { + case G2D_IOC_PROCESS: + { + struct g2d_task *task; + + task = g2d_get_free_task(g2d_dev); + if (task == NULL) + return -EBUSY; + + kref_init(&task->starter); + + g2d_start_task(task); + } + } return 0; } diff --git a/drivers/gpu/exynos/g2d/g2d_task.c b/drivers/gpu/exynos/g2d/g2d_task.c index 3b99531ae1dc..3173f175292d 100644 --- a/drivers/gpu/exynos/g2d/g2d_task.c +++ b/drivers/gpu/exynos/g2d/g2d_task.c @@ -15,11 +15,111 @@ #include #include +#include #include #include "g2d.h" #include "g2d_task.h" +static void g2d_schedule_task(struct g2d_task *task) +{ + struct g2d_device *g2d_dev = task->g2d_dev; + unsigned long flags; + int ret; + + /* + * Unconditional invocation of pm_runtime_get_sync() has no side effect + * in g2d_schedule(). It just increases the usage count of RPM if this + * function skips calling g2d_device_run(). The skip only happens when + * there is no task to run in g2d_dev->tasks_prepared. + * If pm_runtime_get_sync() enabled power, there must be a task in + * g2d_dev->tasks_prepared. + */ + ret = pm_runtime_get_sync(g2d_dev->dev); + if (ret < 0) { + dev_err(g2d_dev->dev, "Failed to enable power (%d)\n", ret); + /* TODO: cancel task */ + return; + } + + spin_lock_irqsave(&g2d_dev->lock_task, flags); + + list_add_tail(&task->node, &g2d_dev->tasks_active); + + change_task_state_prepared(task); + change_task_state_active(task); + /* + * g2d_device_run() is not reentrant while g2d_schedule() is + * reentrant g2d_device_run() should be protected with + * g2d_dev->lock_task from race. + */ + if (g2d_device_run(g2d_dev, task) < 0) { + pm_runtime_put(g2d_dev->dev); + /* TODO: cancel task */ + } + + spin_unlock_irqrestore(&g2d_dev->lock_task, flags); +} + +static void g2d_task_schedule_work(struct work_struct *work) +{ + g2d_schedule_task(container_of(work, struct g2d_task, work)); +} + +static void g2d_queuework_task(struct kref *kref) +{ + struct g2d_task *task = container_of(kref, struct g2d_task, starter); + struct g2d_device *g2d_dev = task->g2d_dev; + bool failed; + + failed = !queue_work(g2d_dev->schedule_workq, &task->work); + + BUG_ON(failed); +} + +void g2d_start_task(struct g2d_task *task) +{ + reinit_completion(&task->completion); + + kref_put(&task->starter, g2d_queuework_task); +} + +struct g2d_task *g2d_get_free_task(struct g2d_device *g2d_dev) +{ + struct g2d_task *task; + unsigned long flags; + + spin_lock_irqsave(&g2d_dev->lock_task, flags); + + if (list_empty(&g2d_dev->tasks_free)) { + spin_unlock_irqrestore(&g2d_dev->lock_task, flags); + return NULL; + } + + task = list_first_entry(&g2d_dev->tasks_free, struct g2d_task, node); + list_del_init(&task->node); + INIT_WORK(&task->work, g2d_task_schedule_work); + + init_task_state(task); + + spin_unlock_irqrestore(&g2d_dev->lock_task, flags); + + return task; +} + +void g2d_put_free_task(struct g2d_device *g2d_dev, struct g2d_task *task) +{ + unsigned long flags; + + spin_lock_irqsave(&g2d_dev->lock_task, flags); + + clear_task_state(task); + + list_add(&task->node, &g2d_dev->tasks_free); + + spin_unlock_irqrestore(&g2d_dev->lock_task, flags); +} + void g2d_destroy_tasks(struct g2d_device *g2d_dev) { struct g2d_task *task, *next; @@ -43,6 +143,8 @@ void g2d_destroy_tasks(struct g2d_device *g2d_dev) } spin_unlock_irqrestore(&g2d_dev->lock_task, flags); + + destroy_workqueue(g2d_dev->schedule_workq); } static struct g2d_task *g2d_create_task(struct g2d_device *g2d_dev) @@ -73,6 +175,8 @@ static struct g2d_task *g2d_create_task(struct g2d_device *g2d_dev) task->g2d_dev = g2d_dev; + init_completion(&task->completion); + return task; err_page: kfree(task); @@ -85,6 +189,10 @@ int g2d_create_tasks(struct g2d_device *g2d_dev) struct g2d_task *task; unsigned int i; + g2d_dev->schedule_workq = create_singlethread_workqueue("g2dscheduler"); + if (!g2d_dev->schedule_workq) + return -ENOMEM; + for (i = 0; i < G2D_MAX_JOBS; i++) { task = g2d_create_task(g2d_dev); diff --git a/drivers/gpu/exynos/g2d/g2d_task.h b/drivers/gpu/exynos/g2d/g2d_task.h index e2bcb62d31f6..60773c0fbc9f 100644 --- a/drivers/gpu/exynos/g2d/g2d_task.h +++ b/drivers/gpu/exynos/g2d/g2d_task.h @@ -19,6 +19,7 @@ #define __EXYNOS_G2D_TASK_H__ #include +#include #include "g2d_format.h" @@ -52,14 +53,14 @@ struct g2d_layer { struct g2d_buffer buffer[G2D_MAX_PLANES]; }; -#define G2D_TASKSTATE_WAITING 1 -#define G2D_TASKSTATE_UNPREPARED 2 -#define G2D_TASKSTATE_PREPARED 3 -#define G2D_TASKSTATE_ACTIVE 4 -#define G2D_TASKSTATE_PROCESSED 5 -#define G2D_TASKSTATE_ERROR 6 -#define G2D_TASKSTATE_KILLED 7 -#define G2D_TASKSTATE_TIMEOUT 8 +#define G2D_TASKSTATE_WAITING (1 << 1) +#define G2D_TASKSTATE_UNPREPARED (1 << 2) +#define G2D_TASKSTATE_PREPARED (1 << 3) +#define G2D_TASKSTATE_ACTIVE (1 << 4) +#define G2D_TASKSTATE_PROCESSED (1 << 5) +#define G2D_TASKSTATE_ERROR (1 << 6) +#define G2D_TASKSTATE_KILLED (1 << 7) +#define G2D_TASKSTATE_TIMEOUT (1 << 8) struct g2d_context; struct g2d_device; @@ -70,6 +71,8 @@ struct g2d_task { struct g2d_device *g2d_dev; unsigned int job_id; + unsigned long state; + struct kref starter; struct g2d_layer source[G2D_MAX_IMAGES]; struct g2d_layer target; @@ -81,9 +84,38 @@ struct g2d_task { unsigned int cmd_count; unsigned int priority; + + struct work_struct work; + struct completion completion; }; +/* The below macros should be called with g2d_device.lock_tasks held */ +#define change_task_state_active(task) do { \ + (task)->state &= ~G2D_TASKSTATE_PREPARED; \ + (task)->state |= G2D_TASKSTATE_ACTIVE; \ +} while (0) + +#define change_task_state_prepared(task) do { \ + (task)->state |= G2D_TASKSTATE_PREPARED; \ + (task)->state &= ~G2D_TASKSTATE_UNPREPARED; \ +} while (0) + +static inline void init_task_state(struct g2d_task *task) +{ + task->state = G2D_TASKSTATE_UNPREPARED; +} + +static inline void clear_task_state(struct g2d_task *task) +{ + task->state = 0; +} + void g2d_destroy_tasks(struct g2d_device *g2d_dev); int g2d_create_tasks(struct g2d_device *g2d_dev); +struct g2d_task *g2d_get_free_task(struct g2d_device *g2d_dev); +void g2d_put_free_task(struct g2d_device *g2d_dev, struct g2d_task *task); + +void g2d_start_task(struct g2d_task *task); + #endif /*__EXYNOS_G2D_TASK_H__*/ diff --git a/drivers/gpu/exynos/g2d/g2d_uapi.h b/drivers/gpu/exynos/g2d/g2d_uapi.h new file mode 100644 index 000000000000..e3ab7e5d9fb0 --- /dev/null +++ b/drivers/gpu/exynos/g2d/g2d_uapi.h @@ -0,0 +1,28 @@ +/* + * linux/drivers/gpu/exynos/g2d/g2d_uapi.h + * + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * + * Samsung Graphics 2D driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _G2D_UAPI_H_ +#define _G2D_UAPI_H_ + +/* + * struct g2d_task - description of a layer processing task + * @version: the version of the format of struct g2d_task_data + */ +struct g2d_task_data { + __u32 version; +}; + +#define G2D_IOC_PROCESS _IOWR('M', 4, struct g2d_task_data) + +#endif /* _G2D_UAPI_H_ */ + + -- 2.20.1