mirror of
https://github.com/torvalds/linux.git
synced 2025-11-03 01:59:51 +02:00
The original code causes a circular locking dependency found by lockdep.
======================================================
WARNING: possible circular locking dependency detected
6.16.0-rc6-lgci-xe-xe-pw-151626v3+ #1 Tainted: G S U
------------------------------------------------------
xe_fault_inject/5091 is trying to acquire lock:
ffff888156815688 ((work_completion)(&(&devcd->del_wk)->work)){+.+.}-{0:0}, at: __flush_work+0x25d/0x660
but task is already holding lock:
ffff888156815620 (&devcd->mutex){+.+.}-{3:3}, at: dev_coredump_put+0x3f/0xa0
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #2 (&devcd->mutex){+.+.}-{3:3}:
mutex_lock_nested+0x4e/0xc0
devcd_data_write+0x27/0x90
sysfs_kf_bin_write+0x80/0xf0
kernfs_fop_write_iter+0x169/0x220
vfs_write+0x293/0x560
ksys_write+0x72/0xf0
__x64_sys_write+0x19/0x30
x64_sys_call+0x2bf/0x2660
do_syscall_64+0x93/0xb60
entry_SYSCALL_64_after_hwframe+0x76/0x7e
-> #1 (kn->active#236){++++}-{0:0}:
kernfs_drain+0x1e2/0x200
__kernfs_remove+0xae/0x400
kernfs_remove_by_name_ns+0x5d/0xc0
remove_files+0x54/0x70
sysfs_remove_group+0x3d/0xa0
sysfs_remove_groups+0x2e/0x60
device_remove_attrs+0xc7/0x100
device_del+0x15d/0x3b0
devcd_del+0x19/0x30
process_one_work+0x22b/0x6f0
worker_thread+0x1e8/0x3d0
kthread+0x11c/0x250
ret_from_fork+0x26c/0x2e0
ret_from_fork_asm+0x1a/0x30
-> #0 ((work_completion)(&(&devcd->del_wk)->work)){+.+.}-{0:0}:
__lock_acquire+0x1661/0x2860
lock_acquire+0xc4/0x2f0
__flush_work+0x27a/0x660
flush_delayed_work+0x5d/0xa0
dev_coredump_put+0x63/0xa0
xe_driver_devcoredump_fini+0x12/0x20 [xe]
devm_action_release+0x12/0x30
release_nodes+0x3a/0x120
devres_release_all+0x8a/0xd0
device_unbind_cleanup+0x12/0x80
device_release_driver_internal+0x23a/0x280
device_driver_detach+0x14/0x20
unbind_store+0xaf/0xc0
drv_attr_store+0x21/0x50
sysfs_kf_write+0x4a/0x80
kernfs_fop_write_iter+0x169/0x220
vfs_write+0x293/0x560
ksys_write+0x72/0xf0
__x64_sys_write+0x19/0x30
x64_sys_call+0x2bf/0x2660
do_syscall_64+0x93/0xb60
entry_SYSCALL_64_after_hwframe+0x76/0x7e
other info that might help us debug this:
Chain exists of: (work_completion)(&(&devcd->del_wk)->work) --> kn->active#236 --> &devcd->mutex
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&devcd->mutex);
lock(kn->active#236);
lock(&devcd->mutex);
lock((work_completion)(&(&devcd->del_wk)->work));
*** DEADLOCK ***
5 locks held by xe_fault_inject/5091:
#0: ffff8881129f9488 (sb_writers#5){.+.+}-{0:0}, at: ksys_write+0x72/0xf0
#1: ffff88810c755078 (&of->mutex#2){+.+.}-{3:3}, at: kernfs_fop_write_iter+0x123/0x220
#2: ffff8881054811a0 (&dev->mutex){....}-{3:3}, at: device_release_driver_internal+0x55/0x280
#3: ffff888156815620 (&devcd->mutex){+.+.}-{3:3}, at: dev_coredump_put+0x3f/0xa0
#4: ffffffff8359e020 (rcu_read_lock){....}-{1:2}, at: __flush_work+0x72/0x660
stack backtrace:
CPU: 14 UID: 0 PID: 5091 Comm: xe_fault_inject Tainted: G S U 6.16.0-rc6-lgci-xe-xe-pw-151626v3+ #1 PREEMPT_{RT,(lazy)}
Tainted: [S]=CPU_OUT_OF_SPEC, [U]=USER
Hardware name: Micro-Star International Co., Ltd. MS-7D25/PRO Z690-A DDR4(MS-7D25), BIOS 1.10 12/13/2021
Call Trace:
<TASK>
dump_stack_lvl+0x91/0xf0
dump_stack+0x10/0x20
print_circular_bug+0x285/0x360
check_noncircular+0x135/0x150
? register_lock_class+0x48/0x4a0
__lock_acquire+0x1661/0x2860
lock_acquire+0xc4/0x2f0
? __flush_work+0x25d/0x660
? mark_held_locks+0x46/0x90
? __flush_work+0x25d/0x660
__flush_work+0x27a/0x660
? __flush_work+0x25d/0x660
? trace_hardirqs_on+0x1e/0xd0
? __pfx_wq_barrier_func+0x10/0x10
flush_delayed_work+0x5d/0xa0
dev_coredump_put+0x63/0xa0
xe_driver_devcoredump_fini+0x12/0x20 [xe]
devm_action_release+0x12/0x30
release_nodes+0x3a/0x120
devres_release_all+0x8a/0xd0
device_unbind_cleanup+0x12/0x80
device_release_driver_internal+0x23a/0x280
? bus_find_device+0xa8/0xe0
device_driver_detach+0x14/0x20
unbind_store+0xaf/0xc0
drv_attr_store+0x21/0x50
sysfs_kf_write+0x4a/0x80
kernfs_fop_write_iter+0x169/0x220
vfs_write+0x293/0x560
ksys_write+0x72/0xf0
__x64_sys_write+0x19/0x30
x64_sys_call+0x2bf/0x2660
do_syscall_64+0x93/0xb60
? __f_unlock_pos+0x15/0x20
? __x64_sys_getdents64+0x9b/0x130
? __pfx_filldir64+0x10/0x10
? do_syscall_64+0x1a2/0xb60
? clear_bhb_loop+0x30/0x80
? clear_bhb_loop+0x30/0x80
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x76e292edd574
Code: c7 00 16 00 00 00 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 80 3d d5 ea 0e 00 00 74 13 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 54 c3 0f 1f 00 55 48 89 e5 48 83 ec 20 48 89
RSP: 002b:00007fffe247a828 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 000076e292edd574
RDX: 000000000000000c RSI: 00006267f6306063 RDI: 000000000000000b
RBP: 000000000000000c R08: 000076e292fc4b20 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000202 R12: 00006267f6306063
R13: 000000000000000b R14: 00006267e6859c00 R15: 000076e29322a000
</TASK>
xe 0000:03:00.0: [drm] Xe device coredump has been deleted.
Fixes: 01daccf748 ("devcoredump : Serialize devcd_del work")
Cc: Mukesh Ojha <quic_mojha@quicinc.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Johannes Berg <johannes@sipsolutions.net>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: linux-kernel@vger.kernel.org
Cc: stable@vger.kernel.org # v6.1+
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
Cc: Matthew Brost <matthew.brost@intel.com>
Acked-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250723142416.1020423-1-dev@lankhorst.se
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
480 lines
14 KiB
C
480 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright(c) 2014 Intel Mobile Communications GmbH
|
|
* Copyright(c) 2015 Intel Deutschland GmbH
|
|
*
|
|
* Author: Johannes Berg <johannes@sipsolutions.net>
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/devcoredump.h>
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
static struct class devcd_class;
|
|
|
|
/* global disable flag, for security purposes */
|
|
static bool devcd_disabled;
|
|
|
|
struct devcd_entry {
|
|
struct device devcd_dev;
|
|
void *data;
|
|
size_t datalen;
|
|
/*
|
|
* There are 2 races for which mutex is required.
|
|
*
|
|
* The first race is between device creation and userspace writing to
|
|
* schedule immediately destruction.
|
|
*
|
|
* This race is handled by arming the timer before device creation, but
|
|
* when device creation fails the timer still exists.
|
|
*
|
|
* To solve this, hold the mutex during device_add(), and set
|
|
* init_completed on success before releasing the mutex.
|
|
*
|
|
* That way the timer will never fire until device_add() is called,
|
|
* it will do nothing if init_completed is not set. The timer is also
|
|
* cancelled in that case.
|
|
*
|
|
* The second race involves multiple parallel invocations of devcd_free(),
|
|
* add a deleted flag so only 1 can call the destructor.
|
|
*/
|
|
struct mutex mutex;
|
|
bool init_completed, deleted;
|
|
struct module *owner;
|
|
ssize_t (*read)(char *buffer, loff_t offset, size_t count,
|
|
void *data, size_t datalen);
|
|
void (*free)(void *data);
|
|
/*
|
|
* If nothing interferes and device_add() was returns success,
|
|
* del_wk will destroy the device after the timer fires.
|
|
*
|
|
* Multiple userspace processes can interfere in the working of the timer:
|
|
* - Writing to the coredump will reschedule the timer to run immediately,
|
|
* if still armed.
|
|
*
|
|
* This is handled by using "if (cancel_delayed_work()) {
|
|
* schedule_delayed_work() }", to prevent re-arming after having
|
|
* been previously fired.
|
|
* - Writing to /sys/class/devcoredump/disabled will destroy the
|
|
* coredump synchronously.
|
|
* This is handled by using disable_delayed_work_sync(), and then
|
|
* checking if deleted flag is set with &devcd->mutex held.
|
|
*/
|
|
struct delayed_work del_wk;
|
|
struct device *failing_dev;
|
|
};
|
|
|
|
static struct devcd_entry *dev_to_devcd(struct device *dev)
|
|
{
|
|
return container_of(dev, struct devcd_entry, devcd_dev);
|
|
}
|
|
|
|
static void devcd_dev_release(struct device *dev)
|
|
{
|
|
struct devcd_entry *devcd = dev_to_devcd(dev);
|
|
|
|
devcd->free(devcd->data);
|
|
module_put(devcd->owner);
|
|
|
|
/*
|
|
* this seems racy, but I don't see a notifier or such on
|
|
* a struct device to know when it goes away?
|
|
*/
|
|
if (devcd->failing_dev->kobj.sd)
|
|
sysfs_delete_link(&devcd->failing_dev->kobj, &dev->kobj,
|
|
"devcoredump");
|
|
|
|
put_device(devcd->failing_dev);
|
|
kfree(devcd);
|
|
}
|
|
|
|
static void __devcd_del(struct devcd_entry *devcd)
|
|
{
|
|
devcd->deleted = true;
|
|
device_del(&devcd->devcd_dev);
|
|
put_device(&devcd->devcd_dev);
|
|
}
|
|
|
|
static void devcd_del(struct work_struct *wk)
|
|
{
|
|
struct devcd_entry *devcd;
|
|
bool init_completed;
|
|
|
|
devcd = container_of(wk, struct devcd_entry, del_wk.work);
|
|
|
|
/* devcd->mutex serializes against dev_coredumpm_timeout */
|
|
mutex_lock(&devcd->mutex);
|
|
init_completed = devcd->init_completed;
|
|
mutex_unlock(&devcd->mutex);
|
|
|
|
if (init_completed)
|
|
__devcd_del(devcd);
|
|
}
|
|
|
|
static ssize_t devcd_data_read(struct file *filp, struct kobject *kobj,
|
|
const struct bin_attribute *bin_attr,
|
|
char *buffer, loff_t offset, size_t count)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct devcd_entry *devcd = dev_to_devcd(dev);
|
|
|
|
return devcd->read(buffer, offset, count, devcd->data, devcd->datalen);
|
|
}
|
|
|
|
static ssize_t devcd_data_write(struct file *filp, struct kobject *kobj,
|
|
const struct bin_attribute *bin_attr,
|
|
char *buffer, loff_t offset, size_t count)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct devcd_entry *devcd = dev_to_devcd(dev);
|
|
|
|
/*
|
|
* Although it's tempting to use mod_delayed work here,
|
|
* that will cause a reschedule if the timer already fired.
|
|
*/
|
|
if (cancel_delayed_work(&devcd->del_wk))
|
|
schedule_delayed_work(&devcd->del_wk, 0);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct bin_attribute devcd_attr_data =
|
|
__BIN_ATTR(data, 0600, devcd_data_read, devcd_data_write, 0);
|
|
|
|
static const struct bin_attribute *const devcd_dev_bin_attrs[] = {
|
|
&devcd_attr_data, NULL,
|
|
};
|
|
|
|
static const struct attribute_group devcd_dev_group = {
|
|
.bin_attrs = devcd_dev_bin_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *devcd_dev_groups[] = {
|
|
&devcd_dev_group, NULL,
|
|
};
|
|
|
|
static int devcd_free(struct device *dev, void *data)
|
|
{
|
|
struct devcd_entry *devcd = dev_to_devcd(dev);
|
|
|
|
/*
|
|
* To prevent a race with devcd_data_write(), disable work and
|
|
* complete manually instead.
|
|
*
|
|
* We cannot rely on the return value of
|
|
* disable_delayed_work_sync() here, because it might be in the
|
|
* middle of a cancel_delayed_work + schedule_delayed_work pair.
|
|
*
|
|
* devcd->mutex here guards against multiple parallel invocations
|
|
* of devcd_free().
|
|
*/
|
|
disable_delayed_work_sync(&devcd->del_wk);
|
|
mutex_lock(&devcd->mutex);
|
|
if (!devcd->deleted)
|
|
__devcd_del(devcd);
|
|
mutex_unlock(&devcd->mutex);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t disabled_show(const struct class *class, const struct class_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sysfs_emit(buf, "%d\n", devcd_disabled);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* disabled_store() worker()
|
|
* class_for_each_device(&devcd_class,
|
|
* NULL, NULL, devcd_free)
|
|
* ...
|
|
* ...
|
|
* while ((dev = class_dev_iter_next(&iter))
|
|
* devcd_del()
|
|
* device_del()
|
|
* put_device() <- last reference
|
|
* error = fn(dev, data) devcd_dev_release()
|
|
* devcd_free(dev, data) kfree(devcd)
|
|
*
|
|
*
|
|
* In the above diagram, it looks like disabled_store() would be racing with parallelly
|
|
* running devcd_del() and result in memory abort after dropping its last reference with
|
|
* put_device(). However, this will not happens as fn(dev, data) runs
|
|
* with its own reference to device via klist_node so it is not its last reference.
|
|
* so, above situation would not occur.
|
|
*/
|
|
|
|
static ssize_t disabled_store(const struct class *class, const struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
long tmp = simple_strtol(buf, NULL, 10);
|
|
|
|
/*
|
|
* This essentially makes the attribute write-once, since you can't
|
|
* go back to not having it disabled. This is intentional, it serves
|
|
* as a system lockdown feature.
|
|
*/
|
|
if (tmp != 1)
|
|
return -EINVAL;
|
|
|
|
devcd_disabled = true;
|
|
|
|
class_for_each_device(&devcd_class, NULL, NULL, devcd_free);
|
|
|
|
return count;
|
|
}
|
|
static CLASS_ATTR_RW(disabled);
|
|
|
|
static struct attribute *devcd_class_attrs[] = {
|
|
&class_attr_disabled.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(devcd_class);
|
|
|
|
static struct class devcd_class = {
|
|
.name = "devcoredump",
|
|
.dev_release = devcd_dev_release,
|
|
.dev_groups = devcd_dev_groups,
|
|
.class_groups = devcd_class_groups,
|
|
};
|
|
|
|
static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count,
|
|
void *data, size_t datalen)
|
|
{
|
|
return memory_read_from_buffer(buffer, count, &offset, data, datalen);
|
|
}
|
|
|
|
static void devcd_freev(void *data)
|
|
{
|
|
vfree(data);
|
|
}
|
|
|
|
/**
|
|
* dev_coredumpv - 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
|
|
* @gfp: allocation flags
|
|
*
|
|
* This function takes ownership of the vmalloc'ed data and will free
|
|
* it when it is no longer used. See dev_coredumpm() for more information.
|
|
*/
|
|
void dev_coredumpv(struct device *dev, void *data, size_t datalen,
|
|
gfp_t gfp)
|
|
{
|
|
dev_coredumpm(dev, NULL, data, datalen, gfp, devcd_readv, devcd_freev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_coredumpv);
|
|
|
|
static int devcd_match_failing(struct device *dev, const void *failing)
|
|
{
|
|
struct devcd_entry *devcd = dev_to_devcd(dev);
|
|
|
|
return devcd->failing_dev == failing;
|
|
}
|
|
|
|
/**
|
|
* devcd_free_sgtable - free all the memory of the given scatterlist table
|
|
* (i.e. both pages and scatterlist instances)
|
|
* NOTE: if two tables allocated with devcd_alloc_sgtable and then chained
|
|
* using the sg_chain function then that function should be called only once
|
|
* on the chained table
|
|
* @data: pointer to sg_table to free
|
|
*/
|
|
static void devcd_free_sgtable(void *data)
|
|
{
|
|
_devcd_free_sgtable(data);
|
|
}
|
|
|
|
/**
|
|
* devcd_read_from_sgtable - copy data from sg_table to a given buffer
|
|
* and return the number of bytes read
|
|
* @buffer: the buffer to copy the data to it
|
|
* @buf_len: the length of the buffer
|
|
* @data: the scatterlist table to copy from
|
|
* @offset: start copy from @offset@ bytes from the head of the data
|
|
* in the given scatterlist
|
|
* @data_len: the length of the data in the sg_table
|
|
*
|
|
* Returns: the number of bytes copied
|
|
*/
|
|
static ssize_t devcd_read_from_sgtable(char *buffer, loff_t offset,
|
|
size_t buf_len, void *data,
|
|
size_t data_len)
|
|
{
|
|
struct scatterlist *table = data;
|
|
|
|
if (offset > data_len)
|
|
return -EINVAL;
|
|
|
|
if (offset + buf_len > data_len)
|
|
buf_len = data_len - offset;
|
|
return sg_pcopy_to_buffer(table, sg_nents(table), buffer, buf_len,
|
|
offset);
|
|
}
|
|
|
|
/**
|
|
* dev_coredump_put - remove device coredump
|
|
* @dev: the struct device for the crashed device
|
|
*
|
|
* dev_coredump_put() removes coredump, if exists, for a given device from
|
|
* the file system and free its associated data otherwise, does nothing.
|
|
*
|
|
* It is useful for modules that do not want to keep coredump
|
|
* available after its unload.
|
|
*/
|
|
void dev_coredump_put(struct device *dev)
|
|
{
|
|
struct device *existing;
|
|
|
|
existing = class_find_device(&devcd_class, NULL, dev,
|
|
devcd_match_failing);
|
|
if (existing) {
|
|
devcd_free(existing, NULL);
|
|
put_device(existing);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_coredump_put);
|
|
|
|
/**
|
|
* dev_coredumpm_timeout - create device coredump with read/free methods with a
|
|
* custom timeout.
|
|
* @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
|
|
* @gfp: allocation flags
|
|
* @read: function to read from the given buffer
|
|
* @free: function to free the given buffer
|
|
* @timeout: time in jiffies to remove coredump
|
|
*
|
|
* 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_coredumpm_timeout(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),
|
|
unsigned long timeout)
|
|
{
|
|
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);
|
|
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);
|
|
devcd->deleted = false;
|
|
|
|
mutex_init(&devcd->mutex);
|
|
device_initialize(&devcd->devcd_dev);
|
|
|
|
dev_set_name(&devcd->devcd_dev, "devcd%d",
|
|
atomic_inc_return(&devcd_count));
|
|
devcd->devcd_dev.class = &devcd_class;
|
|
|
|
dev_set_uevent_suppress(&devcd->devcd_dev, true);
|
|
|
|
/* devcd->mutex prevents devcd_del() completing until init finishes */
|
|
mutex_lock(&devcd->mutex);
|
|
devcd->init_completed = false;
|
|
INIT_DELAYED_WORK(&devcd->del_wk, devcd_del);
|
|
schedule_delayed_work(&devcd->del_wk, timeout);
|
|
|
|
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");
|
|
|
|
dev_set_uevent_suppress(&devcd->devcd_dev, false);
|
|
kobject_uevent(&devcd->devcd_dev.kobj, KOBJ_ADD);
|
|
|
|
/*
|
|
* Safe to run devcd_del() now that we are done with devcd_dev.
|
|
* Alternatively we could have taken a ref on devcd_dev before
|
|
* dropping the lock.
|
|
*/
|
|
devcd->init_completed = true;
|
|
mutex_unlock(&devcd->mutex);
|
|
return;
|
|
put_device:
|
|
mutex_unlock(&devcd->mutex);
|
|
cancel_delayed_work_sync(&devcd->del_wk);
|
|
put_device(&devcd->devcd_dev);
|
|
|
|
put_module:
|
|
module_put(owner);
|
|
free:
|
|
free(data);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_coredumpm_timeout);
|
|
|
|
/**
|
|
* dev_coredumpsg - 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
|
|
* @gfp: allocation flags
|
|
*
|
|
* 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_coredumpsg(struct device *dev, struct scatterlist *table,
|
|
size_t datalen, gfp_t gfp)
|
|
{
|
|
dev_coredumpm(dev, NULL, table, datalen, gfp, devcd_read_from_sgtable,
|
|
devcd_free_sgtable);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_coredumpsg);
|
|
|
|
static int __init devcoredump_init(void)
|
|
{
|
|
return class_register(&devcd_class);
|
|
}
|
|
__initcall(devcoredump_init);
|
|
|
|
static void __exit devcoredump_exit(void)
|
|
{
|
|
class_for_each_device(&devcd_class, NULL, NULL, devcd_free);
|
|
class_unregister(&devcd_class);
|
|
}
|
|
__exitcall(devcoredump_exit);
|