Subject: spu sched: fix wakeup races From: Christoph Hellwig Fix the race between checking for contexts on the runqueue and actually waking them in spu_deactive and spu_yield. The guts of spu_reschedule are split into a new helper called grab_runnable_context which shows if there is a runnable thread below a specified priority and if yes removes if from the runqueue and uses it. This function is used by the new __spu_deactivate hepler shared by preemption and spu_yield to grab a new context before deactivating a specified priority and if yes removes if from the runqueue and uses it. This function is used by the new __spu_deactivate hepler shared by preemption and spu_yield to grab a new context before deactivating the old one. Signed-off-by: Christoph Hellwig Signed-off-by: Arnd Bergmann --- Below is a smaller patch that fixes the race only for the scheduler tick where it really matters, and avoids moving things around as much as possible. Index: linux-sdk2.1/arch/powerpc/platforms/cell/spufs/sched.c =================================================================== --- linux-sdk2.1.orig/arch/powerpc/platforms/cell/spufs/sched.c +++ linux-sdk2.1/arch/powerpc/platforms/cell/spufs/sched.c @@ -57,6 +57,10 @@ struct spu_prio_array { static struct spu_prio_array *spu_prio; static struct workqueue_struct *spu_sched_wq; +static struct spu_context *grab_runnable_context(int prio); +static void spu_unbind_context(struct spu *spu, struct spu_context *ctx); + + static inline int node_allowed(int node) { cpumask_t mask; @@ -112,9 +116,14 @@ void spu_sched_tick(struct work_struct * mutex_lock(&ctx->state_mutex); spu = ctx->spu; if (spu) { - int best = sched_find_first_bit(spu_prio->bitmap); - if (best <= ctx->prio) { - spu_deactivate(ctx); + struct spu_context *new; + + new = grab_runnable_context(ctx->prio + 1); + if (new) { + spu_unbind_context(spu, ctx); + spu_free(spu); + if (new) + wake_up(&new->stop_wq); preempted = 1; } } @@ -312,31 +321,47 @@ static void spu_prio_wait(struct spu_con } /** - * spu_reschedule - try to find a runnable context for a spu - * @spu: spu available + * grab_runnable_context - try to find a runnable context * - * This function is called whenever a spu becomes idle. It looks for the - * most suitable runnable spu context and schedules it for execution. + * Remove the highest priority context on the runqueue and return it + * to the caller. Returns %NULL if no runnable context was found. */ -static void spu_reschedule(struct spu *spu) +static struct spu_context *grab_runnable_context(int prio) { + struct spu_context *ctx = NULL; int best; - spu_free(spu); - spin_lock(&spu_prio->runq_lock); best = sched_find_first_bit(spu_prio->bitmap); - if (best < MAX_PRIO) { + if (best < prio) { struct list_head *rq = &spu_prio->runq[best]; - struct spu_context *ctx; BUG_ON(list_empty(rq)); ctx = list_entry(rq->next, struct spu_context, rq); __spu_del_from_rq(ctx); - wake_up(&ctx->stop_wq); } spin_unlock(&spu_prio->runq_lock); + + return ctx; +} + +/** + * spu_reschedule - try to find a runnable context for a spu + * @spu: spu available + * + * This function is called whenever a spu becomes idle. It looks for the + * most suitable runnable spu context and schedules it for execution. + */ +static void spu_reschedule(struct spu *spu) +{ + struct spu_context *ctx; + + spu_free(spu); + + ctx = grab_runnable_context(MAX_PRIO); + if (ctx) + wake_up(&ctx->stop_wq); } static struct spu *spu_get_idle(struct spu_context *ctx)