[PATCH v8 1/2] devcoredump: add new APIs to support migration of users from old device coredump related APIs

From: Duoming Zhou
Date: Tue Aug 23 2022 - 10:19:19 EST


The dev_coredumpv(), dev_coredumpm() and dev_coredumpsg() could not
be used in atomic context, because they call kvasprintf_const() and
kstrdup() with GFP_KERNEL parameter. The process is shown below:

dev_coredumpv(.., gfp_t gfp)
dev_coredumpm(.., gfp_t gfp)
dev_set_name
kobject_set_name_vargs
kvasprintf_const(GFP_KERNEL, ...); //may sleep
kstrdup(s, GFP_KERNEL); //may sleep

This patch adds new APIs that remove gfp_t parameter of dev_coredumpv(),
dev_coredumpm() and dev_coredumpsg() in order to show they could not be
used in atomic context.

These new APIs will ultimately replace the dev_coredumpv(), dev_coredumpm()
and dev_coredumpsg() when there are no users of the old APIs.

Fixes: 833c95456a70 ("device coredump: add new device coredump class")
Signed-off-by: Duoming Zhou <duoming@xxxxxxxxxx>
---
Changes in v8:
- add new APIs to prepare migration of users from old device coredump related APIs.

drivers/base/devcoredump.c | 116 ++++++++++++++++++++++++++++++++++++
include/linux/devcoredump.h | 34 +++++++++++
2 files changed, 150 insertions(+)

diff --git a/drivers/base/devcoredump.c b/drivers/base/devcoredump.c
index f4d794d6bb8..728457b12ce 100644
--- a/drivers/base/devcoredump.c
+++ b/drivers/base/devcoredump.c
@@ -185,6 +185,21 @@ void dev_coredumpv(struct device *dev, void *data, size_t datalen,
}
EXPORT_SYMBOL_GPL(dev_coredumpv);

+/**
+ * dev_core_dumpv - create device coredump with vmalloc data
+ * @dev: the struct device for the crashed device
+ * @data: vmalloc data containing the device coredump
+ * @datalen: length of the data
+ *
+ * This function takes ownership of the vmalloc'ed data and will free
+ * it when it is no longer used. See dev_core_dumpm() for more information.
+ */
+void dev_core_dumpv(struct device *dev, void *data, size_t datalen)
+{
+ dev_core_dumpm(dev, NULL, data, datalen, devcd_readv, devcd_freev);
+}
+EXPORT_SYMBOL_GPL(dev_core_dumpv);
+
static int devcd_match_failing(struct device *dev, const void *failing)
{
struct devcd_entry *devcd = dev_to_devcd(dev);
@@ -312,6 +327,87 @@ void dev_coredumpm(struct device *dev, struct module *owner,
}
EXPORT_SYMBOL_GPL(dev_coredumpm);

+/**
+ * dev_core_dumpm - create device coredump with read/free methods
+ * @dev: the struct device for the crashed device
+ * @owner: the module that contains the read/free functions, use %THIS_MODULE
+ * @data: data cookie for the @read/@free functions
+ * @datalen: length of the data
+ * @read: function to read from the given buffer
+ * @free: function to free the given buffer
+ *
+ * Creates a new device coredump for the given device. If a previous one hasn't
+ * been read yet, the new coredump is discarded. The data lifetime is determined
+ * by the device coredump framework and when it is no longer needed the @free
+ * function will be called to free the data.
+ */
+void dev_core_dumpm(struct device *dev, struct module *owner,
+ void *data, size_t datalen,
+ ssize_t (*read)(char *buffer, loff_t offset, size_t count,
+ void *data, size_t datalen),
+ void (*free)(void *data))
+{
+ static atomic_t devcd_count = ATOMIC_INIT(0);
+ struct devcd_entry *devcd;
+ struct device *existing;
+
+ if (devcd_disabled)
+ goto free;
+
+ existing = class_find_device(&devcd_class, NULL, dev,
+ devcd_match_failing);
+ if (existing) {
+ put_device(existing);
+ goto free;
+ }
+
+ if (!try_module_get(owner))
+ goto free;
+
+ devcd = kzalloc(sizeof(*devcd), GFP_KERNEL);
+ if (!devcd)
+ goto put_module;
+
+ devcd->owner = owner;
+ devcd->data = data;
+ devcd->datalen = datalen;
+ devcd->read = read;
+ devcd->free = free;
+ devcd->failing_dev = get_device(dev);
+
+ device_initialize(&devcd->devcd_dev);
+
+ dev_set_name(&devcd->devcd_dev, "devcd%d",
+ atomic_inc_return(&devcd_count));
+ devcd->devcd_dev.class = &devcd_class;
+
+ if (device_add(&devcd->devcd_dev))
+ goto put_device;
+
+ /*
+ * These should normally not fail, but there is no problem
+ * continuing without the links, so just warn instead of
+ * failing.
+ */
+ if (sysfs_create_link(&devcd->devcd_dev.kobj, &dev->kobj,
+ "failing_device") ||
+ sysfs_create_link(&dev->kobj, &devcd->devcd_dev.kobj,
+ "devcoredump"))
+ dev_warn(dev, "devcoredump create_link failed\n");
+
+ INIT_DELAYED_WORK(&devcd->del_wk, devcd_del);
+ schedule_delayed_work(&devcd->del_wk, DEVCD_TIMEOUT);
+
+ return;
+ put_device:
+ put_device(&devcd->devcd_dev);
+ put_module:
+ module_put(owner);
+ free:
+ free(data);
+}
+EXPORT_SYMBOL_GPL(dev_core_dumpm);
+
/**
* dev_coredumpsg - create device coredump that uses scatterlist as data
* parameter
@@ -333,6 +429,26 @@ void dev_coredumpsg(struct device *dev, struct scatterlist *table,
}
EXPORT_SYMBOL_GPL(dev_coredumpsg);

+/**
+ * dev_core_dumpsg - create device coredump that uses scatterlist as data
+ * parameter
+ * @dev: the struct device for the crashed device
+ * @table: the dump data
+ * @datalen: length of the data
+ *
+ * Creates a new device coredump for the given device. If a previous one hasn't
+ * been read yet, the new coredump is discarded. The data lifetime is determined
+ * by the device coredump framework and when it is no longer needed
+ * it will free the data.
+ */
+void dev_core_dumpsg(struct device *dev, struct scatterlist *table,
+ size_t datalen)
+{
+ dev_core_dumpm(dev, NULL, table, datalen, devcd_read_from_sgtable,
+ devcd_free_sgtable);
+}
+EXPORT_SYMBOL_GPL(dev_core_dumpsg);
+
static int __init devcoredump_init(void)
{
return class_register(&devcd_class);
diff --git a/include/linux/devcoredump.h b/include/linux/devcoredump.h
index c008169ed2c..113fe63800a 100644
--- a/include/linux/devcoredump.h
+++ b/include/linux/devcoredump.h
@@ -55,14 +55,26 @@ static inline void _devcd_free_sgtable(struct scatterlist *table)
void dev_coredumpv(struct device *dev, void *data, size_t datalen,
gfp_t gfp);

+void dev_core_dumpv(struct device *dev, void *data, size_t datalen);
+
void dev_coredumpm(struct device *dev, struct module *owner,
void *data, size_t datalen, gfp_t gfp,
ssize_t (*read)(char *buffer, loff_t offset, size_t count,
void *data, size_t datalen),
void (*free)(void *data));

+void dev_core_dumpm(struct device *dev, struct module *owner,
+ void *data, size_t datalen,
+ ssize_t (*read)(char *buffer, loff_t offset, size_t count,
+ void *data, size_t datalen),
+ void (*free)(void *data));
+
void dev_coredumpsg(struct device *dev, struct scatterlist *table,
size_t datalen, gfp_t gfp);
+
+void dev_core_dumpsg(struct device *dev, struct scatterlist *table,
+ size_t datalen);
+
#else
static inline void dev_coredumpv(struct device *dev, void *data,
size_t datalen, gfp_t gfp)
@@ -70,6 +82,12 @@ static inline void dev_coredumpv(struct device *dev, void *data,
vfree(data);
}

+static inline void dev_core_dumpv(struct device *dev, void *data,
+ size_t datalen)
+{
+ vfree(data);
+}
+
static inline void
dev_coredumpm(struct device *dev, struct module *owner,
void *data, size_t datalen, gfp_t gfp,
@@ -80,11 +98,27 @@ dev_coredumpm(struct device *dev, struct module *owner,
free(data);
}

+static inline void
+dev_core_dumpm(struct device *dev, struct module *owner,
+ void *data, size_t datalen,
+ ssize_t (*read)(char *buffer, loff_t offset, size_t count,
+ void *data, size_t datalen),
+ void (*free)(void *data))
+{
+ free(data);
+}
+
static inline void dev_coredumpsg(struct device *dev, struct scatterlist *table,
size_t datalen, gfp_t gfp)
{
_devcd_free_sgtable(table);
}
+
+static inline void dev_core_dumpsg(struct device *dev, struct scatterlist *table,
+ size_t datalen)
+{
+ _devcd_free_sgtable(table);
+}
#endif /* CONFIG_DEV_COREDUMP */

#endif /* __DEVCOREDUMP_H */
--
2.17.1