[2/5] mtdram: Add flight recorder

Message ID 20171206085039.27164-3-dirk.behme@de.bosch.com
State New
Delegated to: Richard Weinberger
Headers show
Series
  • Add flight recorder to MTDRAM
Related show

Commit Message

Dirk Behme Dec. 6, 2017, 8:50 a.m.
From: Manfred Spraul <manfred@colorfullife.com>

The patch adds the option to enable a flight recorder for the
mtdram test device: All ERASE and WRITE commands are logged, e.g.
to perform targeted power fail testing.

Signed-off-by: Manfred Spraul <manfred.spraul@de.bosch.com>
Cc: Manfred Spraul <manfred@colorfullife.com>
---
 drivers/mtd/devices/Kconfig  |  25 +++++
 drivers/mtd/devices/mtdram.c | 239 ++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 252 insertions(+), 12 deletions(-)

Patch

diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index d8b67ba0b5de..8647214089c9 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -200,6 +200,31 @@  config MTDRAM_WRITEBUF_SIZE
 	  This allows you to specify the writebuf size that is reported
 	  by the device emulated by the MTDRAM driver.
 
+config MTDRAM_FLIGHTRECORDER
+	bool "Flight recorder for MTDRAM test device"
+	depends on (MTD_MTDRAM && DEBUG_FS)
+	default n
+	help
+	  This allows you to enable flight recorder mode for the MTDRAM test
+	  device: All ERASE and WRITE commands are logged and exposed in
+	  debugfs.
+
+config MTDRAM_FLIGHTRECORDER_BUFFER_SIZE
+	int "MTDRAM flightrecorder buffer size in KiB"
+	depends on MTDRAM_FLIGHTRECORDER
+	default "131072"
+	help
+	  This allows you to set the size of the flight recorder buffer for
+	  the MTDRAM test device.
+
+config MTDRAM_FLIGHTRECORDER_ENABLED
+	bool "Flight recorder for MTDRAM test device"
+	default y
+	depends on MTDRAM_FLIGHTRECORDER
+	help
+	  This flag sets the initial setting of the flight recorder. If enabled,
+	  the recording starts immediate at module start.
+
 config MTD_BLOCK2MTD
 	tristate "MTD using block device"
 	depends on BLOCK
diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c
index 0c8652ac0395..1dc0a5ce0f07 100644
--- a/drivers/mtd/devices/mtdram.c
+++ b/drivers/mtd/devices/mtdram.c
@@ -18,6 +18,190 @@ 
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/mtdram.h>
 
+#ifdef CONFIG_MTDRAM_FLIGHTRECORDER
+
+#include <linux/debugfs.h>
+#include <linux/sched.h>
+
+static unsigned long fr_buffersize = CONFIG_MTDRAM_FLIGHTRECORDER_BUFFER_SIZE;
+
+#define FR_BUFFER_TOTAL_SIZE	(fr_buffersize * 1024)
+#define FR_BUFFER_MARGIN	512
+
+static bool fr_enabled = CONFIG_MTDRAM_FLIGHTRECORDER_ENABLED;
+
+#ifdef MODULE
+module_param(fr_buffersize, ulong, 0);
+MODULE_PARM_DESC(fr_buffersize, "Flight recorder buffer size KiB");
+module_param(fr_enabled, bool, 0);
+MODULE_PARM_DESC(fr_enabled, "Set the initial enabled/disabled status");
+#endif
+
+static char *fr_buffer;
+static int fr_pos;
+static struct dentry *fr_dentry;
+
+static DEFINE_MUTEX(fr_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(fr_wait);
+
+static ssize_t nandrec_read_file(struct file *file, char __user *user_buf,
+				 size_t count, loff_t *ppos)
+{
+	ssize_t r;
+
+	mutex_lock(&fr_mutex);
+
+	/* Every read must read all available data */
+	if (count < fr_pos) {
+		r = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (fr_pos == 0) {
+		r = 0;
+		goto out_unlock;
+	}
+
+	*ppos = 0;
+
+	r = debugfs_file_get(fr_dentry);
+	count = fr_pos;
+	if (likely(!r)) {
+		r = simple_read_from_buffer(user_buf, count, ppos, fr_buffer,
+					    FR_BUFFER_TOTAL_SIZE);
+		debugfs_file_put(fr_dentry);
+	}
+
+	/* Every read must read all available data */
+	WARN_ON(fr_pos != r);
+
+	/* Every read clears the kernel buffer */
+	fr_pos = 0;
+
+	/* if someone waits, wake him up */
+	if (waitqueue_active(&fr_wait))
+		wake_up(&fr_wait);
+
+out_unlock:
+	mutex_unlock(&fr_mutex);
+	return r;
+}
+
+static const struct file_operations fr_fops = {
+	.read =		nandrec_read_file,
+	.open =		simple_open,
+};
+
+#define MAGIC_START	0x12345678UL
+#define MAGIC_END	0x87654321UL
+
+#define FUNC_WRITE	1UL
+#define FUNC_ERASE	2UL
+
+static void write_u32(u32 data)
+{
+	u32 *target = (u32 *)(fr_buffer + fr_pos);
+
+	if (fr_enabled) {
+		*target = cpu_to_le32(data);
+		fr_pos += round_up(sizeof(u32), 8);
+	}
+}
+
+static void write_u64(u64 data)
+{
+	u64 *target = (u64 *)(fr_buffer + fr_pos);
+
+	if (fr_enabled) {
+		*target = cpu_to_le64(data);
+		fr_pos += round_up(sizeof(u64), 8);
+	}
+}
+
+static void write_blob(const u_char *data, int len)
+{
+	u32 *target = (u32 *)(fr_buffer + fr_pos);
+
+	if (fr_enabled) {
+		memcpy(target, data, len);
+		fr_pos += round_up(len, 8);
+	}
+}
+
+static void start_write(int size)
+{
+	mutex_lock(&fr_mutex);
+
+	if (fr_enabled) {
+		while (fr_pos + size + 2*8 >=
+				FR_BUFFER_TOTAL_SIZE - FR_BUFFER_MARGIN) {
+			DEFINE_WAIT(wait);
+
+			pr_info("%p: Waiting - write count %d, current %d.\n",
+				current, size, fr_pos);
+
+			prepare_to_wait(&fr_wait, &wait, TASK_UNINTERRUPTIBLE);
+
+			mutex_unlock(&fr_mutex);
+
+			schedule();
+			mutex_lock(&fr_mutex);
+			finish_wait(&fr_wait, &wait);
+		}
+		write_u32(MAGIC_START);
+	}
+}
+
+static void end_write(void)
+{
+	if (fr_enabled) {
+		write_u32(MAGIC_END);
+		WARN_ON(fr_pos > FR_BUFFER_TOTAL_SIZE - FR_BUFFER_MARGIN);
+	}
+	mutex_unlock(&fr_mutex);
+}
+
+static int fr_init(void)
+{
+	fr_buffer = vmalloc(FR_BUFFER_TOTAL_SIZE);
+	if (!fr_buffer)
+		return -ENOMEM;
+
+	memset(fr_buffer, 0xfa, FR_BUFFER_TOTAL_SIZE);
+
+	fr_dentry = debugfs_create_file("mtdram", 0600, NULL, NULL,
+					&fr_fops);
+
+	if (IS_ERR(fr_dentry)) {
+		vfree(fr_buffer);
+		fr_buffer = NULL;
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void fr_exit(void)
+{
+	if (fr_buffer) {
+		debugfs_remove(fr_dentry);
+		vfree(fr_buffer);
+		fr_buffer = NULL;
+	}
+}
+
+#else /* CONFIG_MTDRAM_FLIGHTRECORDER */
+
+static int fr_init(void)
+{
+	return 0;
+}
+
+static void fr_exit(void)
+{
+}
+
+#endif /* CONFIG_MTDRAM_FLIGHTRECORDER */
+
 static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
 static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
 static unsigned long writebuf_size = CONFIG_MTDRAM_WRITEBUF_SIZE;
@@ -63,6 +247,15 @@  static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
 	if (check_offs_len(mtd, instr->addr, instr->len))
 		return -EINVAL;
 	memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
+
+#ifdef CONFIG_MTDRAM_FLIGHTRECORDER
+	start_write(3*8);
+	write_u32(FUNC_ERASE);
+	write_u64(instr->addr);
+	write_u64(instr->len);
+	end_write();
+#endif
+
 	instr->state = MTD_ERASE_DONE;
 	mtd_erase_callback(instr);
 	return 0;
@@ -114,12 +307,23 @@  static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
 		size_t *retlen, const u_char *buf)
 {
 	memcpy((char *)mtd->priv + to, buf, len);
+
+#ifdef CONFIG_MTDRAM_FLIGHTRECORDER
+	start_write(3*8 + len);
+	write_u32(FUNC_WRITE);
+	write_u64(to);
+	write_u64(len);
+	write_blob(buf, len);
+	end_write();
+#endif
+
 	*retlen = len;
 	return 0;
 }
 
 static void __exit cleanup_mtdram(void)
 {
+	fr_exit();
 	if (mtd_info) {
 		mtd_device_unregister(mtd_info);
 		vfree(mtd_info->priv);
@@ -163,25 +367,36 @@  static int __init init_mtdram(void)
 	if (!total_size)
 		return -EINVAL;
 
+	err = -ENOMEM;
+
 	/* Allocate some memory */
 	mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
 	if (!mtd_info)
-		return -ENOMEM;
+		goto fail_mtdinfo;
 
 	addr = vmalloc(MTDRAM_TOTAL_SIZE);
-	if (!addr) {
-		kfree(mtd_info);
-		mtd_info = NULL;
-		return -ENOMEM;
-	}
+	if (!addr)
+		goto fail_ramdisk;
+
 	err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
-	if (err) {
-		vfree(addr);
-		kfree(mtd_info);
-		mtd_info = NULL;
-		return err;
-	}
+	if (err)
+		goto fail_init_device;
+
 	memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
+	err = fr_init();
+	if (err)
+		goto fail_fr_init;
+
+	return 0;
+
+fail_fr_init:
+	mtd_device_unregister(mtd_info);
+fail_init_device:
+	vfree(addr);
+fail_ramdisk:
+	kfree(mtd_info);
+	mtd_info = NULL;
+fail_mtdinfo:
 	return err;
 }