Commit 1d220435 authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Vinod Koul
Browse files

dmaengine: xilinx: dpdma: Add debugfs support



Expose statistics to debugfs when available. This helps debugging issues
with the DPDMA driver.

Signed-off-by: default avatarHyun Kwon <hyun.kwon@xilinx.com>
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://lore.kernel.org/r/20200812171228.9751-1-laurent.pinchart@ideasonboard.com


Signed-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent 9c857a8e
Loading
Loading
Loading
Loading
+209 −0
Original line number Diff line number Diff line
@@ -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>
@@ -266,6 +267,210 @@ struct xilinx_dpdma_device {
	bool ext_addr;
};

/* -----------------------------------------------------------------------------
 * 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;