All of lore.kernel.org
 help / color / mirror / Atom feed
From: Bin Gao <bin.gao@linux.intel.com>
To: Arnd Bergmann <arnd@arndb.de>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	linux-kernel@vger.kernel.org
Subject: [PATCH 3/4] drivers/misc: add rawio iomem driver
Date: Mon, 21 Oct 2013 17:04:06 -0700	[thread overview]
Message-ID: <5265C0F6.5080807@linux.intel.com> (raw)

With iomem rawio driver, you can read/write memory mapped registers
from any I/O device via debug fs interface.
This driver is based on the rawio framework.

Signed-off-by: Bin Gao <bin.gao@intel.com>
---
 drivers/misc/rawio/Kconfig       |  12 ++
 drivers/misc/rawio/Makefile      |   1 +
 drivers/misc/rawio/rawio_iomem.c | 401
+++++++++++++++++++++++++++++++++++++++
 3 files changed, 414 insertions(+)
 create mode 100644 drivers/misc/rawio/rawio_iomem.c

diff --git a/drivers/misc/rawio/Kconfig b/drivers/misc/rawio/Kconfig
index 47be40a..38e8a52 100644
--- a/drivers/misc/rawio/Kconfig
+++ b/drivers/misc/rawio/Kconfig
@@ -33,4 +33,16 @@ config RAWIO_PCI
 	  To compile this driver as a module, choose M: the module will
 	  be called rawio_pci.

+config RAWIO_IOMEM
+	tristate "rawio I/O memory driver"
+	depends on RAWIO
+	default no
+	help
+	  This option enables the rawio I/O memory driver.
+	  With this driver, you can read or write registers of a memory
+	  mapped I/O devices.
+
+	  To compile this driver as a module, choose M: the module will
+	  be called rawio_iomem.
+
 endif # RAWIO
diff --git a/drivers/misc/rawio/Makefile b/drivers/misc/rawio/Makefile
index 0933ca6..5f86257 100644
--- a/drivers/misc/rawio/Makefile
+++ b/drivers/misc/rawio/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_RAWIO)		+= rawio.o
 obj-$(CONFIG_RAWIO_PCI)		+= rawio_pci.o
+obj-$(CONFIG_RAWIO_IOMEM)	+= rawio_iomem.o
diff --git a/drivers/misc/rawio/rawio_iomem.c
b/drivers/misc/rawio/rawio_iomem.c
new file mode 100644
index 0000000..eaae773
--- /dev/null
+++ b/drivers/misc/rawio/rawio_iomem.c
@@ -0,0 +1,401 @@
+/*
+ * rawio_iomem.c - a driver to read or write a device's I/O memory,
based on
+ *                 the rawio debugfs framework.
+ *
+ * Copyright (c) 2013 Bin Gao <bin.gao@intel.com>
+ *
+ * This file is released under the GPLv2
+ *
+ *
+ * 1: byte, 2: word, 4: dword
+ *
+ * I/O mem read:
+ * echo "r[1|2|4] iomem <physical_addr> [<len>]" >
/sys/kernel/debug/rawio_cmd
+ * cat /sys/kernel/debug/rawio_output
+ * e.g. echo "r iomem 0xff003040 20" > /sys/kernel/debug/rawio_cmd
+ *      cat /sys/kernel/debug/rawio_output
+ *
+ * I/O mem write:
+ * echo "w[1|2|4] iomem <physical_addr> <val>" >
/sys/kernel/debug/rawio_cmd
+ * cat /sys/kernel/debug/rawio_output
+ * e.g. echo "w2 iomem 0xff003042 0xb03f" > /sys/kernel/debug/rawio_cmd
+ *      cat /sys/kernel/debug/rawio_output
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/pci.h>
+#include <linux/pnp.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/rawio.h>
+
+/*
+ * On some platforms, a read or write to a device which is in a low power
+ * state will cause a system error, or even cause a system reboot.
+ * To address this, we use runtime PM APIs to bring up the device to
+ * runnint state before use and put back to original state after use.
+ *
+ * We could use lookup_resource() to map an physical address to a device,
+ * but there are some problems:
+ * 1) lookup_resource() is not exported, so a kernel module can't use it;
+ * 2) To use the 'name' field of 'struct resource' to match a device is
+ *    not reliable;
+ * So we rather walk known device types than look up the resrouce list
+ * to map a physical address(I/O memory address) to a device.
+ */
+
+/* return true if range(start: m2, size: n2) is in range(start: m1,
size: n1) */
+#define IN_RANGE(m1, n1, m2, n2) \
+	(((m2 >= m1) && (m2 < (m1 + n1))) && \
+	(((m2 + n2) >= m1) && ((m2 + n2) < (m1 + n1))))
+
+struct dev_walker {
+	resource_size_t addr;
+	resource_size_t size;
+	struct device *dev;
+	int error;
+};
+
+#ifdef CONFIG_PCI
+int walk_pci_devices(struct device *dev, void *data)
+{
+	int i;
+	resource_size_t start, len;
+	struct pci_dev *pdev = (struct pci_dev *) to_pci_dev(dev);
+	struct dev_walker *walker = (struct dev_walker *) data;
+
+	if (!pdev)
+		return -ENODEV;
+
+	walker->dev = NULL;
+	for (i = 0; i < 6; i++) {
+		start = pci_resource_start(pdev, i);
+		len = pci_resource_len(pdev, i);
+		if (IN_RANGE(start, len, walker->addr, walker->size)) {
+			walker->dev = dev;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+#endif
+
+int walk_platform_devices(struct device *dev, void *data)
+{
+	int i;
+	struct resource *r;
+	resource_size_t start, len;
+	struct platform_device *plat_dev = to_platform_device(dev);
+	struct dev_walker *walker = (struct dev_walker *) data;
+
+	walker->dev = NULL;
+	for (i = 0; i < plat_dev->num_resources; i++) {
+		r = platform_get_resource(plat_dev, IORESOURCE_MEM, i);
+		if (!r)
+			continue;
+		start = r->start;
+		len = r->end - r->start + 1;
+		if (IN_RANGE(start, len, walker->addr, walker->size)) {
+			walker->dev = dev;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_PNP) && !defined(MODULE)
+int walk_pnp_devices(struct device *dev, void *data)
+{
+	int i;
+	struct resource *r;
+	resource_size_t start, len;
+	struct pnp_dev *pnp_dev = (struct pnp_dev *) to_pnp_dev(dev);
+	struct dev_walker *walker = (struct dev_walker *) data;
+
+	walker->dev = NULL;
+	for (i = 0; (r = pnp_get_resource(pnp_dev, IORESOURCE_MEM, i)); i++) {
+		if (!pnp_resource_valid(r))
+			continue;
+
+		start = r->start;
+		len = r->end - r->start + 1;
+		if (IN_RANGE(start, len, walker->addr, walker->size)) {
+			walker->dev = dev;
+			return 1;
+		}
+	}
+
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_ACPI
+static acpi_status do_walk_acpi_device(acpi_handle handle, u32
nesting_level,
+				void *context, void **ret)
+{
+	struct dev_walker *walker = (struct dev_walker *) context;
+	struct acpi_device *adev = NULL;
+	resource_size_t start, len;
+	struct resource_list_entry *rentry;
+	struct list_head resource_list;
+	struct resource *resources;
+	int i, count;
+
+	acpi_bus_get_device(handle, &adev);
+	if (!adev)
+		return AE_CTRL_DEPTH;
+
+	/*
+	 * Simply skip this acpi device if it's already attached to
+	 * other bus types(e.g. platform dev or a pnp dev).
+	 */
+	if (adev->physical_node_count)
+		return 0;
+
+	INIT_LIST_HEAD(&resource_list);
+	count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
+	if (count <= 0)
+		return AE_CTRL_DEPTH;
+
+	resources = kmalloc(count * sizeof(struct resource), GFP_KERNEL);
+	if (!resources) {
+		pr_err("rawio: No memory for resources\n");
+		acpi_dev_free_resource_list(&resource_list);
+		walker->error = -ENOMEM;
+		return AE_CTRL_TERMINATE;
+	}
+
+	count = 0;
+	list_for_each_entry(rentry, &resource_list, node)
+		resources[count++] = rentry->res;
+
+	acpi_dev_free_resource_list(&resource_list);
+
+	for (i = 0; i < count; i++) {
+		start = resources[i].start;
+		len = resources[i].end - resources[i].start + 1;
+		if (IN_RANGE(start, len, walker->addr, walker->size)) {
+			walker->dev = &adev->dev;
+			kfree(resources);
+			return AE_CTRL_TERMINATE;
+		}
+	}
+
+	kfree(resources);
+	return AE_CTRL_DEPTH;
+}
+#endif
+
+static struct device *walk_devices(resource_size_t addr,
resource_size_t size)
+{
+	int ret;
+	struct dev_walker walker;
+
+	walker.addr = addr;
+	walker.size = size;
+	walker.dev = NULL;
+	walker.error = 0;
+
+#ifdef CONFIG_PCI
+	ret = bus_for_each_dev(&pci_bus_type, NULL, (void *)&walker,
+						walk_pci_devices);
+	if (ret == 1)
+		return  walker.dev;
+#endif
+
+	ret = bus_for_each_dev(&platform_bus_type, NULL, (void *)&walker,
+						walk_platform_devices);
+	if (ret == 1)
+		return walker.dev;
+
+#if defined(CONFIG_PNP) && !defined(MODULE)
+	ret = bus_for_each_dev(&pnp_bus_type, NULL, (void *)&walker,
+						walk_pnp_devices);
+	if (ret == 1)
+		return walker.dev;
+#endif
+
+#ifdef CONFIG_ACPI
+	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX,
+			NULL, do_walk_acpi_device, &walker, NULL);
+
+	if (walker.error)
+		return NULL;
+
+	if (walker.dev)
+		return walker.dev;
+#endif
+
+	return NULL;
+}
+
+
+static int rawio_iomem_read(struct rawio_driver *driver, int width,
+			u64 *input, u8 *postfix, int input_num,
+			void **output, int *output_num)
+{
+	int i, size, count;
+	phys_addr_t addr;
+	void *buf;
+	void __iomem *va;
+	struct device *dev = NULL;
+
+	addr = (phys_addr_t)input[0];
+	count = 1;
+	if (input_num == 2)
+		count = (int)input[1];
+	size = width * count;
+
+	if (((width == WIDTH_2) && (addr & 0x1)) ||
+		((width == WIDTH_4) && (addr & 0x3)) ||
+		((width == WIDTH_8) && (addr & 0x7))) {
+		rawio_err("address requires 2 bytes aligned for 16 bit access, 4
bytes aligned for 32 bit access and 8 bytes aligned for 64 bit access\n");
+		return -EINVAL;
+	}
+
+	va = ioremap_nocache(addr, size);
+	if (!va) {
+		rawio_err("can't map physical address %llx\n", addr);
+		return -EIO;
+	}
+
+	buf = kzalloc(size, GFP_KERNEL);
+	if (buf == NULL) {
+		rawio_err("can't alloc memory\n");
+		iounmap(va);
+		return -ENOMEM;
+	}
+
+	dev = walk_devices(addr, size);
+	if (dev)
+		pm_runtime_get_sync(dev);
+
+	for (i = 0; i < count; i++) {
+		switch (width) {
+		case WIDTH_1:
+			*((u8 *)buf + i) = ioread8(va + i);
+			break;
+		case WIDTH_2:
+			*((u16 *)buf + i) = ioread16(va + i * 2);
+			break;
+		case WIDTH_4:
+			*((u32 *)buf + i) = ioread32(va + i * 4);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (dev)
+		pm_runtime_put_sync(dev);
+	iounmap(va);
+	*output = buf;
+	*output_num = count;
+	return 0;
+}
+
+static int rawio_iomem_write(struct rawio_driver *driver, int width,
+			u64 *input, u8 *postfix, int input_num)
+{
+	unsigned long val;
+	phys_addr_t addr;
+	void __iomem *va;
+	struct device *dev;
+
+	addr = (phys_addr_t)input[0];
+	val = (u64)input[1];
+
+	if (((width == WIDTH_2) && (addr & 0x1)) ||
+		((width == WIDTH_4) && (addr & 0x3)) ||
+		((width == WIDTH_8) && (addr & 0x7))) {
+		rawio_err("address requires 2 bytes aligned for 16 bit access, 4
bytes aligned for 32 bit access and 8 bytes aligned for 64 bit access\n");
+		return -EINVAL;
+	}
+
+	va = ioremap_nocache(addr, width);
+	if (!va) {
+		rawio_err("can't map physical address %llx\n", addr);
+		return -EIO;
+	}
+
+	dev = walk_devices(addr, 0);
+	if (dev)
+		pm_runtime_get_sync(dev);
+
+	switch (width) {
+	case WIDTH_1:
+		 *(u8 *)va = (u8)val;
+		break;
+	case WIDTH_2:
+		 *(u16 *)va = (u16)val;
+		break;
+	case WIDTH_4:
+		 *(u32 *)va = (u32)val;
+		break;
+	default:
+		break;
+	}
+
+	if (dev)
+		pm_runtime_put_sync(dev);
+	iounmap(va);
+	return 0;
+}
+
+static struct rawio_ops rawio_iomem_ops = {
+	rawio_iomem_read,
+	NULL,
+	rawio_iomem_write,
+};
+
+static struct rawio_driver rawio_iomem = {
+	{NULL, NULL},
+	"iomem",
+
+	/* read */
+	2, /* max args */
+	{TYPE_U64, TYPE_S16}, /* args type */
+	1, /* min args */
+	{ 0, 0, }, /* args postfix */
+
+	/* write */
+	2, /* max args */
+	{TYPE_U64, TYPE_U64},
+	2, /* min args */
+	{ 0, 0, },
+
+	0, /* index to address arg */
+
+	WIDTH_1 | WIDTH_2 | WIDTH_4, /* supported access width*/
+	WIDTH_4, /* default access width */
+	"r[1|2|4] iomem <addr> [<len>]\nw[1|2|4] iomem <addr> <val>\n",
+	&rawio_iomem_ops,
+	NULL
+};
+
+static int __init rawio_iomem_init(void)
+{
+	return rawio_register_driver(&rawio_iomem);
+}
+module_init(rawio_iomem_init);
+
+static void __exit rawio_iomem_exit(void)
+{
+	rawio_unregister_driver(&rawio_iomem);
+}
+module_exit(rawio_iomem_exit);
+
+MODULE_DESCRIPTION("Rawio I/O memory driver");
+MODULE_VERSION("1.0");
+MODULE_AUTHOR("Bin Gao <bin.gao@intel.com>");
+MODULE_LICENSE("GPL v2");
-- 
1.8.1.2


                 reply	other threads:[~2013-10-22  0:02 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5265C0F6.5080807@linux.intel.com \
    --to=bin.gao@linux.intel.com \
    --cc=arnd@arndb.de \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.