[PATCH 3/4] drivers/misc: add rawio iomem driver

From: Bin Gao
Date: Mon Oct 21 2013 - 20:02:27 EST


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@xxxxxxxxx>
---
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@xxxxxxxxx>
+ *
+ * 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@xxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
--
1.8.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/