[COMMON] g2d: add debugging feature
authorhyesoo.yu <hyesoo.yu@samsung.com>
Wed, 24 May 2017 11:27:49 +0000 (20:27 +0900)
committerSeungchul Kim <sc377.kim@samsung.com>
Mon, 28 May 2018 05:27:21 +0000 (14:27 +0900)
remove the spinlock on g2d_dump_task to avoid spinlock
lockup and add the stamp at each task state for debug

Change-Id: I449dbe41d56bdbb0dc468b970e2c9102c2b46979
Signed-off-by: hyesoo.yu <hyesoo.yu@samsung.com>
drivers/gpu/exynos/g2d/Makefile
drivers/gpu/exynos/g2d/g2d.h
drivers/gpu/exynos/g2d/g2d_debug.c [new file with mode: 0644]
drivers/gpu/exynos/g2d/g2d_debug.h [new file with mode: 0644]
drivers/gpu/exynos/g2d/g2d_drv.c
drivers/gpu/exynos/g2d/g2d_fence.c
drivers/gpu/exynos/g2d/g2d_task.c
drivers/gpu/exynos/g2d/g2d_task.h
drivers/gpu/exynos/g2d/g2d_uapi_process.c

index 0cfe4f68f64ea76922895a234bb9607beb35438d..1db499152d20cb738563c8e2dc4a60d570fe0f33 100644 (file)
@@ -1,3 +1,3 @@
 obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_drv.o g2d_task.o g2d_regs.o
 obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_uapi_process.o g2d_command.o
-obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_fence.o
+obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_fence.o g2d_debug.o
index 4fdff95ae4609b8dc4cbce2e32628267dac5846e..b2856fbff56c17d01c0d8790e56820fb33bac63c 100644 (file)
@@ -50,6 +50,10 @@ struct g2d_device {
 
        struct notifier_block   pm_notifier;
        wait_queue_head_t       freeze_wait;
+
+       struct dentry *debug_root;
+       struct dentry *debug;
+       struct dentry *debug_logs;
 };
 
 struct g2d_context {
diff --git a/drivers/gpu/exynos/g2d/g2d_debug.c b/drivers/gpu/exynos/g2d/g2d_debug.c
new file mode 100644 (file)
index 0000000..aea9c9b
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * linux/drivers/gpu/exynos/g2d/g2d_debug.c
+ *
+ * Copyright (C) 2017 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+
+#include "g2d.h"
+#include "g2d_task.h"
+#include "g2d_uapi.h"
+#include "g2d_debug.h"
+
+static unsigned int g2d_debug;
+
+#define G2D_MAX_STAMP_SIZE 1024
+
+static struct g2d_stamp {
+       ktime_t time;
+       struct g2d_task *task;
+       u32 state;
+       u32 job_id;
+       u32 val;
+       u8 cpu;
+} g2d_stamp_list[G2D_MAX_STAMP_SIZE];
+
+static atomic_t p_stamp;
+
+static int g2d_stamp_show(struct seq_file *s, void *unused)
+{
+       int ptr = atomic_read(&p_stamp);
+       struct g2d_stamp *stamp;
+       int i;
+
+       if (ptr < 0)
+               return 0;
+
+       /* in chronological order */
+       ptr = (ptr + 1) & (G2D_MAX_STAMP_SIZE - 1);
+       i = ptr;
+
+       while (1) {
+               stamp = &g2d_stamp_list[i];
+
+               seq_printf(s, "[%d] %u:%u@%u (0x%x) %06llu\n", i++,
+                       stamp->cpu, stamp->job_id, stamp->val, stamp->state,
+                       ktime_to_us(stamp->time));
+
+               i &= (G2D_MAX_STAMP_SIZE - 1);
+
+               if (i == ptr)
+                       break;
+       }
+
+       return 0;
+}
+
+static int g2d_debug_logs_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, g2d_stamp_show, inode->i_private);
+}
+
+static const struct file_operations g2d_debug_logs_fops = {
+       .open = g2d_debug_logs_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+void g2d_init_debug(struct g2d_device *g2d_dev)
+{
+       atomic_set(&p_stamp, -1);
+
+       g2d_dev->debug_root = debugfs_create_dir("g2d", NULL);
+       if (!g2d_dev->debug_root) {
+               dev_err(g2d_dev->dev, "debugfs : failed to create root directory\n");
+               return;
+       }
+
+       g2d_dev->debug = debugfs_create_u32("debug",
+                                       0644, g2d_dev->debug_root, &g2d_debug);
+       if (!g2d_dev->debug) {
+               dev_err(g2d_dev->dev, "debugfs : failed to create debug file\n");
+               return;
+       }
+
+       g2d_dev->debug_logs = debugfs_create_file("logs",
+                                       0444, g2d_dev->debug_root, g2d_dev, &g2d_debug_logs_fops);
+       if (!g2d_dev->debug_logs) {
+               dev_err(g2d_dev->dev, "debugfs : failed to create debug logs file\n");
+               return;
+       }
+}
+
+void g2d_destroy_debug(struct g2d_device *g2d_dev)
+{
+       debugfs_remove_recursive(g2d_dev->debug_root);
+}
+
+static struct regs_info g2d_reg_info[] = {
+               /* Start, Size, Name */
+               { 0x0,          0x20,   "General" },
+               { 0x34,         0x10,   "Secure Layer" },
+               { 0xF0,         0x10,   "AFBC debugging" },
+               { 0x80,         0x70,   "Job manager" },
+               { 0x8000,       0x100,  "HW flow control" },
+               { 0x2000,       0x120,  "Layer CSC Coefficient" },
+               { 0x120,        0xE0,   "Destination" },
+               { 0x200,        0x100,  "Layer0" },
+               { 0x300,        0x100,  "Layer1" },
+               { 0x400,        0x100,  "Layer2" },
+               { 0x500,        0x100,  "Layer3" },
+               { 0x600,        0x100,  "Layer4" },
+               { 0x700,        0x100,  "Layer5" },
+               { 0x800,        0x100,  "Layer6" },
+               { 0x900,        0x100,  "Layer7" },
+               { 0xA00,        0x100,  "Layer8" },
+               { 0xB00,        0x100,  "Layer9" },
+               { 0xC00,        0x100,  "Layer10" },
+               { 0xD00,        0x100,  "Layer11" },
+               { 0xE00,        0x100,  "Layer12" },
+               { 0xF00,        0x100,  "Layer13" },
+               { 0x1000,       0x100,  "Layer14" },
+               { 0x1100,       0x100,  "Layer15" },
+};
+
+static void g2d_dump_task(struct g2d_task *task)
+{
+       struct g2d_device *g2d_dev = task->g2d_dev;
+       unsigned int i, num_array;
+       struct g2d_reg *regs;
+
+       num_array = ARRAY_SIZE(g2d_reg_info) - G2D_MAX_IMAGES + task->num_source;
+
+       for (i = 0; i < num_array; i++) {
+               pr_info("[%s: %04X .. %04X]\n",
+                       g2d_reg_info[i].name, g2d_reg_info[i].start,
+                       g2d_reg_info[i].start + g2d_reg_info[i].size);
+               print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
+                       g2d_dev->reg + g2d_reg_info[i].start,
+                       g2d_reg_info[i].size, false);
+       }
+
+       regs = page_address(task->cmd_page);
+
+       for (i = 0; i < task->cmd_count; i++)
+               pr_info("G2D: CMD[%03d] %#06x, %#010x\n",
+                       i, regs[i].offset, regs[i].value);
+}
+
+void g2d_stamp_task(struct g2d_task *task, u32 val)
+{
+       int ptr = atomic_inc_return(&p_stamp) & (G2D_MAX_STAMP_SIZE - 1);
+       struct g2d_stamp *stamp = &g2d_stamp_list[ptr];
+
+       if (task) {
+               stamp->state = task->state;
+               stamp->job_id = task->job_id;
+               stamp->task = task;
+       } else {
+               stamp->task = NULL;
+       }
+
+       stamp->time = ktime_get();
+       stamp->val = val;
+       stamp->cpu = smp_processor_id();
+
+       /* when error status, dump the task */
+       if ((stamp->val == G2D_STAMP_STATE_TIMEOUT_HW) ||
+               (stamp->val == G2D_STAMP_STATE_ERR_INT) ||
+               (stamp->val == G2D_STAMP_STATE_MMUFAULT))
+               g2d_dump_task(task);
+
+       if (stamp->val == G2D_STAMP_STATE_DONE) {
+               if (g2d_debug == 1) {
+                       pr_info("Job #%x took %06llu to H/W process\n",
+                       task->job_id, ktime_us_delta(task->ktime_end, task->ktime_begin));
+               } else if (g2d_debug == 2) {
+                       g2d_dump_task(task);
+               }
+       }
+}
diff --git a/drivers/gpu/exynos/g2d/g2d_debug.h b/drivers/gpu/exynos/g2d/g2d_debug.h
new file mode 100644 (file)
index 0000000..17a7680
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * linux/drivers/gpu/exynos/g2d/g2d_debug.h
+ *
+ * Copyright (C) 2017 Samsung Electronics Co., Ltd.
+ *
+ * Contact: Hyesoo Yu <hyesoo.yu@samsung.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef __EXYNOS_G2D_DEBUG_H_
+#define __EXYNOS_G2D_DEBUG_H_
+
+struct regs_info {
+       int start;
+       int size;
+       const char *name;
+};
+
+enum g2d_stamp_id {
+       G2D_STAMP_STATE_ALLOC,
+       G2D_STAMP_STATE_BEGIN,
+       G2D_STAMP_STATE_PM_RESUME,
+       G2D_STAMP_STATE_PUSH,
+       G2D_STAMP_STATE_DONE,
+       G2D_STAMP_STATE_PM_SUSPEND,
+       G2D_STAMP_STATE_FREE,
+       G2D_STAMP_STATE_TIMEOUT_FENCE,
+       G2D_STAMP_STATE_TIMEOUT_HW,
+       G2D_STAMP_STATE_ERR_INT,
+       G2D_STAMP_STATE_MMUFAULT,
+       G2D_STAMP_STATE_SHUTDOWN_S,
+       G2D_STAMP_STATE_SHUTDOWN_E,
+       G2D_STAMP_STATE_SUSPEND_S,
+       G2D_STAMP_STATE_SUSPEND_E,
+       G2D_STAMP_STATE_RESUME_S,
+       G2D_STAMP_STATE_RESUME_E,
+};
+
+void g2d_init_debug(struct g2d_device *dev);
+void g2d_destroy_debug(struct g2d_device *dev);
+void g2d_stamp_task(struct g2d_task *task, u32 val);
+#endif /* __EXYNOS_G2D_HELPER_H_ */
index 0d1afbd578fc959a4afba2a69f8fd011d4785ce6..4b5ad3333559954a93fbd7a178f0bac81dd94a6d 100644 (file)
@@ -29,6 +29,7 @@
 #include "g2d_regs.h"
 #include "g2d_task.h"
 #include "g2d_uapi_process.h"
+#include "g2d_debug.h"
 
 #define MODULE_NAME "exynos-g2d"
 
@@ -81,6 +82,8 @@ void g2d_hw_timeout_handler(unsigned long arg)
                /* Time out is not caused by this task */
                goto out;
 
+       g2d_stamp_task(task, G2D_STAMP_STATE_TIMEOUT_HW);
+
        mark_task_state_killed(task);
 
        g2d_hw_kill_task(g2d_dev, task->job_id);
@@ -93,6 +96,8 @@ int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task)
 {
        g2d_hw_push_task(g2d_dev, task);
 
+       g2d_stamp_task(task, G2D_STAMP_STATE_PUSH);
+
        return 0;
 }
 
@@ -133,7 +138,7 @@ static irqreturn_t g2d_irq_handler(int irq, void *priv)
                                "%s: Error occurred during running job %d\n",
                                __func__, job_id);
 
-                       g2d_dump_task(g2d_dev, job_id);
+                       g2d_stamp_task(task, G2D_STAMP_STATE_ERR_INT);
                }
 
                g2d_flush_all_tasks(g2d_dev);
@@ -156,9 +161,15 @@ static int g2d_iommu_fault_handler(struct iommu_domain *domain,
                                int fault_flags, void *token)
 {
        struct g2d_device *g2d_dev = token;
+       struct g2d_task *task;
        int job_id = g2d_hw_get_current_task(g2d_dev);
+       unsigned long flags;
+
+       spin_lock_irqsave(&g2d_dev->lock_task, flags);
+       task = g2d_get_active_task_from_id(g2d_dev, job_id);
+       spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
 
-       g2d_dump_task(g2d_dev, job_id);
+       g2d_stamp_task(task, G2D_STAMP_STATE_MMUFAULT);
 
        return 0;
 }
@@ -248,6 +259,8 @@ static long g2d_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                        break;
                }
 
+               g2d_stamp_task(task, G2D_STAMP_STATE_BEGIN);
+
                g2d_start_task(task);
 
                if (!(task->flags & G2D_FLAG_NONBLOCK))
@@ -384,6 +397,8 @@ static int g2d_probe(struct platform_device *pdev)
 
        dev_info(&pdev->dev, "Probed FIMG2D version %#010x\n", version);
 
+       g2d_init_debug(g2d_dev);
+
        return 0;
 err_pm:
        g2d_destroy_tasks(g2d_dev);
@@ -402,18 +417,23 @@ static void g2d_shutdown(struct platform_device *pdev)
 {
        struct g2d_device *g2d_dev = platform_get_drvdata(pdev);
 
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_SHUTDOWN_S);
        g2d_prepare_suspend(g2d_dev);
 
        wait_event(g2d_dev->freeze_wait, list_empty(&g2d_dev->tasks_active));
 
        if (test_and_set_bit(G2D_DEVICE_STATE_IOVMM_DISABLED, &g2d_dev->state))
                iovmm_deactivate(g2d_dev->dev);
+
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_SHUTDOWN_E);
 }
 
 static int g2d_remove(struct platform_device *pdev)
 {
        struct g2d_device *g2d_dev = platform_get_drvdata(pdev);
 
+       g2d_destroy_debug(g2d_dev);
+
        g2d_shutdown(pdev);
 
        g2d_destroy_tasks(g2d_dev);
@@ -428,6 +448,8 @@ static int g2d_remove(struct platform_device *pdev)
 #ifdef CONFIG_PM
 static int g2d_runtime_resume(struct device *dev)
 {
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_PM_RESUME);
+
        return 0;
 }
 
@@ -436,6 +458,7 @@ static int g2d_runtime_suspend(struct device *dev)
        struct g2d_device *g2d_dev = dev_get_drvdata(dev);
 
        clk_unprepare(g2d_dev->clock);
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_PM_SUSPEND);
 
        return 0;
 }
index 3a021482d29e54f5752b934aadef64e3179d7267..44c29f6e5e9261bbd433802dfdd31a84ad5fb4be 100644 (file)
@@ -23,6 +23,7 @@
 #include "g2d_uapi.h"
 #include "g2d_task.h"
 #include "g2d_fence.h"
+#include "g2d_debug.h"
 
 void g2d_fence_timeout_handler(unsigned long arg)
 {
@@ -106,6 +107,8 @@ void g2d_fence_timeout_handler(unsigned long arg)
        g2d_queuework_task(&task->starter);
 
        spin_unlock_irqrestore(&task->fence_timeout_lock, flags);
+
+       g2d_stamp_task(task, G2D_STAMP_STATE_TIMEOUT_FENCE);
 };
 
 static const char *g2d_fence_get_driver_name(struct dma_fence *fence)
index 8b6c0faaa4611b55ebcece3af5dd1099a1cdeef9..f26852328bf047bfdf818a6e41cc22da8672861f 100644 (file)
@@ -23,6 +23,7 @@
 #include "g2d_uapi_process.h"
 #include "g2d_command.h"
 #include "g2d_fence.h"
+#include "g2d_debug.h"
 
 #ifdef CONFIG_EXYNOS_CONTENT_PATH_PROTECTION
 #include <linux/smc.h>
@@ -146,6 +147,8 @@ static void g2d_finish_task(struct g2d_device *g2d_dev,
 {
        list_del_init(&task->node);
 
+       g2d_stamp_task(task, G2D_STAMP_STATE_DONE);
+
        del_timer(&task->timer);
 
        g2d_secure_disable();
@@ -226,7 +229,9 @@ void g2d_prepare_suspend(struct g2d_device *g2d_dev)
        set_bit(G2D_DEVICE_STATE_SUSPEND, &g2d_dev->state);
        spin_unlock_irq(&g2d_dev->lock_task);
 
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_SUSPEND_S);
        wait_event(g2d_dev->freeze_wait, list_empty(&g2d_dev->tasks_active));
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_SUSPEND_E);
 }
 
 void g2d_suspend_finish(struct g2d_device *g2d_dev)
@@ -235,6 +240,7 @@ void g2d_suspend_finish(struct g2d_device *g2d_dev)
 
        spin_lock_irq(&g2d_dev->lock_task);
 
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_RESUME_S);
        clear_bit(G2D_DEVICE_STATE_SUSPEND, &g2d_dev->state);
 
        while (!list_empty(&g2d_dev->tasks_prepared)) {
@@ -245,6 +251,7 @@ void g2d_suspend_finish(struct g2d_device *g2d_dev)
        }
 
        spin_unlock_irq(&g2d_dev->lock_task);
+       g2d_stamp_task(NULL, G2D_STAMP_STATE_RESUME_E);
 }
 
 static void g2d_schedule_task(struct g2d_task *task)
@@ -359,6 +366,8 @@ struct g2d_task *g2d_get_free_task(struct g2d_device *g2d_dev)
 
        g2d_init_commands(task);
 
+       g2d_stamp_task(task, G2D_STAMP_STATE_ALLOC);
+
        spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
 
        return task;
@@ -374,6 +383,8 @@ void g2d_put_free_task(struct g2d_device *g2d_dev, struct g2d_task *task)
 
        list_add(&task->node, &g2d_dev->tasks_free);
 
+       g2d_stamp_task(task, G2D_STAMP_STATE_FREE);
+
        spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
 }
 
@@ -469,28 +480,3 @@ int g2d_create_tasks(struct g2d_device *g2d_dev)
 
        return 0;
 }
-
-void g2d_dump_task(struct g2d_device *g2d_dev, unsigned int job_id)
-{
-       struct g2d_task *task;
-       unsigned long flags;
-       unsigned int i;
-       struct g2d_reg *regs;
-
-       spin_lock_irqsave(&g2d_dev->lock_task, flags);
-
-       list_for_each_entry(task, &g2d_dev->tasks_active, node) {
-               if (task->job_id == job_id)
-                       break;
-       }
-
-       /* TODO: more dump task */
-
-       regs = page_address(task->cmd_page);
-
-       for (i = 0; i < task->cmd_count; i++)
-               pr_info("G2D: CMD[%03d] %#06x, %#010x\n",
-                       i, regs[i].offset, regs[i].value);
-
-       spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
-}
index b8139febc2f53bde0bee7945d57d8291bebaebca..18d28cd2844070e4aa1d3c78f040b55993dd6f63 100644 (file)
@@ -184,7 +184,6 @@ void g2d_suspend_finish(struct g2d_device *g2d_dev);
 
 void g2d_fence_callback(struct dma_fence *fence, struct dma_fence_cb *cb);
 
-void g2d_dump_task(struct g2d_device *g2d_dev, unsigned int job_id);
 void g2d_queuework_task(struct kref *kref);
 
 #endif /*__EXYNOS_G2D_TASK_H__*/
index 67b86baf2f19751bf6db52dc6ad2761d05a9b5b1..26e1e29ea5c21e279ecb3bea38b739b6dbaf12c1 100644 (file)
@@ -32,7 +32,6 @@
 #include "g2d_command.h"
 #include "g2d_fence.h"
 
-
 unsigned int get_layer_payload(struct g2d_layer *layer)
 {
        unsigned int payload = 0;