[COMMON] g2d: cancel jobs running too much time
authorCho KyongHo <pullip.cho@samsung.com>
Sun, 23 Apr 2017 12:26:32 +0000 (21:26 +0900)
committerSeungchul Kim <sc377.kim@samsung.com>
Mon, 28 May 2018 05:27:13 +0000 (14:27 +0900)
We might face with an unexpected accident that G2D does not complete a
pushed job in a definite time. We should be able to identify that jobs
and to discard them to make the H/W predictable.
We decided 500 milliseconds for the dead-line of the completion of a
job running in the H/W. If H/W does not complete a job in 500 msec.,
The driver kills the job running in H/W and check if it is really
killed. If it is not killed in the next 500 msec., the driver
initializes H/W which means that all the jobs running in H/W are
discarded.

Change-Id: I8119a476bb2e736380031f8277d04caf16baf2ba
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_regs.c
drivers/gpu/exynos/g2d/g2d_regs.h
drivers/gpu/exynos/g2d/g2d_task.c
drivers/gpu/exynos/g2d/g2d_task.h

index 1b2afe12e55a707ced45fb447a964aacc54e3919..38bd33b23e7d24a5ca8644a0181a34bcdca1f0d2 100644 (file)
@@ -46,5 +46,6 @@ struct g2d_context {
 #define g2d_job_empty(id, job_mask) ((id & job_mask) == 0)
 
 int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task);
+void g2d_hw_timeout_handler(unsigned long arg);
 
 #endif /* __EXYNOS_G2D_H__ */
index 41a2496f084d734b30d2e72f02b97686bac0965c..99325b88dac23fb4cf86acab6ff261f78c6e0e7a 100644 (file)
 
 #define MODULE_NAME "exynos-g2d"
 
+void g2d_hw_timeout_handler(unsigned long arg)
+{
+       struct g2d_task *task = (struct g2d_task *)arg;
+       struct g2d_device *g2d_dev = task->g2d_dev;
+       unsigned long flags;
+       u32 job_state;
+
+       /* TODO: Dump of internal state of G2D */
+
+       dev_err(g2d_dev->dev, "%s: Time is up: %d msec for job %d\n",
+               __func__, G2D_HW_TIMEOUT_MSEC, task->job_id);
+
+       spin_lock_irqsave(&g2d_dev->lock_task, flags);
+
+       if (!is_task_state_active(task))
+               /*
+                * The task timed out is not currently running in H/W.
+                * It might be just finished by interrupt.
+                */
+               goto out;
+
+       job_state = g2d_hw_get_job_state(g2d_dev, task->job_id);
+       if (job_state == G2D_JOB_STATE_DONE)
+               /*
+                * The task timed out is not currently running in H/W.
+                * It will be processed in the interrupt handler.
+                */
+               goto out;
+
+       if (is_task_state_killed(task)) {
+               /* The killed task is not died in the time out priod. */
+               g2d_hw_global_reset(g2d_dev);
+
+               g2d_flush_all_tasks(g2d_dev);
+
+               dev_err(g2d_dev->dev,
+                       "GLOBAL RESET: killed task not dead in %d msec.\n",
+                       G2D_HW_TIMEOUT_MSEC);
+               goto out;
+       }
+
+       mod_timer(&task->hw_timer,
+         jiffies + msecs_to_jiffies(G2D_HW_TIMEOUT_MSEC));
+
+       if (job_state != G2D_JOB_STATE_RUNNING)
+               /* G2D_JOB_STATE_QUEUEING or G2D_JOB_STATE_SUSPENDING */
+               /* Time out is not caused by this task */
+               goto out;
+
+       mark_task_state_killed(task);
+
+       g2d_hw_kill_task(g2d_dev, task->job_id);
+
+out:
+       spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
+}
+
 int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task)
 {
        g2d_hw_push_task(g2d_dev, task);
index 504fc20d2ea2f48cc3da14aa85dfc080765db70d..c33104ffd8b1c3647c63c2cfad09f1ae95810e69 100644 (file)
@@ -84,3 +84,20 @@ int g2d_hw_get_current_task(struct g2d_device *g2d_dev)
 
        return -1;
 }
+
+void g2d_hw_kill_task(struct g2d_device *g2d_dev, unsigned int job_id)
+{
+       int retry_count = 120;
+
+       writel((0 << 4) | job_id, g2d_dev->reg + G2D_JOB_KILL_REG);
+
+       while (retry_count-- > 0) {
+               if (!(readl(g2d_dev->reg + G2D_JOB_PUSHKILL_STATE_REG) & 0x2)) {
+                       dev_err(g2d_dev->dev,
+                               "%s: Killed JOB %d\n", __func__, job_id);
+                       return;
+               }
+       }
+
+       dev_err(g2d_dev->dev, "%s: Failed to kill job %d\n", __func__, job_id);
+}
index 43d62c2808942ed24c5b52123f04a65840c1dc26..9f9ff6a484079863a5b56859d8338a0b90ea2446 100644 (file)
@@ -76,6 +76,7 @@
 
 void g2d_hw_push_task(struct g2d_device *g2d_dev, struct g2d_task *task);
 int g2d_hw_get_current_task(struct g2d_device *g2d_dev);
+void g2d_hw_kill_task(struct g2d_device *g2d_dev, unsigned int job_id);
 
 static inline u32 g2d_hw_finished_job_ids(struct g2d_device *g2d_dev)
 {
index cce2d86c3faf9e6728eec524343fdcc1c199e5df..6fb3462b8263b94ec914b0af262e41802d0baa65 100644 (file)
@@ -51,6 +51,8 @@ static void g2d_finish_task(struct g2d_device *g2d_dev,
 {
        list_del_init(&task->node);
 
+       del_timer(&task->hw_timer);
+
        clk_disable(g2d_dev->clock);
 
        pm_runtime_put(g2d_dev->dev);
@@ -67,6 +69,12 @@ void g2d_finish_task_with_id(struct g2d_device *g2d_dev,
        if (!task)
                return;
 
+       if (is_task_state_killed(task)) {
+               dev_err(g2d_dev->dev, "%s: Killed task ID %d is completed\n",
+                       __func__, job_id);
+               success = false;
+       }
+
        task->ktime_end = ktime_get();
 
        g2d_finish_task(g2d_dev, task, success);
@@ -137,6 +145,9 @@ static void g2d_schedule_task(struct g2d_task *task)
                goto err_run;
        }
 
+       mod_timer(&task->hw_timer,
+                 jiffies + msecs_to_jiffies(G2D_HW_TIMEOUT_MSEC));
+
        spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
        return;
 err_run:
@@ -249,6 +260,9 @@ static struct g2d_task *g2d_create_task(struct g2d_device *g2d_dev)
        if (!task->cmd_page)
                goto err_page;
 
+       setup_timer(&task->hw_timer,
+                   g2d_hw_timeout_handler, (unsigned long)task);
+
        /* mapping the command data */
        sg_init_table(&sgl, 1);
        sg_set_page(&sgl, task->cmd_page, G2D_CMD_LIST_SIZE, 0);
index 6fb09569485e462084011b618995e73524a5941a..b65459f7f00bcfb9ca1aeca8b7f5078fc7682dbf 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/ktime.h>
 #include <linux/dma-buf.h>
 #include <linux/workqueue.h>
+#include <linux/timer.h>
 
 #include "g2d_format.h"
 
@@ -74,6 +75,7 @@ struct g2d_task {
        unsigned int            job_id;
        unsigned long           state;
        struct kref             starter;
+       struct timer_list       hw_timer;
 
        struct g2d_layer        source[G2D_MAX_IMAGES];
        struct g2d_layer        target;
@@ -130,6 +132,11 @@ static inline void clear_task_state(struct g2d_task *task)
        task->state = 0;
 }
 
+#define is_task_state_active(task) (((task)->state & G2D_TASKSTATE_ACTIVE) != 0)
+#define is_task_state_killed(task) (((task)->state & G2D_TASKSTATE_KILLED) != 0)
+
+#define G2D_HW_TIMEOUT_MSEC    500
+
 struct g2d_task *g2d_get_active_task_from_id(struct g2d_device *g2d_dev,
                                             unsigned int id);
 void g2d_destroy_tasks(struct g2d_device *g2d_dev);