[COMMON] g2d: add request the job and schedule
authorCho KyongHo <pullip.cho@samsung.com>
Sun, 23 Apr 2017 07:34:36 +0000 (16:34 +0900)
committerSeungchul Kim <sc377.kim@samsung.com>
Mon, 28 May 2018 05:27:12 +0000 (14:27 +0900)
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 <hyesoo.yu@samsung.com>
Signed-off-by: Cho KyongHo <pullip.cho@samsung.com>
drivers/gpu/exynos/g2d/g2d.h
drivers/gpu/exynos/g2d/g2d_drv.c
drivers/gpu/exynos/g2d/g2d_task.c
drivers/gpu/exynos/g2d/g2d_task.h
drivers/gpu/exynos/g2d/g2d_uapi.h [new file with mode: 0644]

index d5e74f8b270d054142af24d346ba04c61e434ceb..1b2afe12e55a707ced45fb447a964aacc54e3919 100644 (file)
@@ -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__ */
index 49b2b6e11822d2fa06a062429745cf0155e6fd9c..8112b158bc42cb476fbffdf18ecef0ac4c677c48 100644 (file)
 #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;
 }
 
index 3b99531ae1dcf37ad72cb7dd3d4dc0e52ff86bed..3173f175292d1539cd2b2b6ecfa86dbe1a9e7b48 100644 (file)
 
 #include <linux/kernel.h>
 #include <linux/slab.h>
+#include <linux/pm_runtime.h>
 #include <linux/exynos_iovmm.h>
 
 #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);
 
index e2bcb62d31f6d5b2e032ccd00711c4f5ccb10d6f..60773c0fbc9faddbf948d72dd4c1f81cb4413272 100644 (file)
@@ -19,6 +19,7 @@
 #define __EXYNOS_G2D_TASK_H__
 
 #include <linux/dma-buf.h>
+#include <linux/workqueue.h>
 
 #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 (file)
index 0000000..e3ab7e5
--- /dev/null
@@ -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_ */
+
+