diff options
Diffstat (limited to 'arch/powerpc/platforms')
-rw-r--r-- | arch/powerpc/platforms/cell/Kconfig | 13 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/Makefile | 3 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spu_base.c | 740 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spu_syscalls.c | 86 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/Makefile | 3 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/context.c | 67 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/file.c | 596 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/inode.c | 470 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/spufs.h | 71 | ||||
-rw-r--r-- | arch/powerpc/platforms/cell/spufs/syscalls.c | 106 |
10 files changed, 2155 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/cell/Kconfig b/arch/powerpc/platforms/cell/Kconfig new file mode 100644 index 000000000000..3157071e241c --- /dev/null +++ b/arch/powerpc/platforms/cell/Kconfig @@ -0,0 +1,13 @@ +menu "Cell Broadband Engine options" + depends on PPC_CELL + +config SPU_FS + tristate "SPU file system" + default m + depends on PPC_CELL + help + The SPU file system is used to access Synergistic Processing + Units on machines implementing the Broadband Processor + Architecture. + +endmenu diff --git a/arch/powerpc/platforms/cell/Makefile b/arch/powerpc/platforms/cell/Makefile index 55e094b96bc0..74616cf13af9 100644 --- a/arch/powerpc/platforms/cell/Makefile +++ b/arch/powerpc/platforms/cell/Makefile @@ -1,2 +1,5 @@ obj-y += interrupt.o iommu.o setup.o spider-pic.o obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_SPU_FS) += spufs/ spu_base.o +builtin-spufs-$(CONFIG_SPU_FS) += spu_syscalls.o +obj-y += $(builtin-spufs-m) diff --git a/arch/powerpc/platforms/cell/spu_base.c b/arch/powerpc/platforms/cell/spu_base.c new file mode 100644 index 000000000000..9e9096590a07 --- /dev/null +++ b/arch/powerpc/platforms/cell/spu_base.c @@ -0,0 +1,740 @@ +/* + * Low-level SPU handling + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define DEBUG 1 + +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/semaphore.h> +#include <asm/spu.h> +#include <asm/mmu_context.h> + +#include "interrupt.h" + +static int __spu_trap_invalid_dma(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + force_sig(SIGBUS, /* info, */ current); + return 0; +} + +static int __spu_trap_dma_align(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + force_sig(SIGBUS, /* info, */ current); + return 0; +} + +static int __spu_trap_error(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + force_sig(SIGILL, /* info, */ current); + return 0; +} + +static void spu_restart_dma(struct spu *spu) +{ + struct spu_priv2 __iomem *priv2 = spu->priv2; + out_be64(&priv2->mfc_control_RW, MFC_CNTL_RESTART_DMA_COMMAND); +} + +static int __spu_trap_data_seg(struct spu *spu, unsigned long ea) +{ + struct spu_priv2 __iomem *priv2; + struct mm_struct *mm; + + pr_debug("%s\n", __FUNCTION__); + + if (REGION_ID(ea) != USER_REGION_ID) { + pr_debug("invalid region access at %016lx\n", ea); + return 1; + } + + priv2 = spu->priv2; + mm = spu->mm; + + 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; +} + +static int __spu_trap_data_map(struct spu *spu, unsigned long ea) +{ + unsigned long dsisr; + struct spu_priv1 __iomem *priv1; + + pr_debug("%s\n", __FUNCTION__); + priv1 = spu->priv1; + dsisr = in_be64(&priv1->mfc_dsisr_RW); + + 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); + + /* atomically disable SPU mailbox interrupts */ + spin_lock(&spu->register_lock); + out_be64(&spu->priv1->int_mask_class2_RW, + in_be64(&spu->priv1->int_mask_class2_RW) & ~0x1); + spin_unlock(&spu->register_lock); + return 0; +} + +static int __spu_trap_stop(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + spu->stop_code = in_be32(&spu->problem->spu_status_R); + wake_up(&spu->stop_wq); + return 0; +} + +static int __spu_trap_halt(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + spu->stop_code = in_be32(&spu->problem->spu_status_R); + wake_up(&spu->stop_wq); + return 0; +} + +static int __spu_trap_tag_group(struct spu *spu) +{ + pr_debug("%s\n", __FUNCTION__); + /* wake_up(&spu->dma_wq); */ + return 0; +} + +static int __spu_trap_spubox(struct spu *spu) +{ + wake_up_all(&spu->wbox_wq); + kill_fasync(&spu->wbox_fasync, SIGIO, POLLOUT); + + /* atomically disable SPU mailbox interrupts */ + spin_lock(&spu->register_lock); + out_be64(&spu->priv1->int_mask_class2_RW, + in_be64(&spu->priv1->int_mask_class2_RW) & ~0x10); + spin_unlock(&spu->register_lock); + return 0; +} + +static irqreturn_t +spu_irq_class_0(int irq, void *data, struct pt_regs *regs) +{ + struct spu *spu; + + spu = data; + spu->class_0_pending = 1; + wake_up(&spu->stop_wq); + + return IRQ_HANDLED; +} + +static int +spu_irq_class_0_bottom(struct spu *spu) +{ + unsigned long stat; + + spu->class_0_pending = 0; + + stat = in_be64(&spu->priv1->int_stat_class0_RW); + + if (stat & 1) /* invalid MFC DMA */ + __spu_trap_invalid_dma(spu); + + if (stat & 2) /* invalid DMA alignment */ + __spu_trap_dma_align(spu); + + if (stat & 4) /* error on SPU */ + __spu_trap_error(spu); + + out_be64(&spu->priv1->int_stat_class0_RW, stat); + return 0; +} + +static irqreturn_t +spu_irq_class_1(int irq, void *data, struct pt_regs *regs) +{ + struct spu *spu; + unsigned long stat, dar; + + spu = data; + stat = in_be64(&spu->priv1->int_stat_class1_RW); + dar = in_be64(&spu->priv1->mfc_dar_RW); + + if (stat & 1) /* segment fault */ + __spu_trap_data_seg(spu, dar); + + if (stat & 2) { /* mapping fault */ + __spu_trap_data_map(spu, dar); + } + + if (stat & 4) /* ls compare & suspend on get */ + ; + + if (stat & 8) /* ls compare & suspend on put */ + ; + + out_be64(&spu->priv1->int_stat_class1_RW, stat); + return stat ? IRQ_HANDLED : IRQ_NONE; +} + +static irqreturn_t +spu_irq_class_2(int irq, void *data, struct pt_regs *regs) +{ + struct spu *spu; + unsigned long stat; + + spu = data; + stat = in_be64(&spu->priv1->int_stat_class2_RW); + + pr_debug("class 2 interrupt %d, %lx, %lx\n", irq, stat, + in_be64(&spu->priv1->int_mask_class2_RW)); + + + if (stat & 1) /* PPC core mailbox */ + __spu_trap_mailbox(spu); + + if (stat & 2) /* SPU stop-and-signal */ + __spu_trap_stop(spu); + + if (stat & 4) /* SPU halted */ + __spu_trap_halt(spu); + + if (stat & 8) /* DMA tag group complete */ + __spu_trap_tag_group(spu); + + if (stat & 0x10) /* SPU mailbox threshold */ + __spu_trap_spubox(spu); + + out_be64(&spu->priv1->int_stat_class2_RW, stat); + return stat ? IRQ_HANDLED : IRQ_NONE; +} + +static int +spu_request_irqs(struct spu *spu) +{ + int ret; + int irq_base; + + irq_base = IIC_NODE_STRIDE * spu->node + IIC_SPE_OFFSET; + + snprintf(spu->irq_c0, sizeof (spu->irq_c0), "spe%02d.0", spu->number); + ret = request_irq(irq_base + spu->isrc, + spu_irq_class_0, 0, spu->irq_c0, spu); + if (ret) + goto out; + out_be64(&spu->priv1->int_mask_class0_RW, 0x7); + + snprintf(spu->irq_c1, sizeof (spu->irq_c1), "spe%02d.1", spu->number); + ret = request_irq(irq_base + IIC_CLASS_STRIDE + spu->isrc, + spu_irq_class_1, 0, spu->irq_c1, spu); + if (ret) + goto out1; + out_be64(&spu->priv1->int_mask_class1_RW, 0x3); + + snprintf(spu->irq_c2, sizeof (spu->irq_c2), "spe%02d.2", spu->number); + ret = request_irq(irq_base + 2*IIC_CLASS_STRIDE + spu->isrc, + spu_irq_class_2, 0, spu->irq_c2, spu); + if (ret) + goto out2; + out_be64(&spu->priv1->int_mask_class2_RW, 0xe); + goto out; + +out2: + free_irq(irq_base + IIC_CLASS_STRIDE + spu->isrc, spu); +out1: + free_irq(irq_base + spu->isrc, spu); +out: + return ret; +} + +static void +spu_free_irqs(struct spu *spu) +{ + int irq_base; + + irq_base = IIC_NODE_STRIDE * spu->node + IIC_SPE_OFFSET; + + free_irq(irq_base + spu->isrc, spu); + free_irq(irq_base + IIC_CLASS_STRIDE + spu->isrc, spu); + free_irq(irq_base + 2*IIC_CLASS_STRIDE + spu->isrc, spu); +} + +static LIST_HEAD(spu_list); +static DECLARE_MUTEX(spu_mutex); + +static void spu_init_channels(struct spu *spu) +{ + static const struct { + unsigned channel; + unsigned count; + } zero_list[] = { + { 0x00, 1, }, { 0x01, 1, }, { 0x03, 1, }, { 0x04, 1, }, + { 0x18, 1, }, { 0x19, 1, }, { 0x1b, 1, }, { 0x1d, 1, }, + }, count_list[] = { + { 0x00, 0, }, { 0x03, 0, }, { 0x04, 0, }, { 0x15, 16, }, + { 0x17, 1, }, { 0x18, 0, }, { 0x19, 0, }, { 0x1b, 0, }, + { 0x1c, 1, }, { 0x1d, 0, }, { 0x1e, 1, }, + }; + struct spu_priv2 *priv2; + int i; + + priv2 = spu->priv2; + + /* initialize all channel data to zero */ + for (i = 0; i < ARRAY_SIZE(zero_list); i++) { + int count; + + out_be64(&priv2->spu_chnlcntptr_RW, zero_list[i].channel); + for (count = 0; count < zero_list[i].count; count++) + out_be64(&priv2->spu_chnldata_RW, 0); + } + + /* initialize channel counts to meaningful values */ + for (i = 0; i < ARRAY_SIZE(count_list); i++) { + out_be64(&priv2->spu_chnlcntptr_RW, count_list[i].channel); + out_be64(&priv2->spu_chnlcnt_RW, count_list[i].count); + } +} + +static void spu_init_regs(struct spu *spu) +{ + out_be64(&spu->priv1->int_mask_class0_RW, 0x7); + out_be64(&spu->priv1->int_mask_class1_RW, 0x3); + out_be64(&spu->priv1->int_mask_class2_RW, 0xe); +} + +struct spu *spu_alloc(void) +{ + struct spu *spu; + + down(&spu_mutex); + if (!list_empty(&spu_list)) { + spu = list_entry(spu_list.next, struct spu, list); + list_del_init(&spu->list); + pr_debug("Got SPU %x %d\n", spu->isrc, spu->number); + } else { + pr_debug("No SPU left\n"); + spu = NULL; + } + up(&spu_mutex); + + if (spu) { + spu_init_channels(spu); + spu_init_regs(spu); + } + + return spu; +} +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); + +extern int hash_page(unsigned long ea, unsigned long access, unsigned long trap); //XXX +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); +#if 0 + if (!IS_VALID_EA(ea)) { + return -EFAULT; + } +#endif /* XXX */ + if (mm == NULL) { + return -EFAULT; + } + if (mm->pgd == NULL) { + return -EFAULT; + } + + down_read(&mm->mmap_sem); + vma = find_vma(mm, ea); + if (!vma) + goto bad_area; + if (vma->vm_start <= ea) + goto good_area; + if (!(vma->vm_flags & VM_GROWSDOWN)) + goto bad_area; +#if 0 + if (expand_stack(vma, ea)) + goto bad_area; +#endif /* XXX */ +good_area: + is_write = dsisr & MFC_DSISR_ACCESS_PUT; + if (is_write) { + if (!(vma->vm_flags & VM_WRITE)) + goto bad_area; + } else { + if (dsisr & MFC_DSISR_ACCESS_DENIED) + goto bad_area; + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) + goto bad_area; + } + ret = 0; + switch (handle_mm_fault(mm, vma, ea, is_write)) { + case VM_FAULT_MINOR: + current->min_flt++; + break; + case VM_FAULT_MAJOR: + current->maj_flt++; + break; + case VM_FAULT_SIGBUS: + ret = -EFAULT; + goto bad_area; + case VM_FAULT_OOM: + ret = -ENOMEM; + goto bad_area; + default: + BUG(); + } + up_read(&mm->mmap_sem); + return ret; + +bad_area: + up_read(&mm->mmap_sem); + return -EFAULT; +} + +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); + if (dsisr & MFC_DSISR_PTE_NOT_FOUND) { + if (hash_page(ea, access, 0x300) != 0) + error |= CLASS1_ENABLE_STORAGE_FAULT_INTR; + } + if ((error & CLASS1_ENABLE_STORAGE_FAULT_INTR) || + (dsisr & MFC_DSISR_ACCESS_DENIED)) { + if ((ret = spu_handle_mm_fault(spu)) != 0) + error |= CLASS1_ENABLE_STORAGE_FAULT_INTR; + else + error &= ~CLASS1_ENABLE_STORAGE_FAULT_INTR; + } + if (!error) + spu_restart_dma(spu); + + return ret; +} + +int spu_run(struct spu *spu) +{ + struct spu_problem __iomem *prob; + struct spu_priv1 __iomem *priv1; + struct spu_priv2 __iomem *priv2; + unsigned long 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) + ret = spu_handle_pte_fault(spu); + + if (spu->class_0_pending) + spu_irq_class_0_bottom(spu); + + if (!ret && signal_pending(current)) + ret = -ERESTARTSYS; + + } while (!ret); + + /* Ensure SPU is stopped. */ + out_be32(&prob->spu_runcntl_RW, SPU_RUNCNTL_STOP); + eieio(); + while (in_be32(&prob->spu_status_R) & SPU_STATUS_RUNNING) + cpu_relax(); + + out_be64(&priv2->slb_invalidate_all_W, 0); + 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); + + if ((status & SPU_STATUS_STOPPED_BY_STOP) + && status >> SPU_STOP_STATUS_SHIFT == 0x3fff) { + force_sig(SIGTRAP, current); + ret = -ERESTARTSYS; + } + } + + return ret; +} +EXPORT_SYMBOL(spu_run); + +static void __iomem * __init map_spe_prop(struct device_node *n, + const char *name) +{ + struct address_prop { + unsigned long address; + unsigned int len; + } __attribute__((packed)) *prop; + + void *p; + int proplen; + + p = get_property(n, name, &proplen); + if (proplen != sizeof (struct address_prop)) + return NULL; + + prop = p; + + return ioremap(prop->address, prop->len); +} + +static void spu_unmap(struct spu *spu) +{ + iounmap(spu->priv2); + iounmap(spu->priv1); + iounmap(spu->problem); + iounmap((u8 __iomem *)spu->local_store); +} + +static int __init spu_map_device(struct spu *spu, struct device_node *spe) +{ + char *prop; + int ret; + + ret = -ENODEV; + prop = get_property(spe, "isrc", NULL); + if (!prop) + goto out; + spu->isrc = *(unsigned int *)prop; + + spu->name = get_property(spe, "name", NULL); + if (!spu->name) + goto out; + + prop = get_property(spe, "local-store", NULL); + if (!prop) + goto out; + spu->local_store_phys = *(unsigned long *)prop; + + /* we use local store as ram, not io memory */ + spu->local_store = (void __force *)map_spe_prop(spe, "local-store"); + if (!spu->local_store) + goto out; + + spu->problem= map_spe_prop(spe, "problem"); + if (!spu->problem) + goto out_unmap; + + spu->priv1= map_spe_prop(spe, "priv1"); + if (!spu->priv1) + goto out_unmap; + + spu->priv2= map_spe_prop(spe, "priv2"); + if (!spu->priv2) + goto out_unmap; + ret = 0; + goto out; + +out_unmap: + spu_unmap(spu); +out: + return ret; +} + +static int __init find_spu_node_id(struct device_node *spe) +{ + unsigned int *id; + struct device_node *cpu; + + cpu = spe->parent->parent; + id = (unsigned int *)get_property(cpu, "node-id", NULL); + + return id ? *id : 0; +} + +static int __init create_spu(struct device_node *spe) +{ + struct spu *spu; + int ret; + static int number; + + ret = -ENOMEM; + spu = kmalloc(sizeof (*spu), GFP_KERNEL); + if (!spu) + goto out; + + ret = spu_map_device(spu, spe); + if (ret) + goto out_free; + + spu->node = find_spu_node_id(spe); + spu->stop_code = 0; + spu->slb_replace = 0; + spu->mm = NULL; + spu->class_0_pending = 0; + 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; + + down(&spu_mutex); + spu->number = number++; + ret = spu_request_irqs(spu); + if (ret) + goto out_unmap; + + list_add(&spu->list, &spu_list); + up(&spu_mutex); + + pr_debug(KERN_DEBUG "Using SPE %s %02x %p %p %p %p %d\n", + spu->name, spu->isrc, spu->local_store, + spu->problem, spu->priv1, spu->priv2, spu->number); + goto out; + +out_unmap: + up(&spu_mutex); + spu_unmap(spu); +out_free: + kfree(spu); +out: + return ret; +} + +static void destroy_spu(struct spu *spu) +{ + list_del_init(&spu->list); + + spu_free_irqs(spu); + spu_unmap(spu); + kfree(spu); +} + +static void cleanup_spu_base(void) +{ + struct spu *spu, *tmp; + down(&spu_mutex); + list_for_each_entry_safe(spu, tmp, &spu_list, list) + destroy_spu(spu); + up(&spu_mutex); +} +module_exit(cleanup_spu_base); + +static int __init init_spu_base(void) +{ + struct device_node *node; + int ret; + + ret = -ENODEV; + for (node = of_find_node_by_type(NULL, "spe"); + node; node = of_find_node_by_type(node, "spe")) { + ret = create_spu(node); + if (ret) { + printk(KERN_WARNING "%s: Error initializing %s\n", + __FUNCTION__, node->name); + cleanup_spu_base(); + break; + } + } + /* in some old firmware versions, the spe is called 'spc', so we + look for that as well */ + for (node = of_find_node_by_type(NULL, "spc"); + node; node = of_find_node_by_type(node, "spc")) { + ret = create_spu(node); + if (ret) { + printk(KERN_WARNING "%s: Error initializing %s\n", + __FUNCTION__, node->name); + cleanup_spu_base(); + break; + } + } + return ret; +} +module_init(init_spu_base); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>"); diff --git a/arch/powerpc/platforms/cell/spu_syscalls.c b/arch/powerpc/platforms/cell/spu_syscalls.c new file mode 100644 index 000000000000..43e0b187ffde --- /dev/null +++ b/arch/powerpc/platforms/cell/spu_syscalls.c @@ -0,0 +1,86 @@ +/* + * SPU file system -- system call stubs + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/file.h> +#include <linux/module.h> +#include <linux/syscalls.h> + +#include <asm/spu.h> + +struct spufs_calls spufs_calls = { + .owner = NULL, +}; + +/* These stub syscalls are needed to have the actual implementation + * within a loadable module. When spufs is built into the kernel, + * this file is not used and the syscalls directly enter the fs code */ + +asmlinkage long sys_spu_create(const char __user *name, + unsigned int flags, mode_t mode) +{ + long ret; + + ret = -ENOSYS; + if (try_module_get(spufs_calls.owner)) { + ret = spufs_calls.create_thread(name, flags, mode); + module_put(spufs_calls.owner); + } + return ret; +} + +asmlinkage long sys_spu_run(int fd, __u32 __user *unpc, __u32 __user *ustatus) +{ + long ret; + struct file *filp; + int fput_needed; + + ret = -ENOSYS; + if (try_module_get(spufs_calls.owner)) { + ret = -EBADF; + filp = fget_light(fd, &fput_needed); + if (filp) { + ret = spufs_calls.spu_run(filp, unpc, ustatus); + fput_light(filp, fput_needed); + } + module_put(spufs_calls.owner); + } + return ret; +} + +int register_spu_syscalls(struct spufs_calls *calls) +{ + if (spufs_calls.owner) + return -EBUSY; + + spufs_calls.create_thread = calls->create_thread; + spufs_calls.spu_run = calls->spu_run; + smp_mb(); + spufs_calls.owner = calls->owner; + return 0; +} +EXPORT_SYMBOL_GPL(register_spu_syscalls); + +void unregister_spu_syscalls(struct spufs_calls *calls) +{ + BUG_ON(spufs_calls.owner != calls->owner); + spufs_calls.owner = NULL; +} +EXPORT_SYMBOL_GPL(unregister_spu_syscalls); diff --git a/arch/powerpc/platforms/cell/spufs/Makefile b/arch/powerpc/platforms/cell/spufs/Makefile new file mode 100644 index 000000000000..6f496e37bcb7 --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SPU_FS) += spufs.o + +spufs-y += inode.o file.o context.o syscalls.o diff --git a/arch/powerpc/platforms/cell/spufs/context.c b/arch/powerpc/platforms/cell/spufs/context.c new file mode 100644 index 000000000000..a69b85e2778a --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/context.c @@ -0,0 +1,67 @@ +/* + * SPU file system -- SPU context management + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/slab.h> +#include <asm/spu.h> +#include "spufs.h" + +struct spu_context *alloc_spu_context(void) +{ + struct spu_context *ctx; + ctx = kmalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + goto out; + ctx->spu = spu_alloc(); + if (!ctx->spu) + goto out_free; + init_rwsem(&ctx->backing_sema); + spin_lock_init(&ctx->mmio_lock); + kref_init(&ctx->kref); + goto out; +out_free: + kfree(ctx); + ctx = NULL; +out: + return ctx; +} + +void destroy_spu_context(struct kref *kref) +{ + struct spu_context *ctx; + ctx = container_of(kref, struct spu_context, kref); + if (ctx->spu) + spu_free(ctx->spu); + kfree(ctx); +} + +struct spu_context * get_spu_context(struct spu_context *ctx) +{ + kref_get(&ctx->kref); + return ctx; +} + +int put_spu_context(struct spu_context *ctx) +{ + return kref_put(&ctx->kref, &destroy_spu_context); +} + + diff --git a/arch/powerpc/platforms/cell/spufs/file.c b/arch/powerpc/platforms/cell/spufs/file.c new file mode 100644 index 000000000000..c1e643310494 --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/file.c @@ -0,0 +1,596 @@ +/* + * SPU file system -- file contents + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/fs.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/semaphore.h> +#include <asm/spu.h> +#include <asm/uaccess.h> + +#include "spufs.h" + +static int +spufs_mem_open(struct inode *inode, struct file *file) +{ + struct spufs_inode_info *i = SPUFS_I(inode); + file->private_data = i->i_ctx; + return 0; +} + +static ssize_t +spufs_mem_read(struct file *file, char __user *buffer, + size_t size, loff_t *pos) +{ + struct spu *spu; + struct spu_context *ctx; + int ret; + + ctx = file->private_data; + spu = ctx->spu; + + down_read(&ctx->backing_sema); + if (spu->number & 0/*1*/) { + ret = generic_file_read(file, buffer, size, pos); + goto out; + } + + ret = simple_read_from_buffer(buffer, size, pos, + spu->local_store, LS_SIZE); +out: + up_read(&ctx->backing_sema); + return ret; +} + +static ssize_t +spufs_mem_write(struct file *file, const char __user *buffer, + size_t size, loff_t *pos) +{ + struct spu_context *ctx = file->private_data; + struct spu *spu = ctx->spu; + + if (spu->number & 0) //1) + return generic_file_write(file, buffer, size, pos); + + size = min_t(ssize_t, LS_SIZE - *pos, size); + if (size <= 0) + return -EFBIG; + *pos += size; + return copy_from_user(spu->local_store + *pos - size, + buffer, size) ? -EFAULT : size; +} + +static int +spufs_mem_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct spu_context *ctx = file->private_data; + struct spu *spu = ctx->spu; + unsigned long pfn; + + if (spu->number & 0) //1) + return generic_file_mmap(file, vma); + + vma->vm_flags |= VM_RESERVED; + vma->vm_page_prot = __pgprot(pgprot_val (vma->vm_page_prot) + | _PAGE_NO_CACHE); + pfn = spu->local_store_phys >> PAGE_SHIFT; + /* + * This will work for actual SPUs, but not for vmalloc memory: + */ + if (remap_pfn_range(vma, vma->vm_start, pfn, + vma->vm_end-vma->vm_start, vma->vm_page_prot)) + return -EAGAIN; + return 0; +} + +static struct file_operations spufs_mem_fops = { + .open = spufs_mem_open, + .read = spufs_mem_read, + .write = spufs_mem_write, + .mmap = spufs_mem_mmap, + .llseek = generic_file_llseek, +}; + +/* generic open function for all pipe-like files */ +static int spufs_pipe_open(struct inode *inode, struct file *file) +{ + struct spufs_inode_info *i = SPUFS_I(inode); + file->private_data = i->i_ctx; + + return nonseekable_open(inode, file); +} + +static ssize_t spufs_mbox_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + struct spu_problem __iomem *prob; + u32 mbox_stat; + u32 mbox_data; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + prob = ctx->spu->problem; + mbox_stat = in_be32(&prob->mb_stat_R); + if (!(mbox_stat & 0x0000ff)) + return -EAGAIN; + + mbox_data = in_be32(&prob->pu_mb_R); + + if (copy_to_user(buf, &mbox_data, sizeof mbox_data)) + return -EFAULT; + + return 4; +} + +static struct file_operations spufs_mbox_fops = { + .open = spufs_pipe_open, + .read = spufs_mbox_read, +}; + +static ssize_t spufs_mbox_stat_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + u32 mbox_stat; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + mbox_stat = in_be32(&ctx->spu->problem->mb_stat_R) & 0xff; + + if (copy_to_user(buf, &mbox_stat, sizeof mbox_stat)) + return -EFAULT; + + return 4; +} + +static struct file_operations spufs_mbox_stat_fops = { + .open = spufs_pipe_open, + .read = spufs_mbox_stat_read, +}; + +/* low-level ibox access function */ +size_t spu_ibox_read(struct spu *spu, u32 *data) +{ + int ret; + + spin_lock_irq(&spu->register_lock); + + if (in_be32(&spu->problem->mb_stat_R) & 0xff0000) { + /* read the first available word */ + *data = in_be64(&spu->priv2->puint_mb_R); + ret = 4; + } else { + /* make sure we get woken up by the interrupt */ + out_be64(&spu->priv1->int_mask_class2_RW, + in_be64(&spu->priv1->int_mask_class2_RW) | 0x1); + ret = 0; + } + + spin_unlock_irq(&spu->register_lock); + return ret; +} +EXPORT_SYMBOL(spu_ibox_read); + +static int spufs_ibox_fasync(int fd, struct file *file, int on) +{ + struct spu_context *ctx; + ctx = file->private_data; + return fasync_helper(fd, file, on, &ctx->spu->ibox_fasync); +} + +static ssize_t spufs_ibox_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + u32 ibox_data; + ssize_t ret; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + + ret = 0; + if (file->f_flags & O_NONBLOCK) { + if (!spu_ibox_read(ctx->spu, &ibox_data)) + ret = -EAGAIN; + } else { + ret = wait_event_interruptible(ctx->spu->ibox_wq, + spu_ibox_read(ctx->spu, &ibox_data)); + } + + if (ret) + return ret; + + ret = 4; + if (copy_to_user(buf, &ibox_data, sizeof ibox_data)) + ret = -EFAULT; + + return ret; +} + +static unsigned int spufs_ibox_poll(struct file *file, poll_table *wait) +{ + struct spu_context *ctx; + struct spu_problem __iomem *prob; + u32 mbox_stat; + unsigned int mask; + + ctx = file->private_data; + prob = ctx->spu->problem; + mbox_stat = in_be32(&prob->mb_stat_R); + + poll_wait(file, &ctx->spu->ibox_wq, wait); + + mask = 0; + if (mbox_stat & 0xff0000) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static struct file_operations spufs_ibox_fops = { + .open = spufs_pipe_open, + .read = spufs_ibox_read, + .poll = spufs_ibox_poll, + .fasync = spufs_ibox_fasync, +}; + +static ssize_t spufs_ibox_stat_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + u32 ibox_stat; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + ibox_stat = (in_be32(&ctx->spu->problem->mb_stat_R) >> 16) & 0xff; + + if (copy_to_user(buf, &ibox_stat, sizeof ibox_stat)) + return -EFAULT; + + return 4; +} + +static struct file_operations spufs_ibox_stat_fops = { + .open = spufs_pipe_open, + .read = spufs_ibox_stat_read, +}; + +/* low-level mailbox write */ +size_t spu_wbox_write(struct spu *spu, u32 data) +{ + int ret; + + spin_lock_irq(&spu->register_lock); + + if (in_be32(&spu->problem->mb_stat_R) & 0x00ff00) { + /* we have space to write wbox_data to */ + out_be32(&spu->problem->spu_mb_W, data); + ret = 4; + } else { + /* make sure we get woken up by the interrupt when space + becomes available */ + out_be64(&spu->priv1->int_mask_class2_RW, + in_be64(&spu->priv1->int_mask_class2_RW) | 0x10); + ret = 0; + } + + spin_unlock_irq(&spu->register_lock); + return ret; +} +EXPORT_SYMBOL(spu_wbox_write); + +static int spufs_wbox_fasync(int fd, struct file *file, int on) +{ + struct spu_context *ctx; + ctx = file->private_data; + return fasync_helper(fd, file, on, &ctx->spu->wbox_fasync); +} + +static ssize_t spufs_wbox_write(struct file *file, const char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + u32 wbox_data; + int ret; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + + if (copy_from_user(&wbox_data, buf, sizeof wbox_data)) + return -EFAULT; + + ret = 0; + if (file->f_flags & O_NONBLOCK) { + if (!spu_wbox_write(ctx->spu, wbox_data)) + ret = -EAGAIN; + } else { + ret = wait_event_interruptible(ctx->spu->wbox_wq, + spu_wbox_write(ctx->spu, wbox_data)); + } + + return ret ? ret : sizeof wbox_data; +} + +static unsigned int spufs_wbox_poll(struct file *file, poll_table *wait) +{ + struct spu_context *ctx; + struct spu_problem __iomem *prob; + u32 mbox_stat; + unsigned int mask; + + ctx = file->private_data; + prob = ctx->spu->problem; + mbox_stat = in_be32(&prob->mb_stat_R); + + poll_wait(file, &ctx->spu->wbox_wq, wait); + + mask = 0; + if (mbox_stat & 0x00ff00) + mask = POLLOUT | POLLWRNORM; + + return mask; +} + +static struct file_operations spufs_wbox_fops = { + .open = spufs_pipe_open, + .write = spufs_wbox_write, + .poll = spufs_wbox_poll, + .fasync = spufs_wbox_fasync, +}; + +static ssize_t spufs_wbox_stat_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + u32 wbox_stat; + + if (len < 4) + return -EINVAL; + + ctx = file->private_data; + wbox_stat = (in_be32(&ctx->spu->problem->mb_stat_R) >> 8) & 0xff; + + if (copy_to_user(buf, &wbox_stat, sizeof wbox_stat)) + return -EFAULT; + + return 4; +} + +static struct file_operations spufs_wbox_stat_fops = { + .open = spufs_pipe_open, + .read = spufs_wbox_stat_read, +}; + +long spufs_run_spu(struct file *file, struct spu_context *ctx, + u32 *npc, u32 *status) +{ + struct spu_problem __iomem *prob; + int ret; + + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + if (!down_write_trylock(&ctx->backing_sema)) + goto out; + } else { + down_write(&ctx->backing_sema); + } + + prob = ctx->spu->problem; + out_be32(&prob->spu_npc_RW, *npc); + + ret = spu_run(ctx->spu); + + *status = in_be32(&prob->spu_status_R); + *npc = in_be32(&prob->spu_npc_RW); + + up_write(&ctx->backing_sema); + +out: + return ret; +} + +static ssize_t spufs_signal1_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + struct spu_problem *prob; + u32 data; + + ctx = file->private_data; + prob = ctx->spu->problem; + + if (len < 4) + return -EINVAL; + + data = in_be32(&prob->signal_notify1); + if (copy_to_user(buf, &data, 4)) + return -EFAULT; + + return 4; +} + +static ssize_t spufs_signal1_write(struct file *file, const char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + struct spu_problem *prob; + u32 data; + + ctx = file->private_data; + prob = ctx->spu->problem; + + if (len < 4) + return -EINVAL; + + if (copy_from_user(&data, buf, 4)) + return -EFAULT; + + out_be32(&prob->signal_notify1, data); + + return 4; +} + +static struct file_operations spufs_signal1_fops = { + .open = spufs_pipe_open, + .read = spufs_signal1_read, + .write = spufs_signal1_write, +}; + +static ssize_t spufs_signal2_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + struct spu_problem *prob; + u32 data; + + ctx = file->private_data; + prob = ctx->spu->problem; + + if (len < 4) + return -EINVAL; + + data = in_be32(&prob->signal_notify2); + if (copy_to_user(buf, &data, 4)) + return -EFAULT; + + return 4; +} + +static ssize_t spufs_signal2_write(struct file *file, const char __user *buf, + size_t len, loff_t *pos) +{ + struct spu_context *ctx; + struct spu_problem *prob; + u32 data; + + ctx = file->private_data; + prob = ctx->spu->problem; + + if (len < 4) + return -EINVAL; + + if (copy_from_user(&data, buf, 4)) + return -EFAULT; + + out_be32(&prob->signal_notify2, data); + + return 4; +} + +static struct file_operations spufs_signal2_fops = { + .open = spufs_pipe_open, + .read = spufs_signal2_read, + .write = spufs_signal2_write, +}; + +static void spufs_signal1_type_set(void *data, u64 val) +{ + struct spu_context *ctx = data; + struct spu_priv2 *priv2 = ctx->spu->priv2; + u64 tmp; + + spin_lock_irq(&ctx->spu->register_lock); + tmp = in_be64(&priv2->spu_cfg_RW); + if (val) + tmp |= 1; + else + tmp &= ~1; + out_be64(&priv2->spu_cfg_RW, tmp); + spin_unlock_irq(&ctx->spu->register_lock); +} + +static u64 spufs_signal1_type_get(void *data) +{ + struct spu_context *ctx = data; + return (in_be64(&ctx->spu->priv2->spu_cfg_RW) & 1) != 0; +} +DEFINE_SIMPLE_ATTRIBUTE(spufs_signal1_type, spufs_signal1_type_get, + spufs_signal1_type_set, "%llu"); + +static void spufs_signal2_type_set(void *data, u64 val) +{ + struct spu_context *ctx = data; + struct spu_priv2 *priv2 = ctx->spu->priv2; + u64 tmp; + + spin_lock_irq(&ctx->spu->register_lock); + tmp = in_be64(&priv2->spu_cfg_RW); + if (val) + tmp |= 2; + else + tmp &= ~2; + out_be64(&priv2->spu_cfg_RW, tmp); + spin_unlock_irq(&ctx->spu->register_lock); +} + +static u64 spufs_signal2_type_get(void *data) +{ + struct spu_context *ctx = data; + return (in_be64(&ctx->spu->priv2->spu_cfg_RW) & 2) != 0; +} +DEFINE_SIMPLE_ATTRIBUTE(spufs_signal2_type, spufs_signal2_type_get, + spufs_signal2_type_set, "%llu"); + +static void spufs_npc_set(void *data, u64 val) +{ + struct spu_context *ctx = data; + out_be32(&ctx->spu->problem->spu_npc_RW, val); +} + +static u64 spufs_npc_get(void *data) +{ + struct spu_context *ctx = data; + u64 ret; + ret = in_be32(&ctx->spu->problem->spu_npc_RW); + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(spufs_npc_ops, spufs_npc_get, spufs_npc_set, "%llx\n") + +struct tree_descr spufs_dir_contents[] = { + { "mem", &spufs_mem_fops, 0666, }, + { "mbox", &spufs_mbox_fops, 0444, }, + { "ibox", &spufs_ibox_fops, 0444, }, + { "wbox", &spufs_wbox_fops, 0222, }, + { "mbox_stat", &spufs_mbox_stat_fops, 0444, }, + { "ibox_stat", &spufs_ibox_stat_fops, 0444, }, + { "wbox_stat", &spufs_wbox_stat_fops, 0444, }, + { "signal1", &spufs_signal1_fops, 0666, }, + { "signal2", &spufs_signal2_fops, 0666, }, + { "signal1_type", &spufs_signal1_type, 0666, }, + { "signal2_type", &spufs_signal2_type, 0666, }, + { "npc", &spufs_npc_ops, 0666, }, + {}, +}; diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c new file mode 100644 index 000000000000..f7aa0a6b1ce5 --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/inode.c @@ -0,0 +1,470 @@ +/* + * SPU file system + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/backing-dev.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/module.h> +#include <linux/namei.h> +#include <linux/pagemap.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/parser.h> + +#include <asm/io.h> +#include <asm/semaphore.h> +#include <asm/spu.h> +#include <asm/uaccess.h> + +#include "spufs.h" + +static kmem_cache_t *spufs_inode_cache; + +/* Information about the backing dev, same as ramfs */ +#if 0 +static struct backing_dev_info spufs_backing_dev_info = { + .ra_pages = 0, /* No readahead */ + .capabilities = BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK | + BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY | BDI_CAP_READ_MAP | + BDI_CAP_WRITE_MAP, +}; + +static struct address_space_operations spufs_aops = { + .readpage = simple_readpage, + .prepare_write = simple_prepare_write, + .commit_write = simple_commit_write, +}; +#endif + +/* Inode operations */ + +static struct inode * +spufs_alloc_inode(struct super_block *sb) +{ + struct spufs_inode_info *ei; + + ei = kmem_cache_alloc(spufs_inode_cache, SLAB_KERNEL); + if (!ei) + return NULL; + return &ei->vfs_inode; +} + +static void +spufs_destroy_inode(struct inode *inode) +{ + kmem_cache_free(spufs_inode_cache, SPUFS_I(inode)); +} + +static void +spufs_init_once(void *p, kmem_cache_t * cachep, unsigned long flags) +{ + struct spufs_inode_info *ei = p; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) { + inode_init_once(&ei->vfs_inode); + } +} + +static struct inode * +spufs_new_inode(struct super_block *sb, int mode) +{ + struct inode *inode; + + inode = new_inode(sb); + if (!inode) + goto out; + + inode->i_mode = mode; + inode->i_uid = current->fsuid; + inode->i_gid = current->fsgid; + inode->i_blksize = PAGE_CACHE_SIZE; + inode->i_blocks = 0; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; +out: + return inode; +} + +static int +spufs_setattr(struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = dentry->d_inode; + +/* dump_stack(); + pr_debug("ia_size %lld, i_size:%lld\n", attr->ia_size, inode->i_size); +*/ + if ((attr->ia_valid & ATTR_SIZE) && + (attr->ia_size != inode->i_size)) + return -EINVAL; + return inode_setattr(inode, attr); +} + + +static int +spufs_new_file(struct super_block *sb, struct dentry *dentry, + struct file_operations *fops, int mode, + struct spu_context *ctx) +{ + static struct inode_operations spufs_file_iops = { + .getattr = simple_getattr, + .setattr = spufs_setattr, + .unlink = simple_unlink, + }; + struct inode *inode; + int ret; + + ret = -ENOSPC; + inode = spufs_new_inode(sb, S_IFREG | mode); + if (!inode) + goto out; + + ret = 0; + inode->i_op = &spufs_file_iops; + inode->i_fop = fops; + inode->u.generic_ip = SPUFS_I(inode)->i_ctx = get_spu_context(ctx); + d_add(dentry, inode); +out: + return ret; +} + +static void +spufs_delete_inode(struct inode *inode) +{ + if (SPUFS_I(inode)->i_ctx) + put_spu_context(SPUFS_I(inode)->i_ctx); + clear_inode(inode); +} + +static int +spufs_fill_dir(struct dentry *dir, struct tree_descr *files, + int mode, struct spu_context *ctx) +{ + struct dentry *dentry; + int ret; + + while (files->name && files->name[0]) { + ret = -ENOMEM; + dentry = d_alloc_name(dir, files->name); + if (!dentry) + goto out; + ret = spufs_new_file(dir->d_sb, dentry, files->ops, + files->mode & mode, ctx); + if (ret) + goto out; + files++; + } + return 0; +out: + // FIXME: remove all files that are left + + return ret; +} + +static int spufs_rmdir(struct inode *root, struct dentry *dir_dentry) +{ + struct dentry *dentry; + int err; + + spin_lock(&dcache_lock); + /* remove all entries */ + err = 0; + list_for_each_entry(dentry, &dir_dentry->d_subdirs, d_child) { + if (d_unhashed(dentry) || !dentry->d_inode) + continue; + atomic_dec(&dentry->d_count); + spin_lock(&dentry->d_lock); + __d_drop(dentry); + spin_unlock(&dentry->d_lock); + } + spin_unlock(&dcache_lock); + if (!err) { + shrink_dcache_parent(dir_dentry); + err = simple_rmdir(root, dir_dentry); + } + return err; +} + +static int spufs_dir_close(struct inode *inode, struct file *file) +{ + struct inode *dir; + struct dentry *dentry; + int ret; + + dentry = file->f_dentry; + dir = dentry->d_parent->d_inode; + down(&dir->i_sem); + ret = spufs_rmdir(dir, file->f_dentry); + WARN_ON(ret); + up(&dir->i_sem); + return dcache_dir_close(inode, file); +} + +struct inode_operations spufs_dir_inode_operations = { + .lookup = simple_lookup, +}; + +struct file_operations spufs_autodelete_dir_operations = { + .open = dcache_dir_open, + .release = spufs_dir_close, + .llseek = dcache_dir_lseek, + .read = generic_read_dir, + .readdir = dcache_readdir, + .fsync = simple_sync_file, +}; + +static int +spufs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int ret; + struct inode *inode; + struct spu_context *ctx; + + ret = -ENOSPC; + inode = spufs_new_inode(dir->i_sb, mode | S_IFDIR); + if (!inode) + goto out; + + if (dir->i_mode & S_ISGID) { + inode->i_gid = dir->i_gid; + inode->i_mode &= S_ISGID; + } + ctx = alloc_spu_context(); + SPUFS_I(inode)->i_ctx = ctx; + if (!ctx) + goto out_iput; + + inode->i_op = &spufs_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + ret = spufs_fill_dir(dentry, spufs_dir_contents, mode, ctx); + if (ret) + goto out_free_ctx; + + d_instantiate(dentry, inode); + dget(dentry); + dir->i_nlink++; + goto out; + +out_free_ctx: + put_spu_context(ctx); +out_iput: + iput(inode); +out: + return ret; +} + +long +spufs_create_thread(struct nameidata *nd, const char *name, + unsigned int flags, mode_t mode) +{ + struct dentry *dentry; + struct file *filp; + int ret; + + /* need to be at the root of spufs */ + ret = -EINVAL; + if (nd->dentry->d_sb->s_magic != SPUFS_MAGIC || + nd->dentry != nd->dentry->d_sb->s_root) + goto out; + + dentry = lookup_create(nd, 1); + ret = PTR_ERR(dentry); + if (IS_ERR(dentry)) + goto out_dir; + + ret = -EEXIST; + if (dentry->d_inode) + goto out_dput; + + mode &= ~current->fs->umask; + ret = spufs_mkdir(nd->dentry->d_inode, dentry, mode & S_IRWXUGO); + if (ret) + goto out_dput; + + ret = get_unused_fd(); + if (ret < 0) + goto out_dput; + + dentry->d_inode->i_nlink++; + + filp = filp_open(name, O_RDONLY, mode); + if (IS_ERR(filp)) { + // FIXME: remove directory again + put_unused_fd(ret); + ret = PTR_ERR(filp); + } else { + filp->f_op = &spufs_autodelete_dir_operations; + fd_install(ret, filp); + } + +out_dput: + dput(dentry); +out_dir: + up(&nd->dentry->d_inode->i_sem); +out: + return ret; +} + +/* File system initialization */ +enum { + Opt_uid, Opt_gid, Opt_err, +}; + +static match_table_t spufs_tokens = { + { Opt_uid, "uid=%d" }, + { Opt_gid, "gid=%d" }, + { Opt_err, NULL }, +}; + +static int +spufs_parse_options(char *options, struct inode *root) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + + while ((p = strsep(&options, ",")) != NULL) { + int token, option; + + if (!*p) + continue; + + token = match_token(p, spufs_tokens, args); + switch (token) { + case Opt_uid: + if (match_int(&args[0], &option)) + return 0; + root->i_uid = option; + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return 0; + root->i_gid = option; + break; + default: + return 0; + } + } + return 1; +} + +static int +spufs_create_root(struct super_block *sb, void *data) { + struct inode *inode; + int ret; + + ret = -ENOMEM; + inode = spufs_new_inode(sb, S_IFDIR | 0775); + if (!inode) + goto out; + + inode->i_op = &spufs_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + SPUFS_I(inode)->i_ctx = NULL; + + ret = -EINVAL; + if (!spufs_parse_options(data, inode)) + goto out_iput; + + ret = -ENOMEM; + sb->s_root = d_alloc_root(inode); + if (!sb->s_root) + goto out_iput; + + return 0; +out_iput: + iput(inode); +out: + return ret; +} + +static int +spufs_fill_super(struct super_block *sb, void *data, int silent) +{ + static struct super_operations s_ops = { + .alloc_inode = spufs_alloc_inode, + .destroy_inode = spufs_destroy_inode, + .statfs = simple_statfs, + .delete_inode = spufs_delete_inode, + .drop_inode = generic_delete_inode, + }; + + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = SPUFS_MAGIC; + sb->s_op = &s_ops; + + return spufs_create_root(sb, data); +} + +static struct super_block * +spufs_get_sb(struct file_system_type *fstype, int flags, + const char *name, void *data) +{ + return get_sb_single(fstype, flags, data, spufs_fill_super); +} + +static struct file_system_type spufs_type = { + .owner = THIS_MODULE, + .name = "spufs", + .get_sb = spufs_get_sb, + .kill_sb = kill_litter_super, +}; + +static int spufs_init(void) +{ + int ret; + ret = -ENOMEM; + spufs_inode_cache = kmem_cache_create("spufs_inode_cache", + sizeof(struct spufs_inode_info), 0, + SLAB_HWCACHE_ALIGN, spufs_init_once, NULL); + + if (!spufs_inode_cache) + goto out; + ret = register_filesystem(&spufs_type); + if (ret) + goto out_cache; + ret = register_spu_syscalls(&spufs_calls); + if (ret) + goto out_fs; + return 0; +out_fs: + unregister_filesystem(&spufs_type); +out_cache: + kmem_cache_destroy(spufs_inode_cache); +out: + return ret; +} +module_init(spufs_init); + +static void spufs_exit(void) +{ + unregister_spu_syscalls(&spufs_calls); + unregister_filesystem(&spufs_type); + kmem_cache_destroy(spufs_inode_cache); +} +module_exit(spufs_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>"); + diff --git a/arch/powerpc/platforms/cell/spufs/spufs.h b/arch/powerpc/platforms/cell/spufs/spufs.h new file mode 100644 index 000000000000..b37fe797ea1c --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/spufs.h @@ -0,0 +1,71 @@ +/* + * SPU file system + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 + * + * Author: Arnd Bergmann <arndb@de.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef SPUFS_H +#define SPUFS_H + +#include <linux/kref.h> +#include <linux/rwsem.h> +#include <linux/spinlock.h> +#include <linux/fs.h> + +#include <asm/spu.h> + +/* The magic number for our file system */ +enum { + SPUFS_MAGIC = 0x23c9b64e, +}; + +struct spu_context { + struct spu *spu; /* pointer to a physical SPU */ + struct rw_semaphore backing_sema; /* protects the above */ + spinlock_t mmio_lock; /* protects mmio access */ + + struct kref kref; +}; + +struct spufs_inode_info { + struct spu_context *i_ctx; + struct inode vfs_inode; +}; +#define SPUFS_I(inode) \ + container_of(inode, struct spufs_inode_info, vfs_inode) + +extern struct tree_descr spufs_dir_contents[]; + +/* system call implementation */ +long spufs_run_spu(struct file *file, + struct spu_context *ctx, u32 *npc, u32 *status); +long spufs_create_thread(struct nameidata *nd, const char *name, + unsigned int flags, mode_t mode); + +/* context management */ +struct spu_context * alloc_spu_context(void); +void destroy_spu_context(struct kref *kref); +struct spu_context * get_spu_context(struct spu_context *ctx); +int put_spu_context(struct spu_context *ctx); + +void spu_acquire(struct spu_context *ctx); +void spu_release(struct spu_context *ctx); +void spu_acquire_runnable(struct spu_context *ctx); +void spu_acquire_saved(struct spu_context *ctx); + +#endif diff --git a/arch/powerpc/platforms/cell/spufs/syscalls.c b/arch/powerpc/platforms/cell/spufs/syscalls.c new file mode 100644 index 000000000000..3f71bb5e9d8e --- /dev/null +++ b/arch/powerpc/platforms/cell/spufs/syscalls.c @@ -0,0 +1,106 @@ +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/mount.h> +#include <linux/namei.h> + +#include <asm/uaccess.h> + +#include "spufs.h" + +/** + * sys_spu_run - run code loaded into an SPU + * + * @unpc: next program counter for the SPU + * @ustatus: status of the SPU + * + * This system call transfers the control of execution of a + * user space thread to an SPU. It will return when the + * SPU has finished executing or when it hits an error + * condition and it will be interrupted if a signal needs + * to be delivered to a handler in user space. + * + * The next program counter is set to the passed value + * before the SPU starts fetching code and the user space + * pointer gets updated with the new value when returning + * from kernel space. + * + * The status value returned from spu_run reflects the + * value of the spu_status register after the SPU has stopped. + * + */ +long do_spu_run(struct file *filp, __u32 __user *unpc, __u32 __user *ustatus) +{ + long ret; + struct spufs_inode_info *i; + u32 npc, status; + + ret = -EFAULT; + if (get_user(npc, unpc)) + goto out; + + ret = -EINVAL; + if (filp->f_vfsmnt->mnt_sb->s_magic != SPUFS_MAGIC) + goto out; + + i = SPUFS_I(filp->f_dentry->d_inode); + ret = spufs_run_spu(filp, i->i_ctx, &npc, &status); + + if (ret ==-EAGAIN || ret == -EIO) + ret = status; + + if (put_user(npc, unpc)) + ret = -EFAULT; + + if (ustatus && put_user(status, ustatus)) + ret = -EFAULT; +out: + return ret; +} + +#ifndef MODULE +asmlinkage long sys_spu_run(int fd, __u32 __user *unpc, __u32 __user *ustatus) +{ + int fput_needed; + struct file *filp; + long ret; + + ret = -EBADF; + filp = fget_light(fd, &fput_needed); + if (filp) { + ret = do_spu_run(filp, unpc, ustatus); + fput_light(filp, fput_needed); + } + + return ret; +} +#endif + +asmlinkage long sys_spu_create(const char __user *pathname, + unsigned int flags, mode_t mode) +{ + char *tmp; + int ret; + + tmp = getname(pathname); + ret = PTR_ERR(tmp); + if (!IS_ERR(tmp)) { + struct nameidata nd; + + ret = path_lookup(tmp, LOOKUP_PARENT| + LOOKUP_OPEN|LOOKUP_CREATE, &nd); + if (!ret) { + ret = spufs_create_thread(&nd, pathname, flags, mode); + path_release(&nd); + } + putname(tmp); + } + + return ret; +} + +struct spufs_calls spufs_calls = { + .create_thread = sys_spu_create, + .spu_run = do_spu_run, + .owner = THIS_MODULE, +}; |