lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1343397500-31283-1-git-send-email-dp@opensource.wolfsonmicro.com>
Date:	Fri, 27 Jul 2012 14:58:20 +0100
From:	Dimitris Papastamos <dp@...nsource.wolfsonmicro.com>
To:	Mark Brown <broonie@...nsource.wolfsonmicro.com>
Cc:	linux-kernel@...r.kernel.org, patches@...nsource.wolfsonmicro.com
Subject: [PATCH v2] regmap: Add regmap dummy driver

Add a pseudo-driver for debugging and stress-testing the
regmap/regcache APIs.  A standard set of tools for working
with this driver (mainly sh scripts) will be put in a repo
at https://github.com/quantumdream/regmap-tools

Some of these tests will require one to build with
REGMAP_ALLOW_WRITE_DEBUGFS defined.

Signed-off-by: Dimitris Papastamos <dp@...nsource.wolfsonmicro.com>
---
 This is an initial implementation of the regdummy driver for regmap.
 This is mainly useful for debugging/stress-testing regcache as it
 removes the need for real hardware and can be done in an emulated
 environment very easily.

 There'll be incremental patches adding more features such as,
 support for configurable volatile/readable/etc. registers via
 debugfs entries.

 v2 updates:
 - Factored out the access to the MMIO region into a separate
 function.
 - Removed bogus Change-Id

 drivers/base/regmap/Kconfig        |   8 +
 drivers/base/regmap/Makefile       |   1 +
 drivers/base/regmap/regmap-dummy.c | 609 +++++++++++++++++++++++++++++++++++++
 3 files changed, 618 insertions(+)
 create mode 100644 drivers/base/regmap/regmap-dummy.c

diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index 6be390b..5a1ab02 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -20,3 +20,11 @@ config REGMAP_MMIO
 
 config REGMAP_IRQ
 	bool
+
+config REGMAP_DUMMY
+	tristate
+	select REGMAP_MMIO
+	help
+	  Say Y or M if you want to add the regdummy driver for regmap.
+	  This is a pseudo-driver used for debugging and stress-testing
+	  the regmap/regcache APIs.
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 5e75d1b..c5d70f1 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
 obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
 obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o
 obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o
+obj-$(CONFIG_REGMAP_DUMMY) += regmap-dummy.o
diff --git a/drivers/base/regmap/regmap-dummy.c b/drivers/base/regmap/regmap-dummy.c
new file mode 100644
index 0000000..078e090
--- /dev/null
+++ b/drivers/base/regmap/regmap-dummy.c
@@ -0,0 +1,609 @@
+/*
+ * Register map access API - Dummy regmap driver
+ *
+ * Copyright 2012 Wolfson Microelectronics PLC.
+ *
+ * Author: Dimitris Papastamos <dp@...nsource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+#define DEFAULT_REGS_SIZE 1024
+
+struct regdummy_dev {
+	struct device *dev;
+	struct mutex lock;
+
+	/* Set when regdummy defaults have been modified.
+	 * This is useful to know so we don't reinit the
+	 * cache if there is no reason to do so. */
+	unsigned int dirty:1;
+
+	void *regs;
+	unsigned int regs_size;
+	unsigned int regs_size_new;
+
+	struct regmap *map;
+	struct regmap_config *config;
+	struct reg_default *regdef;
+};
+
+static struct dentry *regdummy_debugfs_root;
+
+/* Default volatile register callback, this should
+ * normally be configured by the user via a debugfs
+ * entry */
+static bool regdummy_volatile_reg(struct device *dev,
+				  unsigned int reg)
+{
+	return false;
+}
+
+/* Default readable register callback, this should
+ * normally be configured by the user via a debugfs
+ * entry */
+static bool regdummy_readable_reg(struct device *dev,
+				  unsigned int reg)
+{
+	return true;
+}
+
+/* Default precious register callback, this should
+ * normally be configured by the user via a debugfs
+ * entry */
+static bool regdummy_precious_reg(struct device *dev,
+				  unsigned int reg)
+{
+	return false;
+}
+
+/* Calculate the length of a fixed format  */
+static size_t regmap_calc_reg_len(int max_val, char *buf, size_t buf_size)
+{
+	snprintf(buf, buf_size, "%x", max_val);
+	return strlen(buf);
+}
+
+static ssize_t regdummy_defaults_read_file(struct file *file, char __user *user_buf,
+					   size_t count, loff_t *ppos)
+{
+	int reg_len, val_len, tot_len;
+	size_t buf_pos = 0;
+	loff_t p = 0;
+	ssize_t ret;
+	int i;
+	struct regdummy_dev *rdevp = file->private_data;
+	struct regmap_config *config;
+	struct reg_default *regdef;
+	unsigned int val;
+	unsigned int j;
+	unsigned int regdef_num;
+	char *buf;
+
+	if (*ppos < 0 || !count)
+		return -EINVAL;
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&rdevp->lock);
+
+	config = rdevp->config;
+	regdef = rdevp->regdef;
+	regdef_num = rdevp->regs_size / config->reg_stride;
+
+	/* Calculate the length of a fixed format  */
+	reg_len = regmap_calc_reg_len(config->max_register, buf, count);
+	val_len = 2 * DIV_ROUND_UP(config->val_bits, 8);
+	tot_len = reg_len + val_len + 3;      /* : \n */
+
+	for (i = 0; i <= config->max_register; i += config->reg_stride) {
+		if (!regdummy_readable_reg(rdevp->dev, i))
+			continue;
+
+		if (regdummy_precious_reg(rdevp->dev, i))
+			continue;
+
+		/* If we're in the region the user is trying to read */
+		if (p >= *ppos) {
+			/* ...but not beyond it */
+			if (buf_pos >= count - 1 - tot_len)
+				break;
+
+			/* Format the register */
+			snprintf(buf + buf_pos, count - buf_pos, "%.*x: ",
+				 reg_len, i);
+			buf_pos += reg_len + 2;
+
+			/* Format the value, write all X if we can't read */
+			for (j = 0; j < regdef_num; j++) {
+				if (regdef[j].reg == i) {
+					val = regdef[j].def;
+					snprintf(buf + buf_pos, count - buf_pos,
+						 "%.*x", val_len, val);
+					break;
+				}
+			}
+			if (j == regdef_num)
+				memset(buf + buf_pos, 'X', val_len);
+			buf_pos += 2 * DIV_ROUND_UP(config->val_bits, 8);
+
+			buf[buf_pos++] = '\n';
+		}
+		p += tot_len;
+	}
+
+	ret = buf_pos;
+
+	if (copy_to_user(user_buf, buf, buf_pos)) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	*ppos += buf_pos;
+
+out:
+	mutex_unlock(&rdevp->lock);
+
+	kfree(buf);
+	return ret;
+}
+
+static ssize_t regdummy_defaults_write_file(struct file *file,
+					    const char __user *user_buf,
+					    size_t count, loff_t *ppos)
+{
+	char buf[32];
+	size_t buf_size;
+	char *start = buf;
+	unsigned long reg, value;
+	struct regdummy_dev *rdevp = file->private_data;
+	struct regmap_config *config = rdevp->config;
+	struct reg_default *regdef = rdevp->regdef;
+	unsigned int i;
+	unsigned int regdef_num = rdevp->regs_size / config->reg_stride;
+
+	buf_size = min(count, (sizeof(buf)-1));
+	if (copy_from_user(buf, user_buf, buf_size))
+		return -EFAULT;
+	buf[buf_size] = 0;
+
+	while (*start == ' ')
+		start++;
+	reg = simple_strtoul(start, &start, 16);
+	while (*start == ' ')
+		start++;
+	if (strict_strtoul(start, 16, &value))
+		return -EINVAL;
+
+	mutex_lock(&rdevp->lock);
+	for (i = 0; i < regdef_num; i++) {
+		if (regdef[i].reg == reg) {
+			if (regdef[i].def != value)
+				rdevp->dirty = 1;
+			regdef[i].def = value;
+			break;
+		}
+	}
+	mutex_unlock(&rdevp->lock);
+	return buf_size;
+}
+
+static const struct file_operations regdummy_defaults_fops = {
+	.open = simple_open,
+	.read = regdummy_defaults_read_file,
+	.write = regdummy_defaults_write_file,
+	.llseek = default_llseek,
+};
+
+/* Called with lock held */
+static int regdummy_create_debugfs(struct device *dev)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+
+	regdummy_debugfs_root = debugfs_create_dir("regdummy", NULL);
+	if (IS_ERR(regdummy_debugfs_root))
+		return PTR_ERR(regdummy_debugfs_root);
+
+	debugfs_create_file("defaults", 0400, regdummy_debugfs_root,
+			    rdevp, &regdummy_defaults_fops);
+
+	return 0;
+}
+
+/* Called with lock held */
+static void regdummy_exit_debugfs(struct device *dev)
+{
+	debugfs_remove_recursive(regdummy_debugfs_root);
+}
+
+/* Called with lock held */
+static int regdummy_init_regmap(struct device *dev)
+{
+	struct regmap_config *config;
+	struct reg_default *regdef;
+	int ret;
+	void *regs;
+	unsigned int regdef_num, regdef_num_raw;
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	unsigned int i;
+
+	config = kzalloc(sizeof(*config), GFP_KERNEL);
+	if (!config)
+		return -ENOMEM;
+	rdevp->config = config;
+
+	/* Fill up a sample regmap_config structure */
+	config->reg_bits = 32;
+	config->val_bits = 32;
+	config->reg_stride = 4;
+
+	/* Allocate our fake __iomem region */
+	rdevp->regs_size = rdevp->regs_size_new = DEFAULT_REGS_SIZE;
+	regs = kzalloc(rdevp->regs_size, GFP_KERNEL);
+	if (!regs) {
+		ret = -ENOMEM;
+		goto err_alloc_config;
+	}
+	rdevp->regs = regs;
+
+	/* Allocate the register defaults */
+	regdef_num = rdevp->regs_size / config->reg_stride;
+	regdef_num_raw = regdef_num * sizeof(*regdef);
+	regdef = kzalloc(regdef_num_raw, GFP_KERNEL);
+	if (!regdef) {
+		ret = -ENOMEM;
+		goto err_alloc_regs;
+	}
+	rdevp->regdef = regdef;
+
+	for (i = 0; i < regdef_num; i++)
+		rdevp->regdef[i].reg = i * config->reg_stride;
+
+	/* Fill up the rest of the regmap_config structure */
+	config->reg_defaults = regdef;
+	config->num_reg_defaults = regdef_num;
+	config->max_register = rdevp->regs_size - config->reg_stride;
+	config->cache_type = REGCACHE_RBTREE;
+	config->volatile_reg = regdummy_volatile_reg;
+	config->readable_reg = regdummy_readable_reg;
+	config->precious_reg = regdummy_precious_reg;
+
+	rdevp->map = regmap_init_mmio(rdevp->dev,
+				      (void __iomem *)rdevp->regs,
+				      config);
+	if (IS_ERR(rdevp->map)) {
+		ret = PTR_ERR(rdevp->map);
+		goto err_alloc_regdef;
+	}
+
+	ret = regdummy_create_debugfs(dev);
+	if (ret < 0) {
+		dev_err(rdevp->dev, "Failed to create debugfs entries: %d\n",
+			ret);
+		goto err_regmap_init;
+	}
+
+	return 0;
+
+err_regmap_init:
+	regmap_exit(rdevp->map);
+err_alloc_regdef:
+	kfree(regdef);
+err_alloc_regs:
+	kfree(regs);
+err_alloc_config:
+	kfree(config);
+	return ret;
+}
+
+/* Called with lock held */
+static void regdummy_free_regmap(struct device *dev)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+
+	regdummy_exit_debugfs(dev);
+
+	if (rdevp->map)
+		regmap_exit(rdevp->map);
+
+	kfree(rdevp->config);
+	kfree(rdevp->regs);
+	kfree(rdevp->regdef);
+}
+
+/* Called with lock held */
+static void regdummy_write_raw(struct device *dev, void *buf,
+			       unsigned int reg, unsigned int val)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	struct regmap_config *config = rdevp->config;
+
+	switch (config->val_bits) {
+	case 8: {
+		u8 *p = buf;
+		p[reg] = val;
+		break;
+	}
+	case 16: {
+		u16 *p = buf;
+		p[reg] = val;
+		break;
+	}
+	case 32: {
+		u32 *p = buf;
+		p[reg] = val;
+		break;
+	}
+	default:
+		BUG();
+		break;
+	}
+}
+
+static int regdummy_reinit_map(struct device *dev)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	struct reg_default *regdef_new;
+	struct regmap_config *config = rdevp->config;
+	unsigned int regdef_num_new;
+	unsigned int regdef_num_raw_new;
+	unsigned int regdef_num;
+	void *regs_new;
+	unsigned int i;
+
+	int ret;
+
+	if (!rdevp->dirty)
+		return 0;
+
+	/* Allocate the new fake __iomem region */
+	regs_new = kzalloc(rdevp->regs_size_new, GFP_KERNEL);
+	if (!regs_new)
+		return -ENOMEM;
+
+	/* Allocate the new register defaults */
+	regdef_num_new = rdevp->regs_size_new / config->reg_stride;
+	regdef_num_raw_new = regdef_num_new * sizeof(*regdef_new);
+	regdef_new = kzalloc(regdef_num_raw_new, GFP_KERNEL);
+	if (!regdef_new) {
+		kfree(regs_new);
+		return -ENOMEM;
+	}
+
+	/* Fixup the register defaults and the raw regs region */
+	regdef_num = rdevp->regs_size / config->reg_stride;
+	for (i = 0; i < regdef_num_new; i++) {
+		regdef_new[i].reg = i * config->reg_stride;
+		if (i < regdef_num) {
+			regdef_new[i].def = rdevp->regdef[i].def;
+			regdummy_write_raw(dev, regs_new, i,
+					   rdevp->regdef[i].def);
+		}
+	}
+
+	/* Re-initialize everything */
+	regmap_exit(rdevp->map);
+
+	kfree(rdevp->regs);
+	rdevp->regs = regs_new;
+	rdevp->regs_size = rdevp->regs_size_new;
+
+	kfree(rdevp->regdef);
+	rdevp->regdef = regdef_new;
+
+	config->reg_defaults = regdef_new;
+	config->num_reg_defaults = regdef_num_new;
+	config->max_register = rdevp->regs_size_new - config->reg_stride;
+
+	rdevp->map = regmap_init_mmio(rdevp->dev,
+				      (void __iomem *)rdevp->regs,
+				      config);
+	if (IS_ERR(rdevp->map)) {
+		ret = PTR_ERR(rdevp->map);
+		dev_err(rdevp->dev, "Failed to reinit regmap: %d\n", ret);
+		rdevp->map = NULL;
+		return ret;
+	}
+
+	rdevp->dirty = 0;
+
+	return 0;
+}
+
+static ssize_t reinit_map_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&rdevp->lock);
+	ret = regdummy_reinit_map(dev);
+	mutex_unlock(&rdevp->lock);
+	if (ret)
+		return ret;
+	return count;
+}
+static DEVICE_ATTR(reinit_map, S_IWUSR, NULL, reinit_map_store);
+
+static ssize_t regs_raw_size_show(struct device *dev, struct device_attribute *attr,
+				  char *buf)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	ssize_t ret;
+
+	mutex_lock(&rdevp->lock);
+	ret = scnprintf(buf, PAGE_SIZE, "%u\n", rdevp->regs_size);
+	mutex_unlock(&rdevp->lock);
+	return ret;
+}
+
+static ssize_t regs_raw_size_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	long val;
+	int ret;
+
+	mutex_lock(&rdevp->lock);
+	ret = strict_strtol(buf, 10, &val);
+	if (ret) {
+		mutex_unlock(&rdevp->lock);
+		return ret;
+	}
+
+	if (!val || (val % rdevp->config->reg_stride)) {
+		mutex_unlock(&rdevp->lock);
+		return -EINVAL;
+	}
+
+	rdevp->regs_size_new = val;
+	rdevp->dirty = 1;
+	regdummy_reinit_map(dev);
+	mutex_unlock(&rdevp->lock);
+	return count;
+}
+static DEVICE_ATTR(regs_raw_size, S_IWUSR | S_IRUGO,
+		   regs_raw_size_show, regs_raw_size_store);
+
+static ssize_t sync_store(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&rdevp->lock);
+	BUG_ON(!rdevp->map);
+	ret = regcache_sync(rdevp->map);
+	mutex_unlock(&rdevp->lock);
+	if (ret)
+		return ret;
+	return count;
+}
+static DEVICE_ATTR(sync, S_IWUSR, NULL, sync_store);
+
+static int __devinit regdummy_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct regdummy_dev *rdevp;
+
+	rdevp = devm_kzalloc(&pdev->dev, sizeof(*rdevp), GFP_KERNEL);
+	if (!rdevp)
+		return -ENOMEM;
+
+	mutex_init(&rdevp->lock);
+
+	rdevp->dev = &pdev->dev;
+	dev_set_drvdata(rdevp->dev, rdevp);
+
+	mutex_lock(&rdevp->lock);
+	ret = regdummy_init_regmap(rdevp->dev);
+	if (ret < 0) {
+		dev_err(rdevp->dev,
+			"Failed to init regmap: %d\n", ret);
+		mutex_unlock(&rdevp->lock);
+		return ret;
+	}
+
+	ret = device_create_file(rdevp->dev,
+				 &dev_attr_reinit_map);
+	if (ret < 0)
+		goto err_regmap_init;
+
+	ret = device_create_file(rdevp->dev,
+				 &dev_attr_regs_raw_size);
+	if (ret < 0)
+		goto err_create_reinit_map;
+
+	ret = device_create_file(rdevp->dev,
+				 &dev_attr_sync);
+	if (ret < 0)
+		goto err_create_regs_raw_size;
+	mutex_unlock(&rdevp->lock);
+
+	return 0;
+
+err_create_regs_raw_size:
+	device_remove_file(rdevp->dev, &dev_attr_regs_raw_size);
+err_create_reinit_map:
+	device_remove_file(rdevp->dev, &dev_attr_reinit_map);
+err_regmap_init:
+	regdummy_free_regmap(rdevp->dev);
+	mutex_unlock(&rdevp->lock);
+	return ret;
+}
+
+static int regdummy_remove(struct platform_device *pdev)
+{
+	struct regdummy_dev *rdevp = dev_get_drvdata(&pdev->dev);
+
+	mutex_lock(&rdevp->lock);
+	device_remove_file(rdevp->dev, &dev_attr_sync);
+	device_remove_file(rdevp->dev, &dev_attr_regs_raw_size);
+	device_remove_file(rdevp->dev, &dev_attr_reinit_map);
+	regdummy_free_regmap(&pdev->dev);
+	dev_set_drvdata(&pdev->dev, NULL);
+	mutex_unlock(&rdevp->lock);
+
+	return 0;
+}
+
+static struct platform_driver regdummy_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "regdummy",
+	},
+	.probe = regdummy_probe,
+	.remove = regdummy_remove,
+};
+
+static struct platform_device regdummy_device = {
+	.name = "regdummy",
+	.id = 0,
+};
+
+static int __init regdummy_init(void)
+{
+	int ret;
+
+	ret = platform_device_register(&regdummy_device);
+	if (ret)
+		return ret;
+
+	ret = platform_driver_register(&regdummy_driver);
+	if (ret) {
+		platform_device_unregister(&regdummy_device);
+		return ret;
+	}
+
+	return 0;
+}
+module_init(regdummy_init);
+
+static void __exit regdummy_exit(void)
+{
+	platform_driver_unregister(&regdummy_driver);
+	platform_device_unregister(&regdummy_device);
+}
+module_exit(regdummy_exit);
+
+MODULE_DESCRIPTION("Regmap dummy driver");
+MODULE_AUTHOR("Dimitris Papastamos <dp@...nsource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
-- 
1.7.11.3

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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ