diff mbox

[net-next,1/2] fs/crashdd: add API to collect hardware dump in second kernel

Message ID f0c38b01859cc6abf32201764311dd48123399c9.1521793455.git.rahul.lakkireddy@chelsio.com (mailing list archive)
State New, archived
Headers show

Commit Message

Rahul Lakkireddy March 23, 2018, 8:31 a.m. UTC
Add a new module crashdd that exports the /sys/kernel/crashdd/
directory in second kernel, containing collected hardware/firmware
dumps.

The sequence of actions done by device drivers to append their device
specific hardware/firmware logs to /sys/kernel/crashdd/ directory are
as follows:

1. During probe (before hardware is initialized), device drivers
register to the crashdd module (via crashdd_add_dump()), with
callback function, along with buffer size and log name needed for
firmware/hardware log collection.

2. Crashdd creates a driver's directory under
/sys/kernel/crashdd/<driver>. Then, it allocates the buffer with
requested size and invokes the device driver's registered callback
function.

3. Device driver collects all hardware/firmware logs into the buffer
and returns control back to crashdd.

4. Crashdd exposes the buffer as a binary file via
/sys/kernel/crashdd/<driver>/<dump_file>.

Suggested-by: Eric Biederman <ebiederm@xmission.com>.
Suggested-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Rahul Lakkireddy <rahul.lakkireddy@chelsio.com>
Signed-off-by: Ganesh Goudar <ganeshgr@chelsio.com>
---
Changes since rfc v2:
- Moved exporting crashdd from procfs to sysfs.  Suggested by
  Stephen Hemminger <stephen@networkplumber.org>
- Moved code from fs/proc/crashdd.c to fs/crashdd/ directory.
- Replaced all proc API with sysfs API and updated comments.
- Calling driver callback before creating the binary file under
  crashdd sysfs.
- Changed binary dump file permission from S_IRUSR to S_IRUGO.
- Changed module name from CRASH_DRIVER_DUMP to CRASH_DEVICE_DUMP.

rfc v2:
- Collecting logs in 2nd kernel instead of during kernel panic.
  Suggested by Eric Biederman <ebiederm@xmission.com>.
- Patch added in this series.

 fs/Kconfig                    |   1 +
 fs/Makefile                   |   1 +
 fs/crashdd/Kconfig            |  10 ++
 fs/crashdd/Makefile           |   3 +
 fs/crashdd/crashdd.c          | 234 ++++++++++++++++++++++++++++++++++++++++++
 fs/crashdd/crashdd_internal.h |  24 +++++
 include/linux/crashdd.h       |  24 +++++
 7 files changed, 297 insertions(+)
 create mode 100644 fs/crashdd/Kconfig
 create mode 100644 fs/crashdd/Makefile
 create mode 100644 fs/crashdd/crashdd.c
 create mode 100644 fs/crashdd/crashdd_internal.h
 create mode 100644 include/linux/crashdd.h

Comments

kernel test robot March 24, 2018, 12:29 p.m. UTC | #1
Hi Rahul,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Rahul-Lakkireddy/fs-crashdd-add-API-to-collect-hardware-dump-in-second-kernel/20180324-193856
config: i386-randconfig-s0-201811 (attached as .config)
compiler: gcc-6 (Debian 6.4.0-9) 6.4.0 20171026
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All errors (new ones prefixed by >>):

   In file included from fs/crashdd/crashdd.c:8:0:
   fs/crashdd/crashdd_internal.h:13:23: error: field 'bin_attr' has incomplete type
     struct bin_attribute bin_attr; /* Binary dump file's attributes */
                          ^~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_read':
   fs/crashdd/crashdd.c:20:43: error: dereferencing pointer to incomplete type 'struct bin_attribute'
     struct crashdd_dump_node *dump = bin_attr->private;
                                              ^~
   fs/crashdd/crashdd.c: In function 'crashdd_mkdir':
>> fs/crashdd/crashdd.c:28:9: error: implicit declaration of function 'kobject_create_and_add' [-Werror=implicit-function-declaration]
     return kobject_create_and_add(name, crashdd_kobj);
            ^~~~~~~~~~~~~~~~~~~~~~
   fs/crashdd/crashdd.c:28:9: warning: return makes pointer from integer without a cast [-Wint-conversion]
     return kobject_create_and_add(name, crashdd_kobj);
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_add_file':
   fs/crashdd/crashdd.c:40:9: error: implicit declaration of function 'sysfs_create_bin_file' [-Werror=implicit-function-declaration]
     return sysfs_create_bin_file(kobj, &dump->bin_attr);
            ^~~~~~~~~~~~~~~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_rmdir':
>> fs/crashdd/crashdd.c:45:2: error: implicit declaration of function 'kobject_put' [-Werror=implicit-function-declaration]
     kobject_put(kobj);
     ^~~~~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_get_driver':
   fs/crashdd/crashdd.c:102:25: error: dereferencing pointer to incomplete type 'struct kobject'
      if (!strcmp(node->kobj->name, name)) {
                            ^~
   fs/crashdd/crashdd.c: In function 'crashdd_init':
>> fs/crashdd/crashdd.c:228:51: error: 'kernel_kobj' undeclared (first use in this function)
     crashdd_kobj = kobject_create_and_add("crashdd", kernel_kobj);
                                                      ^~~~~~~~~~~
   fs/crashdd/crashdd.c:228:51: note: each undeclared identifier is reported only once for each function it appears in
   fs/crashdd/crashdd.c: In function 'crashdd_add_file':
   fs/crashdd/crashdd.c:41:1: warning: control reaches end of non-void function [-Wreturn-type]
    }
    ^
   cc1: some warnings being treated as errors

vim +/kobject_create_and_add +28 fs/crashdd/crashdd.c

     7	
   > 8	#include "crashdd_internal.h"
     9	
    10	static LIST_HEAD(crashdd_list);
    11	static DEFINE_MUTEX(crashdd_mutex);
    12	
    13	#define CRASHDD_SYSFS_MODE 444 /* S_IRUGO */
    14	static struct kobject *crashdd_kobj;
    15	
    16	static ssize_t crashdd_read(struct file *filp, struct kobject *kobj,
    17				    struct bin_attribute *bin_attr,
    18				    char *buf, loff_t fpos, size_t count)
    19	{
    20		struct crashdd_dump_node *dump = bin_attr->private;
    21	
    22		memcpy(buf, dump->buf + fpos, count);
    23		return count;
    24	}
    25	
    26	static struct kobject *crashdd_mkdir(const char *name)
    27	{
  > 28		return kobject_create_and_add(name, crashdd_kobj);
    29	}
    30	
    31	static int crashdd_add_file(struct kobject *kobj, const char *name,
    32				    struct crashdd_dump_node *dump)
    33	{
    34		dump->bin_attr.attr.name = name;
    35		dump->bin_attr.attr.mode = CRASHDD_SYSFS_MODE;
    36		dump->bin_attr.size = dump->size;
    37		dump->bin_attr.read = crashdd_read;
    38		dump->bin_attr.private = dump;
    39	
    40		return sysfs_create_bin_file(kobj, &dump->bin_attr);
    41	}
    42	
    43	static void crashdd_rmdir(struct kobject *kobj)
    44	{
  > 45		kobject_put(kobj);
    46	}
    47	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kernel test robot March 24, 2018, 12:42 p.m. UTC | #2
Hi Rahul,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Rahul-Lakkireddy/fs-crashdd-add-API-to-collect-hardware-dump-in-second-kernel/20180324-193856
config: i386-randconfig-n0-201811 (attached as .config)
compiler: gcc-7 (Debian 7.3.0-1) 7.3.0
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All error/warnings (new ones prefixed by >>):

   In file included from fs/crashdd/crashdd.c:8:0:
>> fs/crashdd/crashdd_internal.h:13:23: error: field 'bin_attr' has incomplete type
     struct bin_attribute bin_attr; /* Binary dump file's attributes */
                          ^~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_read':
>> fs/crashdd/crashdd.c:20:43: error: dereferencing pointer to incomplete type 'struct bin_attribute'
     struct crashdd_dump_node *dump = bin_attr->private;
                                              ^~
   fs/crashdd/crashdd.c: In function 'crashdd_mkdir':
>> fs/crashdd/crashdd.c:28:9: error: implicit declaration of function 'kobject_create_and_add'; did you mean 'proc_create_data'? [-Werror=implicit-function-declaration]
     return kobject_create_and_add(name, crashdd_kobj);
            ^~~~~~~~~~~~~~~~~~~~~~
            proc_create_data
>> fs/crashdd/crashdd.c:28:9: warning: return makes pointer from integer without a cast [-Wint-conversion]
     return kobject_create_and_add(name, crashdd_kobj);
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_add_file':
>> fs/crashdd/crashdd.c:40:9: error: implicit declaration of function 'sysfs_create_bin_file' [-Werror=implicit-function-declaration]
     return sysfs_create_bin_file(kobj, &dump->bin_attr);
            ^~~~~~~~~~~~~~~~~~~~~
   fs/crashdd/crashdd.c: In function 'crashdd_rmdir':
>> fs/crashdd/crashdd.c:45:2: error: implicit declaration of function 'kobject_put'; did you mean 'kref_put'? [-Werror=implicit-function-declaration]
     kobject_put(kobj);
     ^~~~~~~~~~~
     kref_put
   fs/crashdd/crashdd.c: In function 'crashdd_get_driver':
>> fs/crashdd/crashdd.c:102:25: error: dereferencing pointer to incomplete type 'struct kobject'
      if (!strcmp(node->kobj->name, name)) {
                            ^~
   fs/crashdd/crashdd.c: In function 'crashdd_init':
>> fs/crashdd/crashdd.c:228:51: error: 'kernel_kobj' undeclared (first use in this function); did you mean 'kernel_read'?
     crashdd_kobj = kobject_create_and_add("crashdd", kernel_kobj);
                                                      ^~~~~~~~~~~
                                                      kernel_read
   fs/crashdd/crashdd.c:228:51: note: each undeclared identifier is reported only once for each function it appears in
   fs/crashdd/crashdd.c: In function 'crashdd_add_file':
>> fs/crashdd/crashdd.c:41:1: warning: control reaches end of non-void function [-Wreturn-type]
    }
    ^
   cc1: some warnings being treated as errors

vim +/bin_attr +13 fs/crashdd/crashdd_internal.h

     6	
     7	/* Binary dump file's context internal to crashdd */
     8	struct crashdd_dump_node {
     9		/* Pointer to list of dumps under the driver sysfs entry */
    10		struct list_head list;
    11		void *buf;                     /* Buffer containing device's dump */
    12		unsigned long size;            /* Size of the buffer */
  > 13		struct bin_attribute bin_attr; /* Binary dump file's attributes */
    14	};
    15	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/fs/Kconfig b/fs/Kconfig
index bc821a86d965..aae1c55a7dad 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -208,6 +208,7 @@  config ARCH_HAS_GIGANTIC_PAGE
 
 source "fs/configfs/Kconfig"
 source "fs/efivarfs/Kconfig"
+source "fs/crashdd/Kconfig"
 
 endmenu
 
diff --git a/fs/Makefile b/fs/Makefile
index add789ea270a..ff398a44f611 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -128,3 +128,4 @@  obj-y				+= exofs/ # Multiple modules
 obj-$(CONFIG_CEPH_FS)		+= ceph/
 obj-$(CONFIG_PSTORE)		+= pstore/
 obj-$(CONFIG_EFIVAR_FS)		+= efivarfs/
+obj-$(CONFIG_CRASH_DEVICE_DUMP)	+= crashdd/
diff --git a/fs/crashdd/Kconfig b/fs/crashdd/Kconfig
new file mode 100644
index 000000000000..5db9c7c98c17
--- /dev/null
+++ b/fs/crashdd/Kconfig
@@ -0,0 +1,10 @@ 
+config CRASH_DEVICE_DUMP
+	bool "Crash Kernel Device Hardware/Firmware Logs"
+	depends on SYSFS && CRASH_DUMP
+	default y
+	---help---
+	  Device drivers can collect the device specific snapshot of
+	  their hardware or firmware before they are initialized in
+	  crash recovery kernel. If you say Y here a tree of device
+	  specific dumps will be made available under /sys/kernel/crashdd/
+	  directory.
diff --git a/fs/crashdd/Makefile b/fs/crashdd/Makefile
new file mode 100644
index 000000000000..8dbf946c0ea4
--- /dev/null
+++ b/fs/crashdd/Makefile
@@ -0,0 +1,3 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y := crashdd.o
diff --git a/fs/crashdd/crashdd.c b/fs/crashdd/crashdd.c
new file mode 100644
index 000000000000..73882ff7722e
--- /dev/null
+++ b/fs/crashdd/crashdd.c
@@ -0,0 +1,234 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2018 Chelsio Communications, Inc. All rights reserved. */
+
+#include <linux/vmalloc.h>
+#include <linux/crash_dump.h>
+#include <linux/crashdd.h>
+
+#include "crashdd_internal.h"
+
+static LIST_HEAD(crashdd_list);
+static DEFINE_MUTEX(crashdd_mutex);
+
+#define CRASHDD_SYSFS_MODE 444 /* S_IRUGO */
+static struct kobject *crashdd_kobj;
+
+static ssize_t crashdd_read(struct file *filp, struct kobject *kobj,
+			    struct bin_attribute *bin_attr,
+			    char *buf, loff_t fpos, size_t count)
+{
+	struct crashdd_dump_node *dump = bin_attr->private;
+
+	memcpy(buf, dump->buf + fpos, count);
+	return count;
+}
+
+static struct kobject *crashdd_mkdir(const char *name)
+{
+	return kobject_create_and_add(name, crashdd_kobj);
+}
+
+static int crashdd_add_file(struct kobject *kobj, const char *name,
+			    struct crashdd_dump_node *dump)
+{
+	dump->bin_attr.attr.name = name;
+	dump->bin_attr.attr.mode = CRASHDD_SYSFS_MODE;
+	dump->bin_attr.size = dump->size;
+	dump->bin_attr.read = crashdd_read;
+	dump->bin_attr.private = dump;
+
+	return sysfs_create_bin_file(kobj, &dump->bin_attr);
+}
+
+static void crashdd_rmdir(struct kobject *kobj)
+{
+	kobject_put(kobj);
+}
+
+/**
+ * crashdd_init_driver - create a sysfs driver entry.
+ * @name: Name of the directory.
+ *
+ * Creates a directory under /sys/kernel/crashdd/ with @name.  Allocates
+ * and saves the sysfs entry.  The sysfs entry is added to the global
+ * list and then returned to the caller. On failure, returns NULL.
+ */
+static struct crashdd_driver_node *crashdd_init_driver(const char *name)
+{
+	struct crashdd_driver_node *node;
+
+	node = vzalloc(sizeof(*node));
+	if (!node)
+		return NULL;
+
+	/* Create a driver's directory under /sys/kernel/crashdd/ */
+	node->kobj = crashdd_mkdir(name);
+	if (!node->kobj) {
+		vfree(node);
+		return NULL;
+	}
+
+	atomic_set(&node->refcnt, 1);
+
+	/* Initialize the list of dumps that go under this driver's
+	 * directory.
+	 */
+	INIT_LIST_HEAD(&node->dump_list);
+
+	/* Add the driver's entry to global list */
+	mutex_lock(&crashdd_mutex);
+	list_add_tail(&node->list, &crashdd_list);
+	mutex_unlock(&crashdd_mutex);
+
+	return node;
+}
+
+/**
+ * crashdd_get_driver - get an exisiting sysfs driver entry.
+ * @name: Name of the directory.
+ *
+ * Searches and fetches a sysfs entry having @name.  If @name is
+ * found, then the reference count is incremented and the entry
+ * is returned.  If @name is not found, NULL is returned.
+ */
+static struct crashdd_driver_node *crashdd_get_driver(const char *name)
+{
+	struct crashdd_driver_node *node;
+	int found = 0;
+
+	/* Search for an existing driver sysfs entry having @name */
+	mutex_lock(&crashdd_mutex);
+	list_for_each_entry(node, &crashdd_list, list) {
+		if (!strcmp(node->kobj->name, name)) {
+			atomic_inc(&node->refcnt);
+			found = 1;
+			break;
+		}
+	}
+	mutex_unlock(&crashdd_mutex);
+
+	if (found)
+		return node;
+
+	/* No driver with @name found */
+	return NULL;
+}
+
+/**
+ * crashdd_put_driver - put an exisiting sysfs driver entry.
+ * @node: driver sysfs entry.
+ *
+ * Decrement @node reference count.  If there are no dumps left under it,
+ * delete the sysfs directory and remove it from the global list.
+ */
+static void crashdd_put_driver(struct crashdd_driver_node *node)
+{
+	mutex_lock(&crashdd_mutex);
+	if (atomic_dec_and_test(&node->refcnt)) {
+		/* Delete @node driver entry if it has no dumps under it */
+		crashdd_rmdir(node->kobj);
+		list_del(&node->list);
+	}
+	mutex_unlock(&crashdd_mutex);
+}
+
+/**
+ * crashdd_add_dump - Allocate a directory under /sys/kernel/crashdd/ and
+ * add the dump to it.
+ * @driver_name: directory name under which the dump should be added.
+ * @data: dump info.
+ *
+ * Search for /sys/kernel/crashdd/<@driver_name>/ directory.  If not found,
+ * allocate a new directory under /sys/kernel/crashdd/ with @driver_name.
+ * Allocate the dump file's context and invoke the calling driver's dump
+ * collect routine.  Once collection is done, add the dump under
+ * /sys/kernel/crashdd/<@driver_name>/ directory.
+ */
+int crashdd_add_dump(const char *driver_name, struct crashdd_data *data)
+{
+	struct crashdd_driver_node *node;
+	struct crashdd_dump_node *dump;
+	void *buf = NULL;
+	int ret;
+
+	if (!driver_name || !strlen(driver_name) ||
+	    !data || !strlen(data->name) ||
+	    !data->crashdd_callback || !data->size)
+		return -EINVAL;
+
+	/* Get a driver sysfs entry with specified name. */
+	node = crashdd_get_driver(driver_name);
+	if (!node) {
+		/* No driver sysfs entry found with specified name.
+		 * So create a new one
+		 */
+		node = crashdd_init_driver(driver_name);
+		if (!node)
+			return -ENOMEM;
+	}
+
+	dump = vzalloc(sizeof(*dump));
+	if (!dump) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	/* Allocate buffer for driver's to write their dumps */
+	buf = vzalloc(data->size);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	/* Invoke the driver's dump collection routing */
+	ret = data->crashdd_callback(data, buf);
+	if (ret)
+		goto out_err;
+
+	dump->buf = buf;
+	dump->size = data->size;
+
+	/* Add a binary file under /sys/kernel/crashdd/@driver_name/ */
+	ret = crashdd_add_file(node->kobj, data->name, dump);
+	if (ret)
+		goto out_err;
+
+	/* Add the dump to driver sysfs list */
+	mutex_lock(&crashdd_mutex);
+	list_add_tail(&dump->list, &node->dump_list);
+	atomic_inc(&node->refcnt);
+	mutex_unlock(&crashdd_mutex);
+
+	/* Return back the driver sysfs reference */
+	crashdd_put_driver(node);
+	return 0;
+
+out_err:
+	if (buf)
+		vfree(buf);
+
+	if (dump)
+		vfree(dump);
+
+	crashdd_put_driver(node);
+	return ret;
+}
+EXPORT_SYMBOL(crashdd_add_dump);
+
+/* Init function for crash device dump module. */
+static int __init crashdd_init(void)
+{
+	/*
+	 * Only export this directory in 2nd kernel.
+	 */
+	if (!is_kdump_kernel())
+		return 0;
+
+	/* Create /sys/kernel/crashdd/ directory */
+	crashdd_kobj = kobject_create_and_add("crashdd", kernel_kobj);
+	if (!crashdd_kobj)
+		return -ENOMEM;
+
+	return 0;
+}
+fs_initcall(crashdd_init);
diff --git a/fs/crashdd/crashdd_internal.h b/fs/crashdd/crashdd_internal.h
new file mode 100644
index 000000000000..9162d1a4264b
--- /dev/null
+++ b/fs/crashdd/crashdd_internal.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2018 Chelsio Communications, Inc. All rights reserved. */
+
+#ifndef CRASH_DEVICE_DUMP_INTERNAL_H
+#define CRASH_DEVICE_DUMP_INTERNAL_H
+
+/* Binary dump file's context internal to crashdd */
+struct crashdd_dump_node {
+	/* Pointer to list of dumps under the driver sysfs entry */
+	struct list_head list;
+	void *buf;                     /* Buffer containing device's dump */
+	unsigned long size;            /* Size of the buffer */
+	struct bin_attribute bin_attr; /* Binary dump file's attributes */
+};
+
+/* Driver sysfs entry internal to crashdd */
+struct crashdd_driver_node {
+	/* Pointer to global list of driver sysfs entries */
+	struct list_head list;
+	struct list_head dump_list; /* List of dumps under this driver */
+	atomic_t refcnt;            /* Number of dumps under this directory */
+	struct kobject *kobj;       /* Pointer to driver sysfs kobject */
+};
+#endif /* CRASH_DEVICE_DUMP_INTERNAL_H */
diff --git a/include/linux/crashdd.h b/include/linux/crashdd.h
new file mode 100644
index 000000000000..edaba8424019
--- /dev/null
+++ b/include/linux/crashdd.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2018 Chelsio Communications, Inc. All rights reserved. */
+
+#ifndef CRASH_DEVICE_DUMP_H
+#define CRASH_DEVICE_DUMP_H
+
+/* Max dump name length */
+#define CRASHDD_NAME_LENGTH 32
+
+/* Device Dump information to be filled by drivers */
+struct crashdd_data {
+	char name[CRASHDD_NAME_LENGTH]; /* Unique name of the dump */
+	unsigned long size;             /* Size of the dump */
+	/* Driver's registered callback to be invoked to collect dump */
+	int (*crashdd_callback)(struct crashdd_data *data, void *buf);
+};
+
+#ifdef CONFIG_CRASH_DEVICE_DUMP
+int crashdd_add_dump(const char *driver_name, struct crashdd_data *data);
+#else
+#define crashdd_add_dump(x, y) 0
+#endif /* CONFIG_CRASH_DEVICE_DUMP */
+
+#endif /* CRASH_DEVICE_DUMP_H */