dmaengine: xilinx: dpdma: Add debugfs support
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Wed, 12 Aug 2020 17:12:28 +0000 (20:12 +0300)
committerVinod Koul <vkoul@kernel.org>
Mon, 17 Aug 2020 05:50:49 +0000 (11:20 +0530)
Expose statistics to debugfs when available. This helps debugging issues
with the DPDMA driver.

Signed-off-by: Hyun Kwon <hyun.kwon@xilinx.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://lore.kernel.org/r/20200812171228.9751-1-laurent.pinchart@ideasonboard.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/dma/xilinx/xilinx_dpdma.c

index b37197c..7db70d2 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/bitfield.h>
 #include <linux/bits.h>
 #include <linux/clk.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/dmaengine.h>
 #include <linux/dmapool.h>
@@ -267,6 +268,210 @@ struct xilinx_dpdma_device {
 };
 
 /* -----------------------------------------------------------------------------
+ * DebugFS
+ */
+
+#ifdef CONFIG_DEBUG_FS
+
+#define XILINX_DPDMA_DEBUGFS_READ_MAX_SIZE     32
+#define XILINX_DPDMA_DEBUGFS_UINT16_MAX_STR    "65535"
+
+/* Match xilinx_dpdma_testcases vs dpdma_debugfs_reqs[] entry */
+enum xilinx_dpdma_testcases {
+       DPDMA_TC_INTR_DONE,
+       DPDMA_TC_NONE
+};
+
+struct xilinx_dpdma_debugfs {
+       enum xilinx_dpdma_testcases testcase;
+       u16 xilinx_dpdma_irq_done_count;
+       unsigned int chan_id;
+};
+
+static struct xilinx_dpdma_debugfs dpdma_debugfs;
+struct xilinx_dpdma_debugfs_request {
+       const char *name;
+       enum xilinx_dpdma_testcases tc;
+       ssize_t (*read)(char *buf);
+       int (*write)(char *args);
+};
+
+static void xilinx_dpdma_debugfs_desc_done_irq(struct xilinx_dpdma_chan *chan)
+{
+       if (chan->id == dpdma_debugfs.chan_id)
+               dpdma_debugfs.xilinx_dpdma_irq_done_count++;
+}
+
+static ssize_t xilinx_dpdma_debugfs_desc_done_irq_read(char *buf)
+{
+       size_t out_str_len;
+
+       dpdma_debugfs.testcase = DPDMA_TC_NONE;
+
+       out_str_len = strlen(XILINX_DPDMA_DEBUGFS_UINT16_MAX_STR);
+       out_str_len = min_t(size_t, XILINX_DPDMA_DEBUGFS_READ_MAX_SIZE,
+                           out_str_len);
+       snprintf(buf, out_str_len, "%d",
+                dpdma_debugfs.xilinx_dpdma_irq_done_count);
+
+       return 0;
+}
+
+static int xilinx_dpdma_debugfs_desc_done_irq_write(char *args)
+{
+       char *arg;
+       int ret;
+       u32 id;
+
+       arg = strsep(&args, " ");
+       if (!arg || strncasecmp(arg, "start", 5))
+               return -EINVAL;
+
+       arg = strsep(&args, " ");
+       if (!arg)
+               return -EINVAL;
+
+       ret = kstrtou32(arg, 0, &id);
+       if (ret < 0)
+               return ret;
+
+       if (id < ZYNQMP_DPDMA_VIDEO0 || id > ZYNQMP_DPDMA_AUDIO1)
+               return -EINVAL;
+
+       dpdma_debugfs.testcase = DPDMA_TC_INTR_DONE;
+       dpdma_debugfs.xilinx_dpdma_irq_done_count = 0;
+       dpdma_debugfs.chan_id = id;
+
+       return 0;
+}
+
+/* Match xilinx_dpdma_testcases vs dpdma_debugfs_reqs[] entry */
+struct xilinx_dpdma_debugfs_request dpdma_debugfs_reqs[] = {
+       {
+               .name = "DESCRIPTOR_DONE_INTR",
+               .tc = DPDMA_TC_INTR_DONE,
+               .read = xilinx_dpdma_debugfs_desc_done_irq_read,
+               .write = xilinx_dpdma_debugfs_desc_done_irq_write,
+       },
+};
+
+static ssize_t xilinx_dpdma_debugfs_read(struct file *f, char __user *buf,
+                                        size_t size, loff_t *pos)
+{
+       enum xilinx_dpdma_testcases testcase;
+       char *kern_buff;
+       int ret = 0;
+
+       if (*pos != 0 || size <= 0)
+               return -EINVAL;
+
+       kern_buff = kzalloc(XILINX_DPDMA_DEBUGFS_READ_MAX_SIZE, GFP_KERNEL);
+       if (!kern_buff) {
+               dpdma_debugfs.testcase = DPDMA_TC_NONE;
+               return -ENOMEM;
+       }
+
+       testcase = READ_ONCE(dpdma_debugfs.testcase);
+       if (testcase != DPDMA_TC_NONE) {
+               ret = dpdma_debugfs_reqs[testcase].read(kern_buff);
+               if (ret < 0)
+                       goto done;
+       } else {
+               strlcpy(kern_buff, "No testcase executed",
+                       XILINX_DPDMA_DEBUGFS_READ_MAX_SIZE);
+       }
+
+       size = min(size, strlen(kern_buff));
+       if (copy_to_user(buf, kern_buff, size))
+               ret = -EFAULT;
+
+done:
+       kfree(kern_buff);
+       if (ret)
+               return ret;
+
+       *pos = size + 1;
+       return size;
+}
+
+static ssize_t xilinx_dpdma_debugfs_write(struct file *f,
+                                         const char __user *buf, size_t size,
+                                         loff_t *pos)
+{
+       char *kern_buff, *kern_buff_start;
+       char *testcase;
+       unsigned int i;
+       int ret;
+
+       if (*pos != 0 || size <= 0)
+               return -EINVAL;
+
+       /* Supporting single instance of test as of now. */
+       if (dpdma_debugfs.testcase != DPDMA_TC_NONE)
+               return -EBUSY;
+
+       kern_buff = kzalloc(size, GFP_KERNEL);
+       if (!kern_buff)
+               return -ENOMEM;
+       kern_buff_start = kern_buff;
+
+       ret = strncpy_from_user(kern_buff, buf, size);
+       if (ret < 0)
+               goto done;
+
+       /* Read the testcase name from a user request. */
+       testcase = strsep(&kern_buff, " ");
+
+       for (i = 0; i < ARRAY_SIZE(dpdma_debugfs_reqs); i++) {
+               if (!strcasecmp(testcase, dpdma_debugfs_reqs[i].name))
+                       break;
+       }
+
+       if (i == ARRAY_SIZE(dpdma_debugfs_reqs)) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       ret = dpdma_debugfs_reqs[i].write(kern_buff);
+       if (ret < 0)
+               goto done;
+
+       ret = size;
+
+done:
+       kfree(kern_buff_start);
+       return ret;
+}
+
+static const struct file_operations fops_xilinx_dpdma_dbgfs = {
+       .owner = THIS_MODULE,
+       .read = xilinx_dpdma_debugfs_read,
+       .write = xilinx_dpdma_debugfs_write,
+};
+
+static void xilinx_dpdma_debugfs_init(struct xilinx_dpdma_device *xdev)
+{
+       struct dentry *dent;
+
+       dpdma_debugfs.testcase = DPDMA_TC_NONE;
+
+       dent = debugfs_create_file("testcase", 0444, xdev->common.dbg_dev_root,
+                                  NULL, &fops_xilinx_dpdma_dbgfs);
+       if (IS_ERR(dent))
+               dev_err(xdev->dev, "Failed to create debugfs testcase file\n");
+}
+
+#else
+static void xilinx_dpdma_debugfs_init(struct xilinx_dpdma_device *xdev)
+{
+}
+
+static void xilinx_dpdma_debugfs_desc_done_irq(struct xilinx_dpdma_chan *chan)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+/* -----------------------------------------------------------------------------
  * I/O Accessors
  */
 
@@ -842,6 +1047,8 @@ static void xilinx_dpdma_chan_done_irq(struct xilinx_dpdma_chan *chan)
 
        spin_lock_irqsave(&chan->lock, flags);
 
+       xilinx_dpdma_debugfs_desc_done_irq(chan);
+
        if (active)
                vchan_cyclic_callback(&active->vdesc);
        else
@@ -1477,6 +1684,8 @@ static int xilinx_dpdma_probe(struct platform_device *pdev)
 
        xilinx_dpdma_enable_irq(xdev);
 
+       xilinx_dpdma_debugfs_init(xdev);
+
        dev_info(&pdev->dev, "Xilinx DPDMA engine is probed\n");
 
        return 0;