epoll: fix nested calls support

This fixes a regression in 2.6.30.

I unfortunately accepted a patch time ago, to drop the "current" usage
from possible IRQ context, w/out proper thought over it.  The patch
switched to using the CPU id by bounding the nested call callback with a
get_cpu()/put_cpu().

Unfortunately the ep_call_nested() function can be called with a callback
that grabs sleepy locks (from own f_op->poll()), that results in epic
fails.  The following patch uses the proper "context" depending on the
path where it is called, and on the kind of callback.

This has been reported by Stefan Richter, that has also verified the patch
is his previously failing environment.

Signed-off-by: Davide Libenzi <davidel@xmailserver.org>
Reported-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Davide Libenzi 2009-06-17 16:25:58 -07:00 committed by Linus Torvalds
parent 36025a812e
commit 3fe4a975d6

View file

@ -98,7 +98,7 @@ struct epoll_filefd {
struct nested_call_node { struct nested_call_node {
struct list_head llink; struct list_head llink;
void *cookie; void *cookie;
int cpu; void *ctx;
}; };
/* /*
@ -317,17 +317,17 @@ static void ep_nested_calls_init(struct nested_calls *ncalls)
* @nproc: Nested call core function pointer. * @nproc: Nested call core function pointer.
* @priv: Opaque data to be passed to the @nproc callback. * @priv: Opaque data to be passed to the @nproc callback.
* @cookie: Cookie to be used to identify this nested call. * @cookie: Cookie to be used to identify this nested call.
* @ctx: This instance context.
* *
* Returns: Returns the code returned by the @nproc callback, or -1 if * Returns: Returns the code returned by the @nproc callback, or -1 if
* the maximum recursion limit has been exceeded. * the maximum recursion limit has been exceeded.
*/ */
static int ep_call_nested(struct nested_calls *ncalls, int max_nests, static int ep_call_nested(struct nested_calls *ncalls, int max_nests,
int (*nproc)(void *, void *, int), void *priv, int (*nproc)(void *, void *, int), void *priv,
void *cookie) void *cookie, void *ctx)
{ {
int error, call_nests = 0; int error, call_nests = 0;
unsigned long flags; unsigned long flags;
int this_cpu = get_cpu();
struct list_head *lsthead = &ncalls->tasks_call_list; struct list_head *lsthead = &ncalls->tasks_call_list;
struct nested_call_node *tncur; struct nested_call_node *tncur;
struct nested_call_node tnode; struct nested_call_node tnode;
@ -340,7 +340,7 @@ static int ep_call_nested(struct nested_calls *ncalls, int max_nests,
* very much limited. * very much limited.
*/ */
list_for_each_entry(tncur, lsthead, llink) { list_for_each_entry(tncur, lsthead, llink) {
if (tncur->cpu == this_cpu && if (tncur->ctx == ctx &&
(tncur->cookie == cookie || ++call_nests > max_nests)) { (tncur->cookie == cookie || ++call_nests > max_nests)) {
/* /*
* Ops ... loop detected or maximum nest level reached. * Ops ... loop detected or maximum nest level reached.
@ -352,7 +352,7 @@ static int ep_call_nested(struct nested_calls *ncalls, int max_nests,
} }
/* Add the current task and cookie to the list */ /* Add the current task and cookie to the list */
tnode.cpu = this_cpu; tnode.ctx = ctx;
tnode.cookie = cookie; tnode.cookie = cookie;
list_add(&tnode.llink, lsthead); list_add(&tnode.llink, lsthead);
@ -364,10 +364,9 @@ static int ep_call_nested(struct nested_calls *ncalls, int max_nests,
/* Remove the current task from the list */ /* Remove the current task from the list */
spin_lock_irqsave(&ncalls->lock, flags); spin_lock_irqsave(&ncalls->lock, flags);
list_del(&tnode.llink); list_del(&tnode.llink);
out_unlock: out_unlock:
spin_unlock_irqrestore(&ncalls->lock, flags); spin_unlock_irqrestore(&ncalls->lock, flags);
put_cpu();
return error; return error;
} }
@ -408,8 +407,12 @@ static int ep_poll_wakeup_proc(void *priv, void *cookie, int call_nests)
*/ */
static void ep_poll_safewake(wait_queue_head_t *wq) static void ep_poll_safewake(wait_queue_head_t *wq)
{ {
int this_cpu = get_cpu();
ep_call_nested(&poll_safewake_ncalls, EP_MAX_NESTS, ep_call_nested(&poll_safewake_ncalls, EP_MAX_NESTS,
ep_poll_wakeup_proc, NULL, wq); ep_poll_wakeup_proc, NULL, wq, (void *) (long) this_cpu);
put_cpu();
} }
/* /*
@ -663,7 +666,7 @@ static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait)
* could re-enter here. * could re-enter here.
*/ */
pollflags = ep_call_nested(&poll_readywalk_ncalls, EP_MAX_NESTS, pollflags = ep_call_nested(&poll_readywalk_ncalls, EP_MAX_NESTS,
ep_poll_readyevents_proc, ep, ep); ep_poll_readyevents_proc, ep, ep, current);
return pollflags != -1 ? pollflags : 0; return pollflags != -1 ? pollflags : 0;
} }