[PATCH] spufs: Improved SPU preemptability.

This patch makes it easier to preempt an SPU context by
having the scheduler hold ctx->state_sema for much shorter
periods of time.

As part of this restructuring, the control logic for the "run"
operation is moved from arch/ppc64/kernel/spu_base.c to
fs/spufs/file.c.  Of course the base retains "bottom half"
handlers for class{0,1} irqs.  The new run loop will re-acquire
an SPU if preempted.

From: Mark Nutter <mnutter@us.ibm.com>
Signed-off-by: Arnd Bergmann <arndb@de.ibm.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
diff --git a/arch/powerpc/platforms/cell/spufs/file.c b/arch/powerpc/platforms/cell/spufs/file.c
index 0fe1fec..af5adc3 100644
--- a/arch/powerpc/platforms/cell/spufs/file.c
+++ b/arch/powerpc/platforms/cell/spufs/file.c
@@ -25,6 +25,7 @@
 #include <linux/module.h>
 #include <linux/pagemap.h>
 #include <linux/poll.h>
+#include <linux/ptrace.h>
 
 #include <asm/io.h>
 #include <asm/semaphore.h>
@@ -540,26 +541,122 @@
 	.read	= spufs_wbox_stat_read,
 };
 
-long spufs_run_spu(struct file *file, struct spu_context *ctx,
-				u32 *npc, u32 *status)
+/* interrupt-level stop callback function. */
+void spufs_stop_callback(struct spu *spu)
+{
+	struct spu_context *ctx = spu->ctx;
+
+	wake_up_all(&ctx->stop_wq);
+}
+
+static inline int spu_stopped(struct spu_context *ctx, u32 * stat)
+{
+	struct spu *spu;
+	u64 pte_fault;
+
+	*stat = ctx->ops->status_read(ctx);
+	if (ctx->state != SPU_STATE_RUNNABLE)
+		return 1;
+	spu = ctx->spu;
+	pte_fault = spu->dsisr &
+	    (MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED);
+	return (!(*stat & 0x1) || pte_fault || spu->class_0_pending) ? 1 : 0;
+}
+
+static inline int spu_run_init(struct spu_context *ctx, u32 * npc,
+			       u32 * status)
 {
 	int ret;
 
-	ret = spu_acquire_runnable(ctx);
-	if (ret)
+	if ((ret = spu_acquire_runnable(ctx)) != 0)
+		return ret;
+	ctx->ops->npc_write(ctx, *npc);
+	ctx->ops->runcntl_write(ctx, SPU_RUNCNTL_RUNNABLE);
+	return 0;
+}
+
+static inline int spu_run_fini(struct spu_context *ctx, u32 * npc,
+			       u32 * status)
+{
+	int ret = 0;
+
+	*status = ctx->ops->status_read(ctx);
+	*npc = ctx->ops->npc_read(ctx);
+	spu_release(ctx);
+
+	if (signal_pending(current))
+		ret = -ERESTARTSYS;
+	if (unlikely(current->ptrace & PT_PTRACED)) {
+		if ((*status & SPU_STATUS_STOPPED_BY_STOP)
+		    && (*status >> SPU_STOP_STATUS_SHIFT) == 0x3fff) {
+			force_sig(SIGTRAP, current);
+			ret = -ERESTARTSYS;
+		}
+	}
+	return ret;
+}
+
+static inline int spu_reacquire_runnable(struct spu_context *ctx, u32 *npc,
+				         u32 *status)
+{
+	int ret;
+
+	if ((ret = spu_run_fini(ctx, npc, status)) != 0)
+		return ret;
+	if (*status & (SPU_STATUS_STOPPED_BY_STOP |
+		       SPU_STATUS_STOPPED_BY_HALT)) {
+		return *status;
+	}
+	if ((ret = spu_run_init(ctx, npc, status)) != 0)
+		return ret;
+	return 0;
+}
+
+static inline int spu_process_events(struct spu_context *ctx)
+{
+	struct spu *spu = ctx->spu;
+	u64 pte_fault = MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED;
+	int ret = 0;
+
+	if (spu->dsisr & pte_fault)
+		ret = spu_irq_class_1_bottom(spu);
+	if (spu->class_0_pending)
+		ret = spu_irq_class_0_bottom(spu);
+	if (!ret && signal_pending(current))
+		ret = -ERESTARTSYS;
+	return ret;
+}
+
+long spufs_run_spu(struct file *file, struct spu_context *ctx,
+		   u32 * npc, u32 * status)
+{
+	int ret;
+
+	if ((ret = spu_run_init(ctx, npc, status)) != 0)
 		return ret;
 
-	ctx->ops->npc_write(ctx, *npc);
+	do {
+		ret = spufs_wait(ctx->stop_wq, spu_stopped(ctx, status));
+		if (unlikely(ret))
+			break;
+		if (unlikely(ctx->state != SPU_STATE_RUNNABLE)) {
+			ret = spu_reacquire_runnable(ctx, npc, status);
+			if (ret) {
+				return ret;
+			}
+			continue;
+		}
+		ret = spu_process_events(ctx);
 
-	ret = spu_run(ctx->spu);
+	} while (!ret && !(*status & (SPU_STATUS_STOPPED_BY_STOP |
+				      SPU_STATUS_STOPPED_BY_HALT)));
 
+	ctx->ops->runcntl_stop(ctx);
+	ret = spu_run_fini(ctx, npc, status);
 	if (!ret)
-		ret = ctx->ops->status_read(ctx);
-
-	*npc = ctx->ops->npc_read(ctx);
-
-	spu_release(ctx);
+		ret = *status;
 	spu_yield(ctx);
+
 	return ret;
 }