android_kernel_modules_leno.../drivers/misc/rawio/rawio_iomem.c

402 lines
9.2 KiB
C

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