mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-25 13:14:07 +08:00
00cd29b799
The starting node for a klist iteration is often passed in from somewhere way above the klist infrastructure, meaning there's no guarantee the node is still on the list. We've seen this in SCSI where we use bus_find_device() to iterate through a list of devices. In the face of heavy hotplug activity, the last device returned by bus_find_device() can be removed before the next call. This leads to Dec 3 13:22:02 localhost kernel: WARNING: CPU: 2 PID: 28073 at include/linux/kref.h:47 klist_iter_init_node+0x3d/0x50() Dec 3 13:22:02 localhost kernel: Modules linked in: scsi_debug x86_pkg_temp_thermal kvm_intel kvm irqbypass crc32c_intel joydev iTCO_wdt dcdbas ipmi_devintf acpi_power_meter iTCO_vendor_support ipmi_si imsghandler pcspkr wmi acpi_cpufreq tpm_tis tpm shpchp lpc_ich mfd_core nfsd nfs_acl lockd grace sunrpc tg3 ptp pps_core Dec 3 13:22:02 localhost kernel: CPU: 2 PID: 28073 Comm: cat Not tainted 4.4.0-rc1+ #2 Dec 3 13:22:02 localhost kernel: Hardware name: Dell Inc. PowerEdge R320/08VT7V, BIOS 2.0.22 11/19/2013 Dec 3 13:22:02 localhost kernel: ffffffff81a20e77 ffff880613acfd18 ffffffff81321eef 0000000000000000 Dec 3 13:22:02 localhost kernel: ffff880613acfd50 ffffffff8107ca52 ffff88061176b198 0000000000000000 Dec 3 13:22:02 localhost kernel: ffffffff814542b0 ffff880610cfb100 ffff88061176b198 ffff880613acfd60 Dec 3 13:22:02 localhost kernel: Call Trace: Dec 3 13:22:02 localhost kernel: [<ffffffff81321eef>] dump_stack+0x44/0x55 Dec 3 13:22:02 localhost kernel: [<ffffffff8107ca52>] warn_slowpath_common+0x82/0xc0 Dec 3 13:22:02 localhost kernel: [<ffffffff814542b0>] ? proc_scsi_show+0x20/0x20 Dec 3 13:22:02 localhost kernel: [<ffffffff8107cb4a>] warn_slowpath_null+0x1a/0x20 Dec 3 13:22:02 localhost kernel: [<ffffffff8167225d>] klist_iter_init_node+0x3d/0x50 Dec 3 13:22:02 localhost kernel: [<ffffffff81421d41>] bus_find_device+0x51/0xb0 Dec 3 13:22:02 localhost kernel: [<ffffffff814545ad>] scsi_seq_next+0x2d/0x40 [...] And an eventual crash. It can actually occur in any hotplug system which has a device finder and a starting device. We can fix this globally by making sure the starting node for klist_iter_init_node() is actually a member of the list before using it (and by starting from the beginning if it isn't). Reported-by: Ewan D. Milne <emilne@redhat.com> Tested-by: Ewan D. Milne <emilne@redhat.com> Cc: stable@vger.kernel.org Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
407 lines
10 KiB
C
407 lines
10 KiB
C
/*
|
|
* klist.c - Routines for manipulating klists.
|
|
*
|
|
* Copyright (C) 2005 Patrick Mochel
|
|
*
|
|
* This file is released under the GPL v2.
|
|
*
|
|
* This klist interface provides a couple of structures that wrap around
|
|
* struct list_head to provide explicit list "head" (struct klist) and list
|
|
* "node" (struct klist_node) objects. For struct klist, a spinlock is
|
|
* included that protects access to the actual list itself. struct
|
|
* klist_node provides a pointer to the klist that owns it and a kref
|
|
* reference count that indicates the number of current users of that node
|
|
* in the list.
|
|
*
|
|
* The entire point is to provide an interface for iterating over a list
|
|
* that is safe and allows for modification of the list during the
|
|
* iteration (e.g. insertion and removal), including modification of the
|
|
* current node on the list.
|
|
*
|
|
* It works using a 3rd object type - struct klist_iter - that is declared
|
|
* and initialized before an iteration. klist_next() is used to acquire the
|
|
* next element in the list. It returns NULL if there are no more items.
|
|
* Internally, that routine takes the klist's lock, decrements the
|
|
* reference count of the previous klist_node and increments the count of
|
|
* the next klist_node. It then drops the lock and returns.
|
|
*
|
|
* There are primitives for adding and removing nodes to/from a klist.
|
|
* When deleting, klist_del() will simply decrement the reference count.
|
|
* Only when the count goes to 0 is the node removed from the list.
|
|
* klist_remove() will try to delete the node from the list and block until
|
|
* it is actually removed. This is useful for objects (like devices) that
|
|
* have been removed from the system and must be freed (but must wait until
|
|
* all accessors have finished).
|
|
*/
|
|
|
|
#include <linux/klist.h>
|
|
#include <linux/export.h>
|
|
#include <linux/sched.h>
|
|
|
|
/*
|
|
* Use the lowest bit of n_klist to mark deleted nodes and exclude
|
|
* dead ones from iteration.
|
|
*/
|
|
#define KNODE_DEAD 1LU
|
|
#define KNODE_KLIST_MASK ~KNODE_DEAD
|
|
|
|
static struct klist *knode_klist(struct klist_node *knode)
|
|
{
|
|
return (struct klist *)
|
|
((unsigned long)knode->n_klist & KNODE_KLIST_MASK);
|
|
}
|
|
|
|
static bool knode_dead(struct klist_node *knode)
|
|
{
|
|
return (unsigned long)knode->n_klist & KNODE_DEAD;
|
|
}
|
|
|
|
static void knode_set_klist(struct klist_node *knode, struct klist *klist)
|
|
{
|
|
knode->n_klist = klist;
|
|
/* no knode deserves to start its life dead */
|
|
WARN_ON(knode_dead(knode));
|
|
}
|
|
|
|
static void knode_kill(struct klist_node *knode)
|
|
{
|
|
/* and no knode should die twice ever either, see we're very humane */
|
|
WARN_ON(knode_dead(knode));
|
|
*(unsigned long *)&knode->n_klist |= KNODE_DEAD;
|
|
}
|
|
|
|
/**
|
|
* klist_init - Initialize a klist structure.
|
|
* @k: The klist we're initializing.
|
|
* @get: The get function for the embedding object (NULL if none)
|
|
* @put: The put function for the embedding object (NULL if none)
|
|
*
|
|
* Initialises the klist structure. If the klist_node structures are
|
|
* going to be embedded in refcounted objects (necessary for safe
|
|
* deletion) then the get/put arguments are used to initialise
|
|
* functions that take and release references on the embedding
|
|
* objects.
|
|
*/
|
|
void klist_init(struct klist *k, void (*get)(struct klist_node *),
|
|
void (*put)(struct klist_node *))
|
|
{
|
|
INIT_LIST_HEAD(&k->k_list);
|
|
spin_lock_init(&k->k_lock);
|
|
k->get = get;
|
|
k->put = put;
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_init);
|
|
|
|
static void add_head(struct klist *k, struct klist_node *n)
|
|
{
|
|
spin_lock(&k->k_lock);
|
|
list_add(&n->n_node, &k->k_list);
|
|
spin_unlock(&k->k_lock);
|
|
}
|
|
|
|
static void add_tail(struct klist *k, struct klist_node *n)
|
|
{
|
|
spin_lock(&k->k_lock);
|
|
list_add_tail(&n->n_node, &k->k_list);
|
|
spin_unlock(&k->k_lock);
|
|
}
|
|
|
|
static void klist_node_init(struct klist *k, struct klist_node *n)
|
|
{
|
|
INIT_LIST_HEAD(&n->n_node);
|
|
kref_init(&n->n_ref);
|
|
knode_set_klist(n, k);
|
|
if (k->get)
|
|
k->get(n);
|
|
}
|
|
|
|
/**
|
|
* klist_add_head - Initialize a klist_node and add it to front.
|
|
* @n: node we're adding.
|
|
* @k: klist it's going on.
|
|
*/
|
|
void klist_add_head(struct klist_node *n, struct klist *k)
|
|
{
|
|
klist_node_init(k, n);
|
|
add_head(k, n);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_add_head);
|
|
|
|
/**
|
|
* klist_add_tail - Initialize a klist_node and add it to back.
|
|
* @n: node we're adding.
|
|
* @k: klist it's going on.
|
|
*/
|
|
void klist_add_tail(struct klist_node *n, struct klist *k)
|
|
{
|
|
klist_node_init(k, n);
|
|
add_tail(k, n);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_add_tail);
|
|
|
|
/**
|
|
* klist_add_behind - Init a klist_node and add it after an existing node
|
|
* @n: node we're adding.
|
|
* @pos: node to put @n after
|
|
*/
|
|
void klist_add_behind(struct klist_node *n, struct klist_node *pos)
|
|
{
|
|
struct klist *k = knode_klist(pos);
|
|
|
|
klist_node_init(k, n);
|
|
spin_lock(&k->k_lock);
|
|
list_add(&n->n_node, &pos->n_node);
|
|
spin_unlock(&k->k_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_add_behind);
|
|
|
|
/**
|
|
* klist_add_before - Init a klist_node and add it before an existing node
|
|
* @n: node we're adding.
|
|
* @pos: node to put @n after
|
|
*/
|
|
void klist_add_before(struct klist_node *n, struct klist_node *pos)
|
|
{
|
|
struct klist *k = knode_klist(pos);
|
|
|
|
klist_node_init(k, n);
|
|
spin_lock(&k->k_lock);
|
|
list_add_tail(&n->n_node, &pos->n_node);
|
|
spin_unlock(&k->k_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_add_before);
|
|
|
|
struct klist_waiter {
|
|
struct list_head list;
|
|
struct klist_node *node;
|
|
struct task_struct *process;
|
|
int woken;
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(klist_remove_lock);
|
|
static LIST_HEAD(klist_remove_waiters);
|
|
|
|
static void klist_release(struct kref *kref)
|
|
{
|
|
struct klist_waiter *waiter, *tmp;
|
|
struct klist_node *n = container_of(kref, struct klist_node, n_ref);
|
|
|
|
WARN_ON(!knode_dead(n));
|
|
list_del(&n->n_node);
|
|
spin_lock(&klist_remove_lock);
|
|
list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {
|
|
if (waiter->node != n)
|
|
continue;
|
|
|
|
list_del(&waiter->list);
|
|
waiter->woken = 1;
|
|
mb();
|
|
wake_up_process(waiter->process);
|
|
}
|
|
spin_unlock(&klist_remove_lock);
|
|
knode_set_klist(n, NULL);
|
|
}
|
|
|
|
static int klist_dec_and_del(struct klist_node *n)
|
|
{
|
|
return kref_put(&n->n_ref, klist_release);
|
|
}
|
|
|
|
static void klist_put(struct klist_node *n, bool kill)
|
|
{
|
|
struct klist *k = knode_klist(n);
|
|
void (*put)(struct klist_node *) = k->put;
|
|
|
|
spin_lock(&k->k_lock);
|
|
if (kill)
|
|
knode_kill(n);
|
|
if (!klist_dec_and_del(n))
|
|
put = NULL;
|
|
spin_unlock(&k->k_lock);
|
|
if (put)
|
|
put(n);
|
|
}
|
|
|
|
/**
|
|
* klist_del - Decrement the reference count of node and try to remove.
|
|
* @n: node we're deleting.
|
|
*/
|
|
void klist_del(struct klist_node *n)
|
|
{
|
|
klist_put(n, true);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_del);
|
|
|
|
/**
|
|
* klist_remove - Decrement the refcount of node and wait for it to go away.
|
|
* @n: node we're removing.
|
|
*/
|
|
void klist_remove(struct klist_node *n)
|
|
{
|
|
struct klist_waiter waiter;
|
|
|
|
waiter.node = n;
|
|
waiter.process = current;
|
|
waiter.woken = 0;
|
|
spin_lock(&klist_remove_lock);
|
|
list_add(&waiter.list, &klist_remove_waiters);
|
|
spin_unlock(&klist_remove_lock);
|
|
|
|
klist_del(n);
|
|
|
|
for (;;) {
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
if (waiter.woken)
|
|
break;
|
|
schedule();
|
|
}
|
|
__set_current_state(TASK_RUNNING);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_remove);
|
|
|
|
/**
|
|
* klist_node_attached - Say whether a node is bound to a list or not.
|
|
* @n: Node that we're testing.
|
|
*/
|
|
int klist_node_attached(struct klist_node *n)
|
|
{
|
|
return (n->n_klist != NULL);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_node_attached);
|
|
|
|
/**
|
|
* klist_iter_init_node - Initialize a klist_iter structure.
|
|
* @k: klist we're iterating.
|
|
* @i: klist_iter we're filling.
|
|
* @n: node to start with.
|
|
*
|
|
* Similar to klist_iter_init(), but starts the action off with @n,
|
|
* instead of with the list head.
|
|
*/
|
|
void klist_iter_init_node(struct klist *k, struct klist_iter *i,
|
|
struct klist_node *n)
|
|
{
|
|
i->i_klist = k;
|
|
i->i_cur = NULL;
|
|
if (n && kref_get_unless_zero(&n->n_ref))
|
|
i->i_cur = n;
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_iter_init_node);
|
|
|
|
/**
|
|
* klist_iter_init - Iniitalize a klist_iter structure.
|
|
* @k: klist we're iterating.
|
|
* @i: klist_iter structure we're filling.
|
|
*
|
|
* Similar to klist_iter_init_node(), but start with the list head.
|
|
*/
|
|
void klist_iter_init(struct klist *k, struct klist_iter *i)
|
|
{
|
|
klist_iter_init_node(k, i, NULL);
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_iter_init);
|
|
|
|
/**
|
|
* klist_iter_exit - Finish a list iteration.
|
|
* @i: Iterator structure.
|
|
*
|
|
* Must be called when done iterating over list, as it decrements the
|
|
* refcount of the current node. Necessary in case iteration exited before
|
|
* the end of the list was reached, and always good form.
|
|
*/
|
|
void klist_iter_exit(struct klist_iter *i)
|
|
{
|
|
if (i->i_cur) {
|
|
klist_put(i->i_cur, false);
|
|
i->i_cur = NULL;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_iter_exit);
|
|
|
|
static struct klist_node *to_klist_node(struct list_head *n)
|
|
{
|
|
return container_of(n, struct klist_node, n_node);
|
|
}
|
|
|
|
/**
|
|
* klist_prev - Ante up prev node in list.
|
|
* @i: Iterator structure.
|
|
*
|
|
* First grab list lock. Decrement the reference count of the previous
|
|
* node, if there was one. Grab the prev node, increment its reference
|
|
* count, drop the lock, and return that prev node.
|
|
*/
|
|
struct klist_node *klist_prev(struct klist_iter *i)
|
|
{
|
|
void (*put)(struct klist_node *) = i->i_klist->put;
|
|
struct klist_node *last = i->i_cur;
|
|
struct klist_node *prev;
|
|
|
|
spin_lock(&i->i_klist->k_lock);
|
|
|
|
if (last) {
|
|
prev = to_klist_node(last->n_node.prev);
|
|
if (!klist_dec_and_del(last))
|
|
put = NULL;
|
|
} else
|
|
prev = to_klist_node(i->i_klist->k_list.prev);
|
|
|
|
i->i_cur = NULL;
|
|
while (prev != to_klist_node(&i->i_klist->k_list)) {
|
|
if (likely(!knode_dead(prev))) {
|
|
kref_get(&prev->n_ref);
|
|
i->i_cur = prev;
|
|
break;
|
|
}
|
|
prev = to_klist_node(prev->n_node.prev);
|
|
}
|
|
|
|
spin_unlock(&i->i_klist->k_lock);
|
|
|
|
if (put && last)
|
|
put(last);
|
|
return i->i_cur;
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_prev);
|
|
|
|
/**
|
|
* klist_next - Ante up next node in list.
|
|
* @i: Iterator structure.
|
|
*
|
|
* First grab list lock. Decrement the reference count of the previous
|
|
* node, if there was one. Grab the next node, increment its reference
|
|
* count, drop the lock, and return that next node.
|
|
*/
|
|
struct klist_node *klist_next(struct klist_iter *i)
|
|
{
|
|
void (*put)(struct klist_node *) = i->i_klist->put;
|
|
struct klist_node *last = i->i_cur;
|
|
struct klist_node *next;
|
|
|
|
spin_lock(&i->i_klist->k_lock);
|
|
|
|
if (last) {
|
|
next = to_klist_node(last->n_node.next);
|
|
if (!klist_dec_and_del(last))
|
|
put = NULL;
|
|
} else
|
|
next = to_klist_node(i->i_klist->k_list.next);
|
|
|
|
i->i_cur = NULL;
|
|
while (next != to_klist_node(&i->i_klist->k_list)) {
|
|
if (likely(!knode_dead(next))) {
|
|
kref_get(&next->n_ref);
|
|
i->i_cur = next;
|
|
break;
|
|
}
|
|
next = to_klist_node(next->n_node.next);
|
|
}
|
|
|
|
spin_unlock(&i->i_klist->k_lock);
|
|
|
|
if (put && last)
|
|
put(last);
|
|
return i->i_cur;
|
|
}
|
|
EXPORT_SYMBOL_GPL(klist_next);
|