Skip to content

Commit

Permalink
[PATCH] spufs: cooperative scheduler support
Browse files Browse the repository at this point in the history
This adds a scheduler for SPUs to make it possible to use
more logical SPUs than physical ones are present in the
system.

Currently, there is no support for preempting a running
SPU thread, they have to leave the SPU by either triggering
an event on the SPU that causes it to return to the
owning thread or by sending a signal to it.

This patch also adds operations that enable accessing an SPU
in either runnable or saved state. We use an RW semaphore
to protect the state of the SPU from changing underneath
us, while we are holding it readable. In order to change
the state, it is acquired writeable and a context save
or restore is executed before downgrading the semaphore
to read-only.

From: Mark Nutter <[email protected]>,
      Uli Weigand <[email protected]>
Signed-off-by: Arnd Bergmann <[email protected]>
Signed-off-by: Paul Mackerras <[email protected]>
  • Loading branch information
arndb authored and paulusmack committed Jan 9, 2006
1 parent 05b8411 commit 8b3d666
Show file tree
Hide file tree
Showing 14 changed files with 1,680 additions and 323 deletions.
75 changes: 75 additions & 0 deletions arch/powerpc/platforms/cell/setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,77 @@ void cell_show_cpuinfo(struct seq_file *m)
of_node_put(root);
}

#ifdef CONFIG_SPARSEMEM
static int __init find_spu_node_id(struct device_node *spe)
{
unsigned int *id;
#ifdef CONFIG_NUMA
struct device_node *cpu;
cpu = spe->parent->parent;
id = (unsigned int *)get_property(cpu, "node-id", NULL);
#else
id = NULL;
#endif
return id ? *id : 0;
}

static void __init cell_spuprop_present(struct device_node *spe,
const char *prop, int early)
{
struct address_prop {
unsigned long address;
unsigned int len;
} __attribute__((packed)) *p;
int proplen;

unsigned long start_pfn, end_pfn, pfn;
int node_id;

p = (void*)get_property(spe, prop, &proplen);
WARN_ON(proplen != sizeof (*p));

node_id = find_spu_node_id(spe);

start_pfn = p->address >> PAGE_SHIFT;
end_pfn = (p->address + p->len + PAGE_SIZE - 1) >> PAGE_SHIFT;

/* We need to call memory_present *before* the call to sparse_init,
but we can initialize the page structs only *after* that call.
Thus, we're being called twice. */
if (early)
memory_present(node_id, start_pfn, end_pfn);
else {
/* As the pages backing SPU LS and I/O are outside the range
of regular memory, their page structs were not initialized
by free_area_init. Do it here instead. */
for (pfn = start_pfn; pfn < end_pfn; pfn++) {
struct page *page = pfn_to_page(pfn);
set_page_links(page, ZONE_DMA, node_id, pfn);
set_page_count(page, 0);
reset_page_mapcount(page);
SetPageReserved(page);
INIT_LIST_HEAD(&page->lru);
}
}
}

static void __init cell_spumem_init(int early)
{
struct device_node *node;
for (node = of_find_node_by_type(NULL, "spe");
node; node = of_find_node_by_type(node, "spe")) {
cell_spuprop_present(node, "local-store", early);
cell_spuprop_present(node, "problem", early);
cell_spuprop_present(node, "priv1", early);
cell_spuprop_present(node, "priv2", early);
}
}
#else
static void __init cell_spumem_init(int early)
{
}
#endif

static void cell_progress(char *s, unsigned short hex)
{
printk("*** %04x : %s\n", hex, s ? s : "");
Expand Down Expand Up @@ -99,6 +170,8 @@ static void __init cell_setup_arch(void)
#endif

mmio_nvram_init();

cell_spumem_init(0);
}

/*
Expand All @@ -114,6 +187,8 @@ static void __init cell_init_early(void)

ppc64_interrupt_controller = IC_CELL_PIC;

cell_spumem_init(1);

DBG(" <- cell_init_early()\n");
}

Expand Down
138 changes: 76 additions & 62 deletions arch/powerpc/platforms/cell/spu_base.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,51 +69,49 @@ static void spu_restart_dma(struct spu *spu)

static int __spu_trap_data_seg(struct spu *spu, unsigned long ea)
{
struct spu_priv2 __iomem *priv2;
struct mm_struct *mm;
struct spu_priv2 __iomem *priv2 = spu->priv2;
struct mm_struct *mm = spu->mm;
u64 esid, vsid;

pr_debug("%s\n", __FUNCTION__);

if (test_bit(SPU_CONTEXT_SWITCH_ACTIVE_nr, &spu->flags)) {
/* SLBs are pre-loaded for context switch, so
* we should never get here!
*/
printk("%s: invalid access during switch!\n", __func__);
return 1;
}

if (REGION_ID(ea) != USER_REGION_ID) {
if (!mm || (REGION_ID(ea) != USER_REGION_ID)) {
/* Future: support kernel segments so that drivers
* can use SPUs.
*/
pr_debug("invalid region access at %016lx\n", ea);
return 1;
}

priv2 = spu->priv2;
mm = spu->mm;
esid = (ea & ESID_MASK) | SLB_ESID_V;
vsid = (get_vsid(mm->context.id, ea) << SLB_VSID_SHIFT) | SLB_VSID_USER;
if (in_hugepage_area(mm->context, ea))
vsid |= SLB_VSID_L;

out_be64(&priv2->slb_index_W, spu->slb_replace);
out_be64(&priv2->slb_vsid_RW, vsid);
out_be64(&priv2->slb_esid_RW, esid);

spu->slb_replace++;
if (spu->slb_replace >= 8)
spu->slb_replace = 0;

out_be64(&priv2->slb_index_W, spu->slb_replace);
out_be64(&priv2->slb_vsid_RW,
(get_vsid(mm->context.id, ea) << SLB_VSID_SHIFT)
| SLB_VSID_USER);
out_be64(&priv2->slb_esid_RW, (ea & ESID_MASK) | SLB_ESID_V);

spu_restart_dma(spu);

pr_debug("set slb %d context %lx, ea %016lx, vsid %016lx, esid %016lx\n",
spu->slb_replace, mm->context.id, ea,
(get_vsid(mm->context.id, ea) << SLB_VSID_SHIFT)| SLB_VSID_USER,
(ea & ESID_MASK) | SLB_ESID_V);
return 0;
}

extern int hash_page(unsigned long ea, unsigned long access, unsigned long trap); //XXX
static int __spu_trap_data_map(struct spu *spu, unsigned long ea)
static int __spu_trap_data_map(struct spu *spu, unsigned long ea, u64 dsisr)
{
unsigned long dsisr;
struct spu_priv1 __iomem *priv1;

pr_debug("%s\n", __FUNCTION__);
priv1 = spu->priv1;
dsisr = in_be64(&priv1->mfc_dsisr_RW);

/* Handle kernel space hash faults immediately.
User hash faults need to be deferred to process context. */
Expand All @@ -129,14 +127,17 @@ static int __spu_trap_data_map(struct spu *spu, unsigned long ea)
return 1;
}

spu->dar = ea;
spu->dsisr = dsisr;
mb();
wake_up(&spu->stop_wq);
return 0;
}

static int __spu_trap_mailbox(struct spu *spu)
{
wake_up_all(&spu->ibox_wq);
kill_fasync(&spu->ibox_fasync, SIGIO, POLLIN);
if (spu->ibox_callback)
spu->ibox_callback(spu);

/* atomically disable SPU mailbox interrupts */
spin_lock(&spu->register_lock);
Expand Down Expand Up @@ -171,8 +172,8 @@ static int __spu_trap_tag_group(struct spu *spu)

static int __spu_trap_spubox(struct spu *spu)
{
wake_up_all(&spu->wbox_wq);
kill_fasync(&spu->wbox_fasync, SIGIO, POLLOUT);
if (spu->wbox_callback)
spu->wbox_callback(spu);

/* atomically disable SPU mailbox interrupts */
spin_lock(&spu->register_lock);
Expand Down Expand Up @@ -220,17 +221,25 @@ static irqreturn_t
spu_irq_class_1(int irq, void *data, struct pt_regs *regs)
{
struct spu *spu;
unsigned long stat, dar;
unsigned long stat, mask, dar, dsisr;

spu = data;
stat = in_be64(&spu->priv1->int_stat_class1_RW);

/* atomically read & clear class1 status. */
spin_lock(&spu->register_lock);
mask = in_be64(&spu->priv1->int_mask_class1_RW);
stat = in_be64(&spu->priv1->int_stat_class1_RW) & mask;
dar = in_be64(&spu->priv1->mfc_dar_RW);
dsisr = in_be64(&spu->priv1->mfc_dsisr_RW);
out_be64(&spu->priv1->mfc_dsisr_RW, 0UL);
out_be64(&spu->priv1->int_stat_class1_RW, stat);
spin_unlock(&spu->register_lock);

if (stat & 1) /* segment fault */
__spu_trap_data_seg(spu, dar);

if (stat & 2) { /* mapping fault */
__spu_trap_data_map(spu, dar);
__spu_trap_data_map(spu, dar, dsisr);
}

if (stat & 4) /* ls compare & suspend on get */
Expand All @@ -239,7 +248,6 @@ spu_irq_class_1(int irq, void *data, struct pt_regs *regs)
if (stat & 8) /* ls compare & suspend on put */
;

out_be64(&spu->priv1->int_stat_class1_RW, stat);
return stat ? IRQ_HANDLED : IRQ_NONE;
}

Expand Down Expand Up @@ -396,24 +404,20 @@ EXPORT_SYMBOL(spu_alloc);
void spu_free(struct spu *spu)
{
down(&spu_mutex);
spu->ibox_fasync = NULL;
spu->wbox_fasync = NULL;
list_add_tail(&spu->list, &spu_list);
up(&spu_mutex);
}
EXPORT_SYMBOL(spu_free);

static int spu_handle_mm_fault(struct spu *spu)
{
struct spu_priv1 __iomem *priv1;
struct mm_struct *mm = spu->mm;
struct vm_area_struct *vma;
u64 ea, dsisr, is_write;
int ret;

priv1 = spu->priv1;
ea = in_be64(&priv1->mfc_dar_RW);
dsisr = in_be64(&priv1->mfc_dsisr_RW);
ea = spu->dar;
dsisr = spu->dsisr;
#if 0
if (!IS_VALID_EA(ea)) {
return -EFAULT;
Expand Down Expand Up @@ -476,15 +480,14 @@ static int spu_handle_mm_fault(struct spu *spu)

static int spu_handle_pte_fault(struct spu *spu)
{
struct spu_priv1 __iomem *priv1;
u64 ea, dsisr, access, error = 0UL;
int ret = 0;

priv1 = spu->priv1;
ea = in_be64(&priv1->mfc_dar_RW);
dsisr = in_be64(&priv1->mfc_dsisr_RW);
access = (_PAGE_PRESENT | _PAGE_USER);
ea = spu->dar;
dsisr = spu->dsisr;
if (dsisr & MFC_DSISR_PTE_NOT_FOUND) {
access = (_PAGE_PRESENT | _PAGE_USER);
access |= (dsisr & MFC_DSISR_ACCESS_PUT) ? _PAGE_RW : 0UL;
if (hash_page(ea, access, 0x300) != 0)
error |= CLASS1_ENABLE_STORAGE_FAULT_INTR;
}
Expand All @@ -495,40 +498,49 @@ static int spu_handle_pte_fault(struct spu *spu)
else
error &= ~CLASS1_ENABLE_STORAGE_FAULT_INTR;
}
if (!error)
spu->dar = 0UL;
spu->dsisr = 0UL;
if (!error) {
spu_restart_dma(spu);

} else {
__spu_trap_invalid_dma(spu);
}
return ret;
}

static inline int spu_pending(struct spu *spu, u32 * stat)
{
struct spu_problem __iomem *prob = spu->problem;
u64 pte_fault;

*stat = in_be32(&prob->spu_status_R);
pte_fault = spu->dsisr &
(MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED);
return (!(*stat & 0x1) || pte_fault || spu->class_0_pending) ? 1 : 0;
}

int spu_run(struct spu *spu)
{
struct spu_problem __iomem *prob;
struct spu_priv1 __iomem *priv1;
struct spu_priv2 __iomem *priv2;
unsigned long status;
u32 status;
int ret;

prob = spu->problem;
priv1 = spu->priv1;
priv2 = spu->priv2;

/* Let SPU run. */
spu->mm = current->mm;
eieio();
out_be32(&prob->spu_runcntl_RW, SPU_RUNCNTL_RUNNABLE);

do {
ret = wait_event_interruptible(spu->stop_wq,
(!((status = in_be32(&prob->spu_status_R)) & 0x1))
|| (in_be64(&priv1->mfc_dsisr_RW) & MFC_DSISR_PTE_NOT_FOUND)
|| spu->class_0_pending);

if (status & SPU_STATUS_STOPPED_BY_STOP)
ret = -EAGAIN;
else if (status & SPU_STATUS_STOPPED_BY_HALT)
ret = -EIO;
else if (in_be64(&priv1->mfc_dsisr_RW) & MFC_DSISR_PTE_NOT_FOUND)
spu_pending(spu, &status));

if (spu->dsisr &
(MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED))
ret = spu_handle_pte_fault(spu);

if (spu->class_0_pending)
Expand All @@ -537,7 +549,9 @@ int spu_run(struct spu *spu)
if (!ret && signal_pending(current))
ret = -ERESTARTSYS;

} while (!ret);
} while (!ret && !(status &
(SPU_STATUS_STOPPED_BY_STOP |
SPU_STATUS_STOPPED_BY_HALT)));

/* Ensure SPU is stopped. */
out_be32(&prob->spu_runcntl_RW, SPU_RUNCNTL_STOP);
Expand All @@ -549,8 +563,6 @@ int spu_run(struct spu *spu)
out_be64(&priv1->tlb_invalidate_entry_W, 0UL);
eieio();

spu->mm = NULL;

/* Check for SPU breakpoint. */
if (unlikely(current->ptrace & PT_PTRACED)) {
status = in_be32(&prob->spu_status_R);
Expand Down Expand Up @@ -669,19 +681,21 @@ static int __init create_spu(struct device_node *spe)
spu->stop_code = 0;
spu->slb_replace = 0;
spu->mm = NULL;
spu->ctx = NULL;
spu->rq = NULL;
spu->pid = 0;
spu->class_0_pending = 0;
spu->flags = 0UL;
spu->dar = 0UL;
spu->dsisr = 0UL;
spin_lock_init(&spu->register_lock);

out_be64(&spu->priv1->mfc_sdr_RW, mfspr(SPRN_SDR1));
out_be64(&spu->priv1->mfc_sr1_RW, 0x33);

init_waitqueue_head(&spu->stop_wq);
init_waitqueue_head(&spu->wbox_wq);
init_waitqueue_head(&spu->ibox_wq);

spu->ibox_fasync = NULL;
spu->wbox_fasync = NULL;
spu->ibox_callback = NULL;
spu->wbox_callback = NULL;

down(&spu_mutex);
spu->number = number++;
Expand Down
Loading

0 comments on commit 8b3d666

Please sign in to comment.