diff options
Diffstat (limited to 'arch/powerpc/platforms/ps3')
-rw-r--r-- | arch/powerpc/platforms/ps3/Kconfig | 54 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/Makefile | 4 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/exports.c | 27 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/htab.c | 277 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/hvcall.S | 804 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/interrupt.c | 575 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/mm.c | 831 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/os-area.c | 259 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/platform.h | 68 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/repository.c | 840 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/setup.c | 173 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/smp.c | 158 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/spu.c | 613 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/time.c | 104 |
14 files changed, 4787 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/ps3/Kconfig b/arch/powerpc/platforms/ps3/Kconfig new file mode 100644 index 00000000000..de52ec4e9e5 --- /dev/null +++ b/arch/powerpc/platforms/ps3/Kconfig @@ -0,0 +1,54 @@ +menu "PS3 Platform Options" + depends on PPC_PS3 + +config PS3_HTAB_SIZE + depends on PPC_PS3 + int "PS3 Platform pagetable size" + range 18 20 + default 20 + help + This option is only for experts who may have the desire to fine + tune the pagetable size on their system. The value here is + expressed as the log2 of the page table size. Valid values are + 18, 19, and 20, corresponding to 256KB, 512KB and 1MB respectively. + + If unsure, choose the default (20) with the confidence that your + system will have optimal runtime performance. + +config PS3_DYNAMIC_DMA + depends on PPC_PS3 && EXPERIMENTAL + bool "PS3 Platform dynamic DMA page table management" + default n + help + This option will enable kernel support to take advantage of the + per device dynamic DMA page table management provided by the Cell + processor's IO Controller. This support incurs some runtime + overhead and also slightly increases kernel memory usage. The + current implementation should be considered experimental. + + This support is mainly for Linux kernel development. If unsure, + say N. + +config PS3_USE_LPAR_ADDR + depends on PPC_PS3 && EXPERIMENTAL + bool "PS3 use lpar address space" + default y + help + This option is solely for experimentation by experts. Disables + translation of lpar addresses. SPE support currently won't work + without this set to y. + + If you have any doubt, choose the default y. + +config PS3_VUART + depends on PPC_PS3 + bool "PS3 Virtual UART support" + default y + help + Include support for the PS3 Virtual UART. + + This support is required for several system services + including the System Manager and AV Settings. In + general, all users will say Y. + +endmenu diff --git a/arch/powerpc/platforms/ps3/Makefile b/arch/powerpc/platforms/ps3/Makefile new file mode 100644 index 00000000000..3757cfabc8c --- /dev/null +++ b/arch/powerpc/platforms/ps3/Makefile @@ -0,0 +1,4 @@ +obj-y += setup.o mm.o smp.o time.o hvcall.o htab.o repository.o +obj-y += interrupt.o exports.o os-area.o + +obj-$(CONFIG_SPU_BASE) += spu.o diff --git a/arch/powerpc/platforms/ps3/exports.c b/arch/powerpc/platforms/ps3/exports.c new file mode 100644 index 00000000000..a7e8ffd24a6 --- /dev/null +++ b/arch/powerpc/platforms/ps3/exports.c @@ -0,0 +1,27 @@ +/* + * PS3 hvcall exports for modules. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> + +#define LV1_CALL(name, in, out, num) \ + extern s64 _lv1_##name(LV1_##in##_IN_##out##_OUT_ARG_DECL); \ + EXPORT_SYMBOL(_lv1_##name); + +#include <asm/lv1call.h> diff --git a/arch/powerpc/platforms/ps3/htab.c b/arch/powerpc/platforms/ps3/htab.c new file mode 100644 index 00000000000..8fe1769655a --- /dev/null +++ b/arch/powerpc/platforms/ps3/htab.c @@ -0,0 +1,277 @@ +/* + * PS3 pagetable management routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> + +#include <asm/machdep.h> +#include <asm/lmb.h> +#include <asm/udbg.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) do{if(0)printk(fmt);}while(0) +#endif + +static hpte_t *htab; +static unsigned long htab_addr; +static unsigned char *bolttab; +static unsigned char *inusetab; + +static spinlock_t ps3_bolttab_lock = SPIN_LOCK_UNLOCKED; + +#define debug_dump_hpte(_a, _b, _c, _d, _e, _f, _g) \ + _debug_dump_hpte(_a, _b, _c, _d, _e, _f, _g, __func__, __LINE__) +static void _debug_dump_hpte(unsigned long pa, unsigned long va, + unsigned long group, unsigned long bitmap, hpte_t lhpte, int psize, + unsigned long slot, const char* func, int line) +{ + DBG("%s:%d: pa = %lxh\n", func, line, pa); + DBG("%s:%d: lpar = %lxh\n", func, line, + ps3_mm_phys_to_lpar(pa)); + DBG("%s:%d: va = %lxh\n", func, line, va); + DBG("%s:%d: group = %lxh\n", func, line, group); + DBG("%s:%d: bitmap = %lxh\n", func, line, bitmap); + DBG("%s:%d: hpte.v = %lxh\n", func, line, lhpte.v); + DBG("%s:%d: hpte.r = %lxh\n", func, line, lhpte.r); + DBG("%s:%d: psize = %xh\n", func, line, psize); + DBG("%s:%d: slot = %lxh\n", func, line, slot); +} + +static long ps3_hpte_insert(unsigned long hpte_group, unsigned long va, + unsigned long pa, unsigned long rflags, unsigned long vflags, int psize) +{ + unsigned long slot; + hpte_t lhpte; + int secondary = 0; + unsigned long result; + unsigned long bitmap; + unsigned long flags; + unsigned long p_pteg, s_pteg, b_index, b_mask, cb, ci; + + vflags &= ~HPTE_V_SECONDARY; /* this bit is ignored */ + + lhpte.v = hpte_encode_v(va, psize) | vflags | HPTE_V_VALID; + lhpte.r = hpte_encode_r(ps3_mm_phys_to_lpar(pa), psize) | rflags; + + p_pteg = hpte_group / HPTES_PER_GROUP; + s_pteg = ~p_pteg & htab_hash_mask; + + spin_lock_irqsave(&ps3_bolttab_lock, flags); + + BUG_ON(bolttab[p_pteg] == 0xff && bolttab[s_pteg] == 0xff); + + bitmap = (inusetab[p_pteg] << 8) | inusetab[s_pteg]; + + if (bitmap == 0xffff) { + /* + * PTEG is full. Search for victim. + */ + bitmap &= ~((bolttab[p_pteg] << 8) | bolttab[s_pteg]); + do { + ci = mftb() & 15; + cb = 0x8000UL >> ci; + } while ((cb & bitmap) == 0); + } else { + /* + * search free slot in hardware order + * [primary] 0, 2, 4, 6, 1, 3, 5, 7 + * [secondary] 0, 2, 4, 6, 1, 3, 5, 7 + */ + for (ci = 0; ci < HPTES_PER_GROUP; ci += 2) { + cb = 0x8000UL >> ci; + if ((cb & bitmap) == 0) + goto found; + } + for (ci = 1; ci < HPTES_PER_GROUP; ci += 2) { + cb = 0x8000UL >> ci; + if ((cb & bitmap) == 0) + goto found; + } + for (ci = HPTES_PER_GROUP; ci < HPTES_PER_GROUP*2; ci += 2) { + cb = 0x8000UL >> ci; + if ((cb & bitmap) == 0) + goto found; + } + for (ci = HPTES_PER_GROUP+1; ci < HPTES_PER_GROUP*2; ci += 2) { + cb = 0x8000UL >> ci; + if ((cb & bitmap) == 0) + goto found; + } + } + +found: + if (ci < HPTES_PER_GROUP) { + slot = p_pteg * HPTES_PER_GROUP + ci; + } else { + slot = s_pteg * HPTES_PER_GROUP + (ci & 7); + /* lhpte.dw0.dw0.h = 1; */ + vflags |= HPTE_V_SECONDARY; + lhpte.v |= HPTE_V_SECONDARY; + } + + result = lv1_write_htab_entry(0, slot, lhpte.v, lhpte.r); + + if (result) { + debug_dump_hpte(pa, va, hpte_group, bitmap, lhpte, psize, slot); + BUG(); + } + + /* + * If used slot is not in primary HPTE group, + * the slot should be in secondary HPTE group. + */ + + if ((hpte_group ^ slot) & ~(HPTES_PER_GROUP - 1)) { + secondary = 1; + b_index = s_pteg; + } else { + secondary = 0; + b_index = p_pteg; + } + + b_mask = (lhpte.v & HPTE_V_BOLTED) ? 1 << 7 : 0 << 7; + bolttab[b_index] |= b_mask >> (slot & 7); + b_mask = 1 << 7; + inusetab[b_index] |= b_mask >> (slot & 7); + spin_unlock_irqrestore(&ps3_bolttab_lock, flags); + + return (slot & 7) | (secondary << 3); +} + +static long ps3_hpte_remove(unsigned long hpte_group) +{ + panic("ps3_hpte_remove() not implemented"); + return 0; +} + +static long ps3_hpte_updatepp(unsigned long slot, unsigned long newpp, + unsigned long va, int psize, int local) +{ + unsigned long flags; + unsigned long result; + unsigned long pteg, bit; + unsigned long hpte_v, want_v; + + want_v = hpte_encode_v(va, psize); + + spin_lock_irqsave(&ps3_bolttab_lock, flags); + + hpte_v = htab[slot].v; + if (!HPTE_V_COMPARE(hpte_v, want_v) || !(hpte_v & HPTE_V_VALID)) { + spin_unlock_irqrestore(&ps3_bolttab_lock, flags); + + /* ps3_hpte_insert() will be used to update PTE */ + return -1; + } + + result = lv1_write_htab_entry(0, slot, 0, 0); + + if (result) { + DBG("%s: va=%lx slot=%lx psize=%d result = %ld (0x%lx)\n", + __func__, va, slot, psize, result, result); + BUG(); + } + + pteg = slot / HPTES_PER_GROUP; + bit = slot % HPTES_PER_GROUP; + inusetab[pteg] &= ~(0x80 >> bit); + + spin_unlock_irqrestore(&ps3_bolttab_lock, flags); + + /* ps3_hpte_insert() will be used to update PTE */ + return -1; +} + +static void ps3_hpte_updateboltedpp(unsigned long newpp, unsigned long ea, + int psize) +{ + panic("ps3_hpte_updateboltedpp() not implemented"); +} + +static void ps3_hpte_invalidate(unsigned long slot, unsigned long va, + int psize, int local) +{ + unsigned long flags; + unsigned long result; + unsigned long pteg, bit; + + spin_lock_irqsave(&ps3_bolttab_lock, flags); + result = lv1_write_htab_entry(0, slot, 0, 0); + + if (result) { + DBG("%s: va=%lx slot=%lx psize=%d result = %ld (0x%lx)\n", + __func__, va, slot, psize, result, result); + BUG(); + } + + pteg = slot / HPTES_PER_GROUP; + bit = slot % HPTES_PER_GROUP; + inusetab[pteg] &= ~(0x80 >> bit); + spin_unlock_irqrestore(&ps3_bolttab_lock, flags); +} + +static void ps3_hpte_clear(void) +{ + lv1_unmap_htab(htab_addr); +} + +void __init ps3_hpte_init(unsigned long htab_size) +{ + long bitmap_size; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + ppc_md.hpte_invalidate = ps3_hpte_invalidate; + ppc_md.hpte_updatepp = ps3_hpte_updatepp; + ppc_md.hpte_updateboltedpp = ps3_hpte_updateboltedpp; + ppc_md.hpte_insert = ps3_hpte_insert; + ppc_md.hpte_remove = ps3_hpte_remove; + ppc_md.hpte_clear_all = ps3_hpte_clear; + + ppc64_pft_size = __ilog2(htab_size); + + bitmap_size = htab_size / sizeof(hpte_t) / 8; + + bolttab = __va(lmb_alloc(bitmap_size, 1)); + inusetab = __va(lmb_alloc(bitmap_size, 1)); + + memset(bolttab, 0, bitmap_size); + memset(inusetab, 0, bitmap_size); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +void __init ps3_map_htab(void) +{ + long result; + unsigned long htab_size = (1UL << ppc64_pft_size); + + result = lv1_map_htab(0, &htab_addr); + + htab = (hpte_t *)__ioremap(htab_addr, htab_size, PAGE_READONLY_X); + + DBG("%s:%d: lpar %016lxh, virt %016lxh\n", __func__, __LINE__, + htab_addr, (unsigned long)htab); +} diff --git a/arch/powerpc/platforms/ps3/hvcall.S b/arch/powerpc/platforms/ps3/hvcall.S new file mode 100644 index 00000000000..54be6523a0e --- /dev/null +++ b/arch/powerpc/platforms/ps3/hvcall.S @@ -0,0 +1,804 @@ +/* + * PS3 hvcall interface. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * Copyright 2003, 2004 (c) MontaVista Software, Inc. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <asm/processor.h> +#include <asm/ppc_asm.h> + +#define lv1call .long 0x44000022; extsw r3, r3 + +#define LV1_N_IN_0_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_1_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_2_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_3_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_4_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_5_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_6_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_7_IN_0_OUT LV1_N_IN_0_OUT + +#define LV1_0_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r3, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + stdu r4, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + std r4, -16(r1); \ + stdu r5, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_7_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + std r4, -16(r1); \ + std r5, -24(r1); \ + std r6, -32(r1); \ + std r7, -40(r1); \ + std r8, -48(r1); \ + stdu r9, -56(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 56; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + ld r11, -56(r1); \ + std r10, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r4, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + stdu r5, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + stdu r6, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_4_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + stdu r7, -32(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 32; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_5_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + stdu r8, -40(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 40; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_6_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + std r8, -40(r1); \ + stdu r9, -48(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 48; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_7_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + std r8, -40(r1); \ + std r9, -48(r1); \ + stdu r10, -56(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 56; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + ld r11, -56(r1); \ + std r10, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r5, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + stdu r6, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + stdu r7, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_4_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + std r7, -24(r1); \ + stdu r8, -32(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 32; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_5_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + std r7, -24(r1); \ + std r8, -32(r1); \ + stdu r9, -40(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 40; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r6, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r6, -8(r1); \ + stdu r7, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r6, -8(r1); \ + std r7, -16(r1); \ + stdu r8, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r7, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r7, -8(r1); \ + stdu r8, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r7, -8(r1); \ + std r8, -16(r1); \ + stdu r9, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r8, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r8, -8(r1); \ + stdu r9, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r8, -8(r1); \ + std r9, -16(r1); \ + stdu r10, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r9, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r9, -8(r1); \ + stdu r10, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r9, -8(r1); \ + stdu r10, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, 48+8*8(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_7_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r10, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_7_IN_6_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r10, 48+8*7(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r11, 48+8*7(r1); \ + std r4, 0(r11); \ + ld r11, 48+8*8(r1); \ + std r5, 0(r11); \ + ld r11, 48+8*9(r1); \ + std r6, 0(r11); \ + ld r11, 48+8*10(r1); \ + std r7, 0(r11); \ + ld r11, 48+8*11(r1); \ + std r8, 0(r11); \ + ld r11, 48+8*12(r1); \ + std r9, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_8_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r11, 48+8*8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + + .text + +/* the lv1 underscored call definitions expand here */ + +#define LV1_CALL(name, in, out, num) LV1_##in##_IN_##out##_OUT(lv1_##name, num) +#include <asm/lv1call.h> diff --git a/arch/powerpc/platforms/ps3/interrupt.c b/arch/powerpc/platforms/ps3/interrupt.c new file mode 100644 index 00000000000..056c1e4141b --- /dev/null +++ b/arch/powerpc/platforms/ps3/interrupt.c @@ -0,0 +1,575 @@ +/* + * PS3 interrupt routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/irq.h> + +#include <asm/machdep.h> +#include <asm/udbg.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) do{if(0)printk(fmt);}while(0) +#endif + +/** + * ps3_alloc_io_irq - Assign a virq to a system bus device. + * interrupt_id: The device interrupt id read from the system repository. + * @virq: The assigned Linux virq. + * + * An io irq represents a non-virtualized device interrupt. interrupt_id + * coresponds to the interrupt number of the interrupt controller. + */ + +int ps3_alloc_io_irq(unsigned int interrupt_id, unsigned int *virq) +{ + int result; + unsigned long outlet; + + result = lv1_construct_io_irq_outlet(interrupt_id, &outlet); + + if (result) { + pr_debug("%s:%d: lv1_construct_io_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + *virq = irq_create_mapping(NULL, outlet); + + pr_debug("%s:%d: interrupt_id %u => outlet %lu, virq %u\n", + __func__, __LINE__, interrupt_id, outlet, *virq); + + return 0; +} + +int ps3_free_io_irq(unsigned int virq) +{ + int result; + + result = lv1_destruct_io_irq_outlet(virq_to_hw(virq)); + + if (!result) + pr_debug("%s:%d: lv1_destruct_io_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + irq_dispose_mapping(virq); + + return result; +} + +/** + * ps3_alloc_event_irq - Allocate a virq for use with a system event. + * @virq: The assigned Linux virq. + * + * The virq can be used with lv1_connect_interrupt_event_receive_port() to + * arrange to receive events, or with ps3_send_event_locally() to signal + * events. + */ + +int ps3_alloc_event_irq(unsigned int *virq) +{ + int result; + unsigned long outlet; + + result = lv1_construct_event_receive_port(&outlet); + + if (result) { + pr_debug("%s:%d: lv1_construct_event_receive_port failed: %s\n", + __func__, __LINE__, ps3_result(result)); + *virq = NO_IRQ; + return result; + } + + *virq = irq_create_mapping(NULL, outlet); + + pr_debug("%s:%d: outlet %lu, virq %u\n", __func__, __LINE__, outlet, + *virq); + + return 0; +} + +int ps3_free_event_irq(unsigned int virq) +{ + int result; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + result = lv1_destruct_event_receive_port(virq_to_hw(virq)); + + if (result) + pr_debug("%s:%d: lv1_destruct_event_receive_port failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + irq_dispose_mapping(virq); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +int ps3_send_event_locally(unsigned int virq) +{ + return lv1_send_event_locally(virq_to_hw(virq)); +} + +/** + * ps3_connect_event_irq - Assign a virq to a system bus device. + * @did: The HV device identifier read from the system repository. + * @interrupt_id: The device interrupt id read from the system repository. + * @virq: The assigned Linux virq. + * + * An event irq represents a virtual device interrupt. The interrupt_id + * coresponds to the software interrupt number. + */ + +int ps3_connect_event_irq(const struct ps3_device_id *did, + unsigned int interrupt_id, unsigned int *virq) +{ + int result; + + result = ps3_alloc_event_irq(virq); + + if (result) + return result; + + result = lv1_connect_interrupt_event_receive_port(did->bus_id, + did->dev_id, virq_to_hw(*virq), interrupt_id); + + if (result) { + pr_debug("%s:%d: lv1_connect_interrupt_event_receive_port" + " failed: %s\n", __func__, __LINE__, + ps3_result(result)); + ps3_free_event_irq(*virq); + *virq = NO_IRQ; + return result; + } + + pr_debug("%s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__, + interrupt_id, *virq); + + return 0; +} + +int ps3_disconnect_event_irq(const struct ps3_device_id *did, + unsigned int interrupt_id, unsigned int virq) +{ + int result; + + pr_debug(" -> %s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__, + interrupt_id, virq); + + result = lv1_disconnect_interrupt_event_receive_port(did->bus_id, + did->dev_id, virq_to_hw(virq), interrupt_id); + + if (result) + pr_debug("%s:%d: lv1_disconnect_interrupt_event_receive_port" + " failed: %s\n", __func__, __LINE__, + ps3_result(result)); + + ps3_free_event_irq(virq); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +/** + * ps3_alloc_vuart_irq - Configure the system virtual uart virq. + * @virt_addr_bmp: The caller supplied virtual uart interrupt bitmap. + * @virq: The assigned Linux virq. + * + * The system supports only a single virtual uart, so multiple calls without + * freeing the interrupt will return a wrong state error. + */ + +int ps3_alloc_vuart_irq(void* virt_addr_bmp, unsigned int *virq) +{ + int result; + unsigned long outlet; + unsigned long lpar_addr; + + BUG_ON(!is_kernel_addr((unsigned long)virt_addr_bmp)); + + lpar_addr = ps3_mm_phys_to_lpar(__pa(virt_addr_bmp)); + + result = lv1_configure_virtual_uart_irq(lpar_addr, &outlet); + + if (result) { + pr_debug("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + *virq = irq_create_mapping(NULL, outlet); + + pr_debug("%s:%d: outlet %lu, virq %u\n", __func__, __LINE__, + outlet, *virq); + + return 0; +} + +int ps3_free_vuart_irq(unsigned int virq) +{ + int result; + + result = lv1_deconfigure_virtual_uart_irq(); + + if (result) { + pr_debug("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + irq_dispose_mapping(virq); + + return result; +} + +/** + * ps3_alloc_spe_irq - Configure an spe virq. + * @spe_id: The spe_id returned from lv1_construct_logical_spe(). + * @class: The spe interrupt class {0,1,2}. + * @virq: The assigned Linux virq. + * + */ + +int ps3_alloc_spe_irq(unsigned long spe_id, unsigned int class, + unsigned int *virq) +{ + int result; + unsigned long outlet; + + BUG_ON(class > 2); + + result = lv1_get_spe_irq_outlet(spe_id, class, &outlet); + + if (result) { + pr_debug("%s:%d: lv1_get_spe_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + *virq = irq_create_mapping(NULL, outlet); + + pr_debug("%s:%d: spe_id %lu, class %u, outlet %lu, virq %u\n", + __func__, __LINE__, spe_id, class, outlet, *virq); + + return 0; +} + +int ps3_free_spe_irq(unsigned int virq) +{ + irq_dispose_mapping(virq); + return 0; +} + +#define PS3_INVALID_OUTLET ((irq_hw_number_t)-1) +#define PS3_PLUG_MAX 63 + +/** + * struct bmp - a per cpu irq status and mask bitmap structure + * @status: 256 bit status bitmap indexed by plug + * @unused_1: + * @mask: 256 bit mask bitmap indexed by plug + * @unused_2: + * @lock: + * @ipi_debug_brk_mask: + * + * The HV mantains per SMT thread mappings of HV outlet to HV plug on + * behalf of the guest. These mappings are implemented as 256 bit guest + * supplied bitmaps indexed by plug number. The address of the bitmaps are + * registered with the HV through lv1_configure_irq_state_bitmap(). + * + * The HV supports 256 plugs per thread, assigned as {0..255}, for a total + * of 512 plugs supported on a processor. To simplify the logic this + * implementation equates HV plug value to linux virq value, constrains each + * interrupt to have a system wide unique plug number, and limits the range + * of the plug values to map into the first dword of the bitmaps. This + * gives a usable range of plug values of {NUM_ISA_INTERRUPTS..63}. Note + * that there is no constraint on how many in this set an individual thread + * can aquire. + */ + +struct bmp { + struct { + unsigned long status; + unsigned long unused_1[3]; + unsigned long mask; + unsigned long unused_2[3]; + } __attribute__ ((packed)); + spinlock_t lock; + unsigned long ipi_debug_brk_mask; +}; + +/** + * struct private - a per cpu data structure + * @node: HV node id + * @cpu: HV thread id + * @bmp: an HV bmp structure + */ + +struct private { + unsigned long node; + unsigned int cpu; + struct bmp bmp; +}; + +#if defined(DEBUG) +static void _dump_64_bmp(const char *header, const unsigned long *p, unsigned cpu, + const char* func, int line) +{ + pr_debug("%s:%d: %s %u {%04lx_%04lx_%04lx_%04lx}\n", + func, line, header, cpu, + *p >> 48, (*p >> 32) & 0xffff, (*p >> 16) & 0xffff, + *p & 0xffff); +} + +static void __attribute__ ((unused)) _dump_256_bmp(const char *header, + const unsigned long *p, unsigned cpu, const char* func, int line) +{ + pr_debug("%s:%d: %s %u {%016lx:%016lx:%016lx:%016lx}\n", + func, line, header, cpu, p[0], p[1], p[2], p[3]); +} + +#define dump_bmp(_x) _dump_bmp(_x, __func__, __LINE__) +static void _dump_bmp(struct private* pd, const char* func, int line) +{ + unsigned long flags; + + spin_lock_irqsave(&pd->bmp.lock, flags); + _dump_64_bmp("stat", &pd->bmp.status, pd->cpu, func, line); + _dump_64_bmp("mask", &pd->bmp.mask, pd->cpu, func, line); + spin_unlock_irqrestore(&pd->bmp.lock, flags); +} + +#define dump_mask(_x) _dump_mask(_x, __func__, __LINE__) +static void __attribute__ ((unused)) _dump_mask(struct private* pd, + const char* func, int line) +{ + unsigned long flags; + + spin_lock_irqsave(&pd->bmp.lock, flags); + _dump_64_bmp("mask", &pd->bmp.mask, pd->cpu, func, line); + spin_unlock_irqrestore(&pd->bmp.lock, flags); +} +#else +static void dump_bmp(struct private* pd) {}; +#endif /* defined(DEBUG) */ + +static void chip_mask(unsigned int virq) +{ + unsigned long flags; + struct private *pd = get_irq_chip_data(virq); + + pr_debug("%s:%d: cpu %u, virq %d\n", __func__, __LINE__, pd->cpu, virq); + + BUG_ON(virq < NUM_ISA_INTERRUPTS); + BUG_ON(virq > PS3_PLUG_MAX); + + spin_lock_irqsave(&pd->bmp.lock, flags); + pd->bmp.mask &= ~(0x8000000000000000UL >> virq); + spin_unlock_irqrestore(&pd->bmp.lock, flags); + + lv1_did_update_interrupt_mask(pd->node, pd->cpu); +} + +static void chip_unmask(unsigned int virq) +{ + unsigned long flags; + struct private *pd = get_irq_chip_data(virq); + + pr_debug("%s:%d: cpu %u, virq %d\n", __func__, __LINE__, pd->cpu, virq); + + BUG_ON(virq < NUM_ISA_INTERRUPTS); + BUG_ON(virq > PS3_PLUG_MAX); + + spin_lock_irqsave(&pd->bmp.lock, flags); + pd->bmp.mask |= (0x8000000000000000UL >> virq); + spin_unlock_irqrestore(&pd->bmp.lock, flags); + + lv1_did_update_interrupt_mask(pd->node, pd->cpu); +} + +static void chip_eoi(unsigned int virq) +{ + lv1_end_of_interrupt(virq); +} + +static struct irq_chip irq_chip = { + .typename = "ps3", + .mask = chip_mask, + .unmask = chip_unmask, + .eoi = chip_eoi, +}; + +static void host_unmap(struct irq_host *h, unsigned int virq) +{ + int result; + + pr_debug("%s:%d: virq %d\n", __func__, __LINE__, virq); + + lv1_disconnect_irq_plug(virq); + + result = set_irq_chip_data(virq, NULL); + BUG_ON(result); +} + +static DEFINE_PER_CPU(struct private, private); + +static int host_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hwirq) +{ + int result; + unsigned int cpu; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + pr_debug("%s:%d: hwirq %lu => virq %u\n", __func__, __LINE__, hwirq, + virq); + + /* bind this virq to a cpu */ + + preempt_disable(); + cpu = smp_processor_id(); + result = lv1_connect_irq_plug(virq, hwirq); + preempt_enable(); + + if (result) { + pr_info("%s:%d: lv1_connect_irq_plug failed:" + " %s\n", __func__, __LINE__, ps3_result(result)); + return -EPERM; + } + + result = set_irq_chip_data(virq, &per_cpu(private, cpu)); + BUG_ON(result); + + set_irq_chip_and_handler(virq, &irq_chip, handle_fasteoi_irq); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static struct irq_host_ops host_ops = { + .map = host_map, + .unmap = host_unmap, +}; + +void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq) +{ + struct private *pd = &per_cpu(private, cpu); + + pd->bmp.ipi_debug_brk_mask = 0x8000000000000000UL >> virq; + + pr_debug("%s:%d: cpu %u, virq %u, mask %lxh\n", __func__, __LINE__, + cpu, virq, pd->bmp.ipi_debug_brk_mask); +} + +static int bmp_get_and_clear_status_bit(struct bmp *m) +{ + unsigned long flags; + unsigned int bit; + unsigned long x; + + spin_lock_irqsave(&m->lock, flags); + + /* check for ipi break first to stop this cpu ASAP */ + + if (m->status & m->ipi_debug_brk_mask) { + m->status &= ~m->ipi_debug_brk_mask; + spin_unlock_irqrestore(&m->lock, flags); + return __ilog2(m->ipi_debug_brk_mask); + } + + x = (m->status & m->mask); + + for (bit = NUM_ISA_INTERRUPTS, x <<= bit; x; bit++, x <<= 1) + if (x & 0x8000000000000000UL) { + m->status &= ~(0x8000000000000000UL >> bit); + spin_unlock_irqrestore(&m->lock, flags); + return bit; + } + + spin_unlock_irqrestore(&m->lock, flags); + + pr_debug("%s:%d: not found\n", __func__, __LINE__); + return -1; +} + +unsigned int ps3_get_irq(void) +{ + int plug; + + struct private *pd = &__get_cpu_var(private); + + plug = bmp_get_and_clear_status_bit(&pd->bmp); + + if (plug < 1) { + pr_debug("%s:%d: no plug found: cpu %u\n", __func__, __LINE__, + pd->cpu); + dump_bmp(&per_cpu(private, 0)); + dump_bmp(&per_cpu(private, 1)); + return NO_IRQ; + } + +#if defined(DEBUG) + if (plug < NUM_ISA_INTERRUPTS || plug > PS3_PLUG_MAX) { + dump_bmp(&per_cpu(private, 0)); + dump_bmp(&per_cpu(private, 1)); + BUG(); + } +#endif + return plug; +} + +void __init ps3_init_IRQ(void) +{ + int result; + unsigned long node; + unsigned cpu; + struct irq_host *host; + + lv1_get_logical_ppe_id(&node); + + host = irq_alloc_host(IRQ_HOST_MAP_NOMAP, 0, &host_ops, + PS3_INVALID_OUTLET); + irq_set_default_host(host); + irq_set_virq_count(PS3_PLUG_MAX + 1); + + for_each_possible_cpu(cpu) { + struct private *pd = &per_cpu(private, cpu); + + pd->node = node; + pd->cpu = cpu; + spin_lock_init(&pd->bmp.lock); + + result = lv1_configure_irq_state_bitmap(node, cpu, + ps3_mm_phys_to_lpar(__pa(&pd->bmp.status))); + + if (result) + pr_debug("%s:%d: lv1_configure_irq_state_bitmap failed:" + " %s\n", __func__, __LINE__, + ps3_result(result)); + } + + ppc_md.get_irq = ps3_get_irq; +} diff --git a/arch/powerpc/platforms/ps3/mm.c b/arch/powerpc/platforms/ps3/mm.c new file mode 100644 index 00000000000..49c0d010d49 --- /dev/null +++ b/arch/powerpc/platforms/ps3/mm.c @@ -0,0 +1,831 @@ +/* + * PS3 address space management. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/memory_hotplug.h> + +#include <asm/firmware.h> +#include <asm/lmb.h> +#include <asm/udbg.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) do{if(0)printk(fmt);}while(0) +#endif + +enum { +#if defined(CONFIG_PS3_USE_LPAR_ADDR) + USE_LPAR_ADDR = 1, +#else + USE_LPAR_ADDR = 0, +#endif +#if defined(CONFIG_PS3_DYNAMIC_DMA) + USE_DYNAMIC_DMA = 1, +#else + USE_DYNAMIC_DMA = 0, +#endif +}; + +enum { + PAGE_SHIFT_4K = 12U, + PAGE_SHIFT_64K = 16U, + PAGE_SHIFT_16M = 24U, +}; + +static unsigned long make_page_sizes(unsigned long a, unsigned long b) +{ + return (a << 56) | (b << 48); +} + +enum { + ALLOCATE_MEMORY_TRY_ALT_UNIT = 0X04, + ALLOCATE_MEMORY_ADDR_ZERO = 0X08, +}; + +/* valid htab sizes are {18,19,20} = 256K, 512K, 1M */ + +enum { + HTAB_SIZE_MAX = 20U, /* HV limit of 1MB */ + HTAB_SIZE_MIN = 18U, /* CPU limit of 256KB */ +}; + +/*============================================================================*/ +/* virtual address space routines */ +/*============================================================================*/ + +/** + * struct mem_region - memory region structure + * @base: base address + * @size: size in bytes + * @offset: difference between base and rm.size + */ + +struct mem_region { + unsigned long base; + unsigned long size; + unsigned long offset; +}; + +/** + * struct map - address space state variables holder + * @total: total memory available as reported by HV + * @vas_id - HV virtual address space id + * @htab_size: htab size in bytes + * + * The HV virtual address space (vas) allows for hotplug memory regions. + * Memory regions can be created and destroyed in the vas at runtime. + * @rm: real mode (bootmem) region + * @r1: hotplug memory region(s) + * + * ps3 addresses + * virt_addr: a cpu 'translated' effective address + * phys_addr: an address in what Linux thinks is the physical address space + * lpar_addr: an address in the HV virtual address space + * bus_addr: an io controller 'translated' address on a device bus + */ + +struct map { + unsigned long total; + unsigned long vas_id; + unsigned long htab_size; + struct mem_region rm; + struct mem_region r1; +}; + +#define debug_dump_map(x) _debug_dump_map(x, __func__, __LINE__) +static void _debug_dump_map(const struct map* m, const char* func, int line) +{ + DBG("%s:%d: map.total = %lxh\n", func, line, m->total); + DBG("%s:%d: map.rm.size = %lxh\n", func, line, m->rm.size); + DBG("%s:%d: map.vas_id = %lu\n", func, line, m->vas_id); + DBG("%s:%d: map.htab_size = %lxh\n", func, line, m->htab_size); + DBG("%s:%d: map.r1.base = %lxh\n", func, line, m->r1.base); + DBG("%s:%d: map.r1.offset = %lxh\n", func, line, m->r1.offset); + DBG("%s:%d: map.r1.size = %lxh\n", func, line, m->r1.size); +} + +static struct map map; + +/** + * ps3_mm_phys_to_lpar - translate a linux physical address to lpar address + * @phys_addr: linux physical address + */ + +unsigned long ps3_mm_phys_to_lpar(unsigned long phys_addr) +{ + BUG_ON(is_kernel_addr(phys_addr)); + if (USE_LPAR_ADDR) + return phys_addr; + else + return (phys_addr < map.rm.size || phys_addr >= map.total) + ? phys_addr : phys_addr + map.r1.offset; +} + +EXPORT_SYMBOL(ps3_mm_phys_to_lpar); + +/** + * ps3_mm_vas_create - create the virtual address space + */ + +void __init ps3_mm_vas_create(unsigned long* htab_size) +{ + int result; + unsigned long start_address; + unsigned long size; + unsigned long access_right; + unsigned long max_page_size; + unsigned long flags; + + result = lv1_query_logical_partition_address_region_info(0, + &start_address, &size, &access_right, &max_page_size, + &flags); + + if (result) { + DBG("%s:%d: lv1_query_logical_partition_address_region_info " + "failed: %s\n", __func__, __LINE__, + ps3_result(result)); + goto fail; + } + + if (max_page_size < PAGE_SHIFT_16M) { + DBG("%s:%d: bad max_page_size %lxh\n", __func__, __LINE__, + max_page_size); + goto fail; + } + + BUILD_BUG_ON(CONFIG_PS3_HTAB_SIZE > HTAB_SIZE_MAX); + BUILD_BUG_ON(CONFIG_PS3_HTAB_SIZE < HTAB_SIZE_MIN); + + result = lv1_construct_virtual_address_space(CONFIG_PS3_HTAB_SIZE, + 2, make_page_sizes(PAGE_SHIFT_16M, PAGE_SHIFT_64K), + &map.vas_id, &map.htab_size); + + if (result) { + DBG("%s:%d: lv1_construct_virtual_address_space failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail; + } + + result = lv1_select_virtual_address_space(map.vas_id); + + if (result) { + DBG("%s:%d: lv1_select_virtual_address_space failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail; + } + + *htab_size = map.htab_size; + + debug_dump_map(&map); + + return; + +fail: + panic("ps3_mm_vas_create failed"); +} + +/** + * ps3_mm_vas_destroy - + */ + +void ps3_mm_vas_destroy(void) +{ + if (map.vas_id) { + lv1_select_virtual_address_space(0); + lv1_destruct_virtual_address_space(map.vas_id); + map.vas_id = 0; + } +} + +/*============================================================================*/ +/* memory hotplug routines */ +/*============================================================================*/ + +/** + * ps3_mm_region_create - create a memory region in the vas + * @r: pointer to a struct mem_region to accept initialized values + * @size: requested region size + * + * This implementation creates the region with the vas large page size. + * @size is rounded down to a multiple of the vas large page size. + */ + +int ps3_mm_region_create(struct mem_region *r, unsigned long size) +{ + int result; + unsigned long muid; + + r->size = _ALIGN_DOWN(size, 1 << PAGE_SHIFT_16M); + + DBG("%s:%d requested %lxh\n", __func__, __LINE__, size); + DBG("%s:%d actual %lxh\n", __func__, __LINE__, r->size); + DBG("%s:%d difference %lxh (%luMB)\n", __func__, __LINE__, + (unsigned long)(size - r->size), + (size - r->size) / 1024 / 1024); + + if (r->size == 0) { + DBG("%s:%d: size == 0\n", __func__, __LINE__); + result = -1; + goto zero_region; + } + + result = lv1_allocate_memory(r->size, PAGE_SHIFT_16M, 0, + ALLOCATE_MEMORY_TRY_ALT_UNIT, &r->base, &muid); + + if (result || r->base < map.rm.size) { + DBG("%s:%d: lv1_allocate_memory failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto zero_region; + } + + r->offset = r->base - map.rm.size; + return result; + +zero_region: + r->size = r->base = r->offset = 0; + return result; +} + +/** + * ps3_mm_region_destroy - destroy a memory region + * @r: pointer to struct mem_region + */ + +void ps3_mm_region_destroy(struct mem_region *r) +{ + if (r->base) { + lv1_release_memory(r->base); + r->size = r->base = r->offset = 0; + map.total = map.rm.size; + } +} + +/** + * ps3_mm_add_memory - hot add memory + */ + +static int __init ps3_mm_add_memory(void) +{ + int result; + unsigned long start_addr; + unsigned long start_pfn; + unsigned long nr_pages; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return 0; + + BUG_ON(!mem_init_done); + + start_addr = USE_LPAR_ADDR ? map.r1.base : map.rm.size; + start_pfn = start_addr >> PAGE_SHIFT; + nr_pages = (map.r1.size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + DBG("%s:%d: start_addr %lxh, start_pfn %lxh, nr_pages %lxh\n", + __func__, __LINE__, start_addr, start_pfn, nr_pages); + + result = add_memory(0, start_addr, map.r1.size); + + if (result) { + DBG("%s:%d: add_memory failed: (%d)\n", + __func__, __LINE__, result); + return result; + } + + result = online_pages(start_pfn, nr_pages); + + if (result) + DBG("%s:%d: online_pages failed: (%d)\n", + __func__, __LINE__, result); + + return result; +} + +core_initcall(ps3_mm_add_memory); + +/*============================================================================*/ +/* dma routines */ +/*============================================================================*/ + +/** + * dma_lpar_to_bus - Translate an lpar address to ioc mapped bus address. + * @r: pointer to dma region structure + * @lpar_addr: HV lpar address + */ + +static unsigned long dma_lpar_to_bus(struct ps3_dma_region *r, + unsigned long lpar_addr) +{ + BUG_ON(lpar_addr >= map.r1.base + map.r1.size); + return r->bus_addr + (lpar_addr <= map.rm.size ? lpar_addr + : lpar_addr - map.r1.offset); +} + +#define dma_dump_region(_a) _dma_dump_region(_a, __func__, __LINE__) +static void _dma_dump_region(const struct ps3_dma_region *r, const char* func, + int line) +{ + DBG("%s:%d: dev %u:%u\n", func, line, r->did.bus_id, + r->did.dev_id); + DBG("%s:%d: page_size %u\n", func, line, r->page_size); + DBG("%s:%d: bus_addr %lxh\n", func, line, r->bus_addr); + DBG("%s:%d: len %lxh\n", func, line, r->len); +} + +/** + * dma_chunk - A chunk of dma pages mapped by the io controller. + * @region - The dma region that owns this chunk. + * @lpar_addr: Starting lpar address of the area to map. + * @bus_addr: Starting ioc bus address of the area to map. + * @len: Length in bytes of the area to map. + * @link: A struct list_head used with struct ps3_dma_region.chunk_list, the + * list of all chuncks owned by the region. + * + * This implementation uses a very simple dma page manager + * based on the dma_chunk structure. This scheme assumes + * that all drivers use very well behaved dma ops. + */ + +struct dma_chunk { + struct ps3_dma_region *region; + unsigned long lpar_addr; + unsigned long bus_addr; + unsigned long len; + struct list_head link; + unsigned int usage_count; +}; + +#define dma_dump_chunk(_a) _dma_dump_chunk(_a, __func__, __LINE__) +static void _dma_dump_chunk (const struct dma_chunk* c, const char* func, + int line) +{ + DBG("%s:%d: r.dev %u:%u\n", func, line, + c->region->did.bus_id, c->region->did.dev_id); + DBG("%s:%d: r.bus_addr %lxh\n", func, line, c->region->bus_addr); + DBG("%s:%d: r.page_size %u\n", func, line, c->region->page_size); + DBG("%s:%d: r.len %lxh\n", func, line, c->region->len); + DBG("%s:%d: c.lpar_addr %lxh\n", func, line, c->lpar_addr); + DBG("%s:%d: c.bus_addr %lxh\n", func, line, c->bus_addr); + DBG("%s:%d: c.len %lxh\n", func, line, c->len); +} + +static struct dma_chunk * dma_find_chunk(struct ps3_dma_region *r, + unsigned long bus_addr, unsigned long len) +{ + struct dma_chunk *c; + unsigned long aligned_bus = _ALIGN_DOWN(bus_addr, 1 << r->page_size); + unsigned long aligned_len = _ALIGN_UP(len, 1 << r->page_size); + + list_for_each_entry(c, &r->chunk_list.head, link) { + /* intersection */ + if (aligned_bus >= c->bus_addr + && aligned_bus < c->bus_addr + c->len + && aligned_bus + aligned_len <= c->bus_addr + c->len) { + return c; + } + /* below */ + if (aligned_bus + aligned_len <= c->bus_addr) { + continue; + } + /* above */ + if (aligned_bus >= c->bus_addr + c->len) { + continue; + } + + /* we don't handle the multi-chunk case for now */ + + dma_dump_chunk(c); + BUG(); + } + return NULL; +} + +static int dma_free_chunk(struct dma_chunk *c) +{ + int result = 0; + + if (c->bus_addr) { + result = lv1_unmap_device_dma_region(c->region->did.bus_id, + c->region->did.dev_id, c->bus_addr, c->len); + BUG_ON(result); + } + + kfree(c); + return result; +} + +/** + * dma_map_pages - Maps dma pages into the io controller bus address space. + * @r: Pointer to a struct ps3_dma_region. + * @phys_addr: Starting physical address of the area to map. + * @len: Length in bytes of the area to map. + * c_out: A pointer to receive an allocated struct dma_chunk for this area. + * + * This is the lowest level dma mapping routine, and is the one that will + * make the HV call to add the pages into the io controller address space. + */ + +static int dma_map_pages(struct ps3_dma_region *r, unsigned long phys_addr, + unsigned long len, struct dma_chunk **c_out) +{ + int result; + struct dma_chunk *c; + + c = kzalloc(sizeof(struct dma_chunk), GFP_ATOMIC); + + if (!c) { + result = -ENOMEM; + goto fail_alloc; + } + + c->region = r; + c->lpar_addr = ps3_mm_phys_to_lpar(phys_addr); + c->bus_addr = dma_lpar_to_bus(r, c->lpar_addr); + c->len = len; + + result = lv1_map_device_dma_region(c->region->did.bus_id, + c->region->did.dev_id, c->lpar_addr, c->bus_addr, c->len, + 0xf800000000000000UL); + + if (result) { + DBG("%s:%d: lv1_map_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail_map; + } + + list_add(&c->link, &r->chunk_list.head); + + *c_out = c; + return 0; + +fail_map: + kfree(c); +fail_alloc: + *c_out = NULL; + DBG(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +/** + * dma_region_create - Create a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * + * This is the lowest level dma region create routine, and is the one that + * will make the HV call to create the region. + */ + +static int dma_region_create(struct ps3_dma_region* r) +{ + int result; + + r->len = _ALIGN_UP(map.total, 1 << r->page_size); + INIT_LIST_HEAD(&r->chunk_list.head); + spin_lock_init(&r->chunk_list.lock); + + result = lv1_allocate_device_dma_region(r->did.bus_id, r->did.dev_id, + r->len, r->page_size, r->region_type, &r->bus_addr); + + dma_dump_region(r); + + if (result) { + DBG("%s:%d: lv1_allocate_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + r->len = r->bus_addr = 0; + } + + return result; +} + +/** + * dma_region_free - Free a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * + * This is the lowest level dma region free routine, and is the one that + * will make the HV call to free the region. + */ + +static int dma_region_free(struct ps3_dma_region* r) +{ + int result; + struct dma_chunk *c; + struct dma_chunk *tmp; + + list_for_each_entry_safe(c, tmp, &r->chunk_list.head, link) { + list_del(&c->link); + dma_free_chunk(c); + } + + result = lv1_free_device_dma_region(r->did.bus_id, r->did.dev_id, + r->bus_addr); + + if (result) + DBG("%s:%d: lv1_free_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + r->len = r->bus_addr = 0; + + return result; +} + +/** + * dma_map_area - Map an area of memory into a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @virt_addr: Starting virtual address of the area to map. + * @len: Length in bytes of the area to map. + * @bus_addr: A pointer to return the starting ioc bus address of the area to + * map. + * + * This is the common dma mapping routine. + */ + +static int dma_map_area(struct ps3_dma_region *r, unsigned long virt_addr, + unsigned long len, unsigned long *bus_addr) +{ + int result; + unsigned long flags; + struct dma_chunk *c; + unsigned long phys_addr = is_kernel_addr(virt_addr) ? __pa(virt_addr) + : virt_addr; + + *bus_addr = dma_lpar_to_bus(r, ps3_mm_phys_to_lpar(phys_addr)); + + if (!USE_DYNAMIC_DMA) { + unsigned long lpar_addr = ps3_mm_phys_to_lpar(phys_addr); + DBG(" -> %s:%d\n", __func__, __LINE__); + DBG("%s:%d virt_addr %lxh\n", __func__, __LINE__, + virt_addr); + DBG("%s:%d phys_addr %lxh\n", __func__, __LINE__, + phys_addr); + DBG("%s:%d lpar_addr %lxh\n", __func__, __LINE__, + lpar_addr); + DBG("%s:%d len %lxh\n", __func__, __LINE__, len); + DBG("%s:%d bus_addr %lxh (%lxh)\n", __func__, __LINE__, + *bus_addr, len); + } + + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk(r, *bus_addr, len); + + if (c) { + c->usage_count++; + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return 0; + } + + result = dma_map_pages(r, _ALIGN_DOWN(phys_addr, 1 << r->page_size), + _ALIGN_UP(len, 1 << r->page_size), &c); + + if (result) { + *bus_addr = 0; + DBG("%s:%d: dma_map_pages failed (%d)\n", + __func__, __LINE__, result); + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; + } + + c->usage_count = 1; + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; +} + +/** + * dma_unmap_area - Unmap an area of memory from a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @bus_addr: The starting ioc bus address of the area to unmap. + * @len: Length in bytes of the area to unmap. + * + * This is the common dma unmap routine. + */ + +int dma_unmap_area(struct ps3_dma_region *r, unsigned long bus_addr, + unsigned long len) +{ + unsigned long flags; + struct dma_chunk *c; + + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk(r, bus_addr, len); + + if (!c) { + unsigned long aligned_bus = _ALIGN_DOWN(bus_addr, + 1 << r->page_size); + unsigned long aligned_len = _ALIGN_UP(len, 1 << r->page_size); + DBG("%s:%d: not found: bus_addr %lxh\n", + __func__, __LINE__, bus_addr); + DBG("%s:%d: not found: len %lxh\n", + __func__, __LINE__, len); + DBG("%s:%d: not found: aligned_bus %lxh\n", + __func__, __LINE__, aligned_bus); + DBG("%s:%d: not found: aligned_len %lxh\n", + __func__, __LINE__, aligned_len); + BUG(); + } + + c->usage_count--; + + if (!c->usage_count) { + list_del(&c->link); + dma_free_chunk(c); + } + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return 0; +} + +/** + * dma_region_create_linear - Setup a linear dma maping for a device. + * @r: Pointer to a struct ps3_dma_region. + * + * This routine creates an HV dma region for the device and maps all available + * ram into the io controller bus address space. + */ + +static int dma_region_create_linear(struct ps3_dma_region *r) +{ + int result; + unsigned long tmp; + + /* force 16M dma pages for linear mapping */ + + if (r->page_size != PS3_DMA_16M) { + pr_info("%s:%d: forcing 16M pages for linear map\n", + __func__, __LINE__); + r->page_size = PS3_DMA_16M; + } + + result = dma_region_create(r); + BUG_ON(result); + + result = dma_map_area(r, map.rm.base, map.rm.size, &tmp); + BUG_ON(result); + + if (USE_LPAR_ADDR) + result = dma_map_area(r, map.r1.base, map.r1.size, + &tmp); + else + result = dma_map_area(r, map.rm.size, map.r1.size, + &tmp); + + BUG_ON(result); + + return result; +} + +/** + * dma_region_free_linear - Free a linear dma mapping for a device. + * @r: Pointer to a struct ps3_dma_region. + * + * This routine will unmap all mapped areas and free the HV dma region. + */ + +static int dma_region_free_linear(struct ps3_dma_region *r) +{ + int result; + + result = dma_unmap_area(r, dma_lpar_to_bus(r, 0), map.rm.size); + BUG_ON(result); + + result = dma_unmap_area(r, dma_lpar_to_bus(r, map.r1.base), + map.r1.size); + BUG_ON(result); + + result = dma_region_free(r); + BUG_ON(result); + + return result; +} + +/** + * dma_map_area_linear - Map an area of memory into a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @virt_addr: Starting virtual address of the area to map. + * @len: Length in bytes of the area to map. + * @bus_addr: A pointer to return the starting ioc bus address of the area to + * map. + * + * This routine just returns the coresponding bus address. Actual mapping + * occurs in dma_region_create_linear(). + */ + +static int dma_map_area_linear(struct ps3_dma_region *r, + unsigned long virt_addr, unsigned long len, unsigned long *bus_addr) +{ + unsigned long phys_addr = is_kernel_addr(virt_addr) ? __pa(virt_addr) + : virt_addr; + *bus_addr = dma_lpar_to_bus(r, ps3_mm_phys_to_lpar(phys_addr)); + return 0; +} + +/** + * dma_unmap_area_linear - Unmap an area of memory from a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @bus_addr: The starting ioc bus address of the area to unmap. + * @len: Length in bytes of the area to unmap. + * + * This routine does nothing. Unmapping occurs in dma_region_free_linear(). + */ + +static int dma_unmap_area_linear(struct ps3_dma_region *r, + unsigned long bus_addr, unsigned long len) +{ + return 0; +} + +int ps3_dma_region_create(struct ps3_dma_region *r) +{ + return (USE_DYNAMIC_DMA) + ? dma_region_create(r) + : dma_region_create_linear(r); +} + +int ps3_dma_region_free(struct ps3_dma_region *r) +{ + return (USE_DYNAMIC_DMA) + ? dma_region_free(r) + : dma_region_free_linear(r); +} + +int ps3_dma_map(struct ps3_dma_region *r, unsigned long virt_addr, + unsigned long len, unsigned long *bus_addr) +{ + return (USE_DYNAMIC_DMA) + ? dma_map_area(r, virt_addr, len, bus_addr) + : dma_map_area_linear(r, virt_addr, len, bus_addr); +} + +int ps3_dma_unmap(struct ps3_dma_region *r, unsigned long bus_addr, + unsigned long len) +{ + return (USE_DYNAMIC_DMA) ? dma_unmap_area(r, bus_addr, len) + : dma_unmap_area_linear(r, bus_addr, len); +} + +/*============================================================================*/ +/* system startup routines */ +/*============================================================================*/ + +/** + * ps3_mm_init - initialize the address space state variables + */ + +void __init ps3_mm_init(void) +{ + int result; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + result = ps3_repository_read_mm_info(&map.rm.base, &map.rm.size, + &map.total); + + if (result) + panic("ps3_repository_read_mm_info() failed"); + + map.rm.offset = map.rm.base; + map.vas_id = map.htab_size = 0; + + /* this implementation assumes map.rm.base is zero */ + + BUG_ON(map.rm.base); + BUG_ON(!map.rm.size); + + lmb_add(map.rm.base, map.rm.size); + lmb_analyze(); + + /* arrange to do this in ps3_mm_add_memory */ + ps3_mm_region_create(&map.r1, map.total - map.rm.size); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +/** + * ps3_mm_shutdown - final cleanup of address space + */ + +void ps3_mm_shutdown(void) +{ + ps3_mm_region_destroy(&map.r1); + map.total = map.rm.size; +} diff --git a/arch/powerpc/platforms/ps3/os-area.c b/arch/powerpc/platforms/ps3/os-area.c new file mode 100644 index 00000000000..58358305dc1 --- /dev/null +++ b/arch/powerpc/platforms/ps3/os-area.c @@ -0,0 +1,259 @@ +/* + * PS3 'Other OS' area data. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/io.h> + +#include <asm/lmb.h> +#include <asm/ps3.h> + +#include "platform.h" + +enum { + OS_AREA_SEGMENT_SIZE = 0X200, +}; + +enum { + HEADER_LDR_FORMAT_RAW = 0, + HEADER_LDR_FORMAT_GZIP = 1, +}; + +/** + * struct os_area_header - os area header segment. + * @magic_num: Always 'cell_ext_os_area'. + * @hdr_version: Header format version number. + * @os_area_offset: Starting segment number of os image area. + * @ldr_area_offset: Starting segment number of bootloader image area. + * @ldr_format: HEADER_LDR_FORMAT flag. + * @ldr_size: Size of bootloader image in bytes. + * + * Note that the docs refer to area offsets. These are offsets in units of + * segments from the start of the os area (top of the header). These are + * better thought of as segment numbers. The os area of the os area is + * reserved for the os image. + */ + +struct os_area_header { + s8 magic_num[16]; + u32 hdr_version; + u32 os_area_offset; + u32 ldr_area_offset; + u32 _reserved_1; + u32 ldr_format; + u32 ldr_size; + u32 _reserved_2[6]; +} __attribute__ ((packed)); + +enum { + PARAM_BOOT_FLAG_GAME_OS = 0, + PARAM_BOOT_FLAG_OTHER_OS = 1, +}; + +enum { + PARAM_AV_MULTI_OUT_NTSC = 0, + PARAM_AV_MULTI_OUT_PAL_RGB = 1, + PARAM_AV_MULTI_OUT_PAL_YCBCR = 2, + PARAM_AV_MULTI_OUT_SECAM = 3, +}; + +enum { + PARAM_CTRL_BUTTON_O_IS_YES = 0, + PARAM_CTRL_BUTTON_X_IS_YES = 1, +}; + +/** + * struct os_area_params - os area params segment. + * @boot_flag: User preference of operating system, PARAM_BOOT_FLAG flag. + * @num_params: Number of params in this (params) segment. + * @rtc_diff: Difference in seconds between 1970 and the ps3 rtc value. + * @av_multi_out: User preference of AV output, PARAM_AV_MULTI_OUT flag. + * @ctrl_button: User preference of controller button config, PARAM_CTRL_BUTTON + * flag. + * @static_ip_addr: User preference of static IP address. + * @network_mask: User preference of static network mask. + * @default_gateway: User preference of static default gateway. + * @dns_primary: User preference of static primary dns server. + * @dns_secondary: User preference of static secondary dns server. + * + * User preference of zero for static_ip_addr means use dhcp. + */ + +struct os_area_params { + u32 boot_flag; + u32 _reserved_1[3]; + u32 num_params; + u32 _reserved_2[3]; + /* param 0 */ + s64 rtc_diff; + u8 av_multi_out; + u8 ctrl_button; + u8 _reserved_3[6]; + /* param 1 */ + u8 static_ip_addr[4]; + u8 network_mask[4]; + u8 default_gateway[4]; + u8 _reserved_4[4]; + /* param 2 */ + u8 dns_primary[4]; + u8 dns_secondary[4]; + u8 _reserved_5[8]; +} __attribute__ ((packed)); + +/** + * struct saved_params - Static working copies of data from the 'Other OS' area. + * + * For the convinience of the guest, the HV makes a copy of the 'Other OS' area + * in flash to a high address in the boot memory region and then puts that RAM + * address and the byte count into the repository for retreval by the guest. + * We copy the data we want into a static variable and allow the memory setup + * by the HV to be claimed by the lmb manager. + */ + +struct saved_params { + /* param 0 */ + s64 rtc_diff; + unsigned int av_multi_out; + unsigned int ctrl_button; + /* param 1 */ + u8 static_ip_addr[4]; + u8 network_mask[4]; + u8 default_gateway[4]; + /* param 2 */ + u8 dns_primary[4]; + u8 dns_secondary[4]; +} static saved_params; + +#define dump_header(_a) _dump_header(_a, __func__, __LINE__) +static void _dump_header(const struct os_area_header __iomem *h, const char* func, + int line) +{ + pr_debug("%s:%d: h.magic_num: '%s'\n", func, line, + h->magic_num); + pr_debug("%s:%d: h.hdr_version: %u\n", func, line, + h->hdr_version); + pr_debug("%s:%d: h.os_area_offset: %u\n", func, line, + h->os_area_offset); + pr_debug("%s:%d: h.ldr_area_offset: %u\n", func, line, + h->ldr_area_offset); + pr_debug("%s:%d: h.ldr_format: %u\n", func, line, + h->ldr_format); + pr_debug("%s:%d: h.ldr_size: %xh\n", func, line, + h->ldr_size); +} + +#define dump_params(_a) _dump_params(_a, __func__, __LINE__) +static void _dump_params(const struct os_area_params __iomem *p, const char* func, + int line) +{ + pr_debug("%s:%d: p.boot_flag: %u\n", func, line, p->boot_flag); + pr_debug("%s:%d: p.num_params: %u\n", func, line, p->num_params); + pr_debug("%s:%d: p.rtc_diff %ld\n", func, line, p->rtc_diff); + pr_debug("%s:%d: p.av_multi_out %u\n", func, line, p->av_multi_out); + pr_debug("%s:%d: p.ctrl_button: %u\n", func, line, p->ctrl_button); + pr_debug("%s:%d: p.static_ip_addr: %u.%u.%u.%u\n", func, line, + p->static_ip_addr[0], p->static_ip_addr[1], + p->static_ip_addr[2], p->static_ip_addr[3]); + pr_debug("%s:%d: p.network_mask: %u.%u.%u.%u\n", func, line, + p->network_mask[0], p->network_mask[1], + p->network_mask[2], p->network_mask[3]); + pr_debug("%s:%d: p.default_gateway: %u.%u.%u.%u\n", func, line, + p->default_gateway[0], p->default_gateway[1], + p->default_gateway[2], p->default_gateway[3]); + pr_debug("%s:%d: p.dns_primary: %u.%u.%u.%u\n", func, line, + p->dns_primary[0], p->dns_primary[1], + p->dns_primary[2], p->dns_primary[3]); + pr_debug("%s:%d: p.dns_secondary: %u.%u.%u.%u\n", func, line, + p->dns_secondary[0], p->dns_secondary[1], + p->dns_secondary[2], p->dns_secondary[3]); +} + +static int __init verify_header(const struct os_area_header *header) +{ + if (memcmp(header->magic_num, "cell_ext_os_area", 16)) { + pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); + return -1; + } + + if (header->hdr_version < 1) { + pr_debug("%s:%d hdr_version failed\n", __func__, __LINE__); + return -1; + } + + if (header->os_area_offset > header->ldr_area_offset) { + pr_debug("%s:%d offsets failed\n", __func__, __LINE__); + return -1; + } + + return 0; +} + +int __init ps3_os_area_init(void) +{ + int result; + u64 lpar_addr; + unsigned int size; + struct os_area_header *header; + struct os_area_params *params; + + result = ps3_repository_read_boot_dat_info(&lpar_addr, &size); + + if (result) { + pr_debug("%s:%d ps3_repository_read_boot_dat_info failed\n", + __func__, __LINE__); + return result; + } + + header = (struct os_area_header *)__va(lpar_addr); + params = (struct os_area_params *)__va(lpar_addr + OS_AREA_SEGMENT_SIZE); + + result = verify_header(header); + + if (result) { + pr_debug("%s:%d verify_header failed\n", __func__, __LINE__); + dump_header(header); + return -EIO; + } + + dump_header(header); + dump_params(params); + + saved_params.rtc_diff = params->rtc_diff; + saved_params.av_multi_out = params->av_multi_out; + saved_params.ctrl_button = params->ctrl_button; + memcpy(saved_params.static_ip_addr, params->static_ip_addr, 4); + memcpy(saved_params.network_mask, params->network_mask, 4); + memcpy(saved_params.default_gateway, params->default_gateway, 4); + memcpy(saved_params.dns_secondary, params->dns_secondary, 4); + + return result; +} + +/** + * ps3_os_area_rtc_diff - Returns the ps3 rtc diff value. + * + * The ps3 rtc maintains a value that approximates seconds since + * 2000-01-01 00:00:00 UTC. Returns the exact number of seconds from 1970 to + * 2000 when saved_params.rtc_diff has not been properly set up. + */ + +u64 ps3_os_area_rtc_diff(void) +{ + return saved_params.rtc_diff ? saved_params.rtc_diff : 946684800UL; +} diff --git a/arch/powerpc/platforms/ps3/platform.h b/arch/powerpc/platforms/ps3/platform.h new file mode 100644 index 00000000000..23b111bea9d --- /dev/null +++ b/arch/powerpc/platforms/ps3/platform.h @@ -0,0 +1,68 @@ +/* + * PS3 platform declarations. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(_PS3_PLATFORM_H) +#define _PS3_PLATFORM_H + +#include <linux/rtc.h> + +/* htab */ + +void __init ps3_hpte_init(unsigned long htab_size); +void __init ps3_map_htab(void); + +/* mm */ + +void __init ps3_mm_init(void); +void __init ps3_mm_vas_create(unsigned long* htab_size); +void ps3_mm_vas_destroy(void); +void ps3_mm_shutdown(void); + +/* irq */ + +void ps3_init_IRQ(void); +void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq); + +/* smp */ + +void smp_init_ps3(void); +void ps3_smp_cleanup_cpu(int cpu); + +/* time */ + +void __init ps3_calibrate_decr(void); +unsigned long __init ps3_get_boot_time(void); +void ps3_get_rtc_time(struct rtc_time *time); +int ps3_set_rtc_time(struct rtc_time *time); + +/* os area */ + +int __init ps3_os_area_init(void); +u64 ps3_os_area_rtc_diff(void); + +/* spu */ + +#if defined(CONFIG_SPU_BASE) +void ps3_spu_set_platform (void); +#else +static inline void ps3_spu_set_platform (void) {} +#endif + +#endif diff --git a/arch/powerpc/platforms/ps3/repository.c b/arch/powerpc/platforms/ps3/repository.c new file mode 100644 index 00000000000..273a0d621bd --- /dev/null +++ b/arch/powerpc/platforms/ps3/repository.c @@ -0,0 +1,840 @@ +/* + * PS3 repository routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <asm/ps3.h> +#include <asm/lv1call.h> + +enum ps3_vendor_id { + PS3_VENDOR_ID_NONE = 0, + PS3_VENDOR_ID_SONY = 0x8000000000000000UL, +}; + +enum ps3_lpar_id { + PS3_LPAR_ID_CURRENT = 0, + PS3_LPAR_ID_PME = 1, +}; + +#define dump_field(_a, _b) _dump_field(_a, _b, __func__, __LINE__) +static void _dump_field(const char *hdr, u64 n, const char* func, int line) +{ +#if defined(DEBUG) + char s[16]; + const char *const in = (const char *)&n; + unsigned int i; + + for (i = 0; i < 8; i++) + s[i] = (in[i] <= 126 && in[i] >= 32) ? in[i] : '.'; + s[i] = 0; + + pr_debug("%s:%d: %s%016lx : %s\n", func, line, hdr, n, s); +#endif +} + +#define dump_node_name(_a, _b, _c, _d, _e) \ + _dump_node_name(_a, _b, _c, _d, _e, __func__, __LINE__) +static void _dump_node_name (unsigned int lpar_id, u64 n1, u64 n2, u64 n3, + u64 n4, const char* func, int line) +{ + pr_debug("%s:%d: lpar: %u\n", func, line, lpar_id); + _dump_field("n1: ", n1, func, line); + _dump_field("n2: ", n2, func, line); + _dump_field("n3: ", n3, func, line); + _dump_field("n4: ", n4, func, line); +} + +#define dump_node(_a, _b, _c, _d, _e, _f, _g) \ + _dump_node(_a, _b, _c, _d, _e, _f, _g, __func__, __LINE__) +static void _dump_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, + u64 v1, u64 v2, const char* func, int line) +{ + pr_debug("%s:%d: lpar: %u\n", func, line, lpar_id); + _dump_field("n1: ", n1, func, line); + _dump_field("n2: ", n2, func, line); + _dump_field("n3: ", n3, func, line); + _dump_field("n4: ", n4, func, line); + pr_debug("%s:%d: v1: %016lx\n", func, line, v1); + pr_debug("%s:%d: v2: %016lx\n", func, line, v2); +} + +/** + * make_first_field - Make the first field of a repository node name. + * @text: Text portion of the field. + * @index: Numeric index portion of the field. Use zero for 'don't care'. + * + * This routine sets the vendor id to zero (non-vendor specific). + * Returns field value. + */ + +static u64 make_first_field(const char *text, u64 index) +{ + u64 n; + + strncpy((char *)&n, text, 8); + return PS3_VENDOR_ID_NONE + (n >> 32) + index; +} + +/** + * make_field - Make subsequent fields of a repository node name. + * @text: Text portion of the field. Use "" for 'don't care'. + * @index: Numeric index portion of the field. Use zero for 'don't care'. + * + * Returns field value. + */ + +static u64 make_field(const char *text, u64 index) +{ + u64 n; + + strncpy((char *)&n, text, 8); + return n + index; +} + +/** + * read_node - Read a repository node from raw fields. + * @n1: First field of node name. + * @n2: Second field of node name. Use zero for 'don't care'. + * @n3: Third field of node name. Use zero for 'don't care'. + * @n4: Fourth field of node name. Use zero for 'don't care'. + * @v1: First repository value (high word). + * @v2: Second repository value (low word). Optional parameter, use zero + * for 'don't care'. + */ + +static int read_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, + u64 *_v1, u64 *_v2) +{ + int result; + u64 v1; + u64 v2; + + if (lpar_id == PS3_LPAR_ID_CURRENT) { + u64 id; + lv1_get_logical_partition_id(&id); + lpar_id = id; + } + + result = lv1_get_repository_node_value(lpar_id, n1, n2, n3, n4, &v1, + &v2); + + if (result) { + pr_debug("%s:%d: lv1_get_repository_node_value failed: %s\n", + __func__, __LINE__, ps3_result(result)); + dump_node_name(lpar_id, n1, n2, n3, n4); + return result; + } + + dump_node(lpar_id, n1, n2, n3, n4, v1, v2); + + if (_v1) + *_v1 = v1; + if (_v2) + *_v2 = v2; + + if (v1 && !_v1) + pr_debug("%s:%d: warning: discarding non-zero v1: %016lx\n", + __func__, __LINE__, v1); + if (v2 && !_v2) + pr_debug("%s:%d: warning: discarding non-zero v2: %016lx\n", + __func__, __LINE__, v2); + + return result; +} + +int ps3_repository_read_bus_str(unsigned int bus_index, const char *bus_str, + u64 *value) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field(bus_str, 0), + 0, 0, + value, 0); +} + +int ps3_repository_read_bus_id(unsigned int bus_index, unsigned int *bus_id) +{ + int result; + u64 v1; + u64 v2; /* unused */ + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("id", 0), + 0, 0, + &v1, &v2); + *bus_id = v1; + return result; +} + +int ps3_repository_read_bus_type(unsigned int bus_index, + enum ps3_bus_type *bus_type) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("type", 0), + 0, 0, + &v1, 0); + *bus_type = v1; + return result; +} + +int ps3_repository_read_bus_num_dev(unsigned int bus_index, + unsigned int *num_dev) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("num_dev", 0), + 0, 0, + &v1, 0); + *num_dev = v1; + return result; +} + +int ps3_repository_read_dev_str(unsigned int bus_index, + unsigned int dev_index, const char *dev_str, u64 *value) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field(dev_str, 0), + 0, + value, 0); +} + +int ps3_repository_read_dev_id(unsigned int bus_index, unsigned int dev_index, + unsigned int *dev_id) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("id", 0), + 0, + &v1, 0); + *dev_id = v1; + return result; +} + +int ps3_repository_read_dev_type(unsigned int bus_index, + unsigned int dev_index, enum ps3_dev_type *dev_type) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("type", 0), + 0, + &v1, 0); + *dev_type = v1; + return result; +} + +int ps3_repository_read_dev_intr(unsigned int bus_index, + unsigned int dev_index, unsigned int intr_index, + unsigned int *intr_type, unsigned int* interrupt_id) +{ + int result; + u64 v1; + u64 v2; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("intr", intr_index), + 0, + &v1, &v2); + *intr_type = v1; + *interrupt_id = v2; + return result; +} + +int ps3_repository_read_dev_reg_type(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, unsigned int *reg_type) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("reg", reg_index), + make_field("type", 0), + &v1, 0); + *reg_type = v1; + return result; +} + +int ps3_repository_read_dev_reg_addr(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, u64 *bus_addr, u64 *len) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("reg", reg_index), + make_field("data", 0), + bus_addr, len); +} + +int ps3_repository_read_dev_reg(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, unsigned int *reg_type, + u64 *bus_addr, u64 *len) +{ + int result = ps3_repository_read_dev_reg_type(bus_index, dev_index, + reg_index, reg_type); + return result ? result + : ps3_repository_read_dev_reg_addr(bus_index, dev_index, + reg_index, bus_addr, len); +} + +#if defined(DEBUG) +int ps3_repository_dump_resource_info(unsigned int bus_index, + unsigned int dev_index) +{ + int result = 0; + unsigned int res_index; + + pr_debug(" -> %s:%d: (%u:%u)\n", __func__, __LINE__, + bus_index, dev_index); + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_interrupt_type intr_type; + unsigned int interrupt_id; + + result = ps3_repository_read_dev_intr(bus_index, dev_index, + res_index, &intr_type, &interrupt_id); + + if (result) { + if (result != LV1_NO_ENTRY) + pr_debug("%s:%d ps3_repository_read_dev_intr" + " (%u:%u) failed\n", __func__, __LINE__, + bus_index, dev_index); + break; + } + + pr_debug("%s:%d (%u:%u) intr_type %u, interrupt_id %u\n", + __func__, __LINE__, bus_index, dev_index, intr_type, + interrupt_id); + } + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_region_type reg_type; + u64 bus_addr; + u64 len; + + result = ps3_repository_read_dev_reg(bus_index, dev_index, + res_index, ®_type, &bus_addr, &len); + + if (result) { + if (result != LV1_NO_ENTRY) + pr_debug("%s:%d ps3_repository_read_dev_reg" + " (%u:%u) failed\n", __func__, __LINE__, + bus_index, dev_index); + break; + } + + pr_debug("%s:%d (%u:%u) reg_type %u, bus_addr %lxh, len %lxh\n", + __func__, __LINE__, bus_index, dev_index, reg_type, + bus_addr, len); + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static int dump_device_info(unsigned int bus_index, unsigned int num_dev) +{ + int result = 0; + unsigned int dev_index; + + pr_debug(" -> %s:%d: bus_%u\n", __func__, __LINE__, bus_index); + + for (dev_index = 0; dev_index < num_dev; dev_index++) { + enum ps3_dev_type dev_type; + unsigned int dev_id; + + result = ps3_repository_read_dev_type(bus_index, dev_index, + &dev_type); + + if (result) { + pr_debug("%s:%d ps3_repository_read_dev_type" + " (%u:%u) failed\n", __func__, __LINE__, + bus_index, dev_index); + break; + } + + result = ps3_repository_read_dev_id(bus_index, dev_index, + &dev_id); + + if (result) { + pr_debug("%s:%d ps3_repository_read_dev_id" + " (%u:%u) failed\n", __func__, __LINE__, + bus_index, dev_index); + continue; + } + + pr_debug("%s:%d (%u:%u): dev_type %u, dev_id %u\n", __func__, + __LINE__, bus_index, dev_index, dev_type, dev_id); + + ps3_repository_dump_resource_info(bus_index, dev_index); + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +int ps3_repository_dump_bus_info(void) +{ + int result = 0; + unsigned int bus_index; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + for (bus_index = 0; bus_index < 10; bus_index++) { + enum ps3_bus_type bus_type; + unsigned int bus_id; + unsigned int num_dev; + + result = ps3_repository_read_bus_type(bus_index, &bus_type); + + if (result) { + pr_debug("%s:%d read_bus_type(%u) failed\n", + __func__, __LINE__, bus_index); + break; + } + + result = ps3_repository_read_bus_id(bus_index, &bus_id); + + if (result) { + pr_debug("%s:%d read_bus_id(%u) failed\n", + __func__, __LINE__, bus_index); + continue; + } + + if (bus_index != bus_id) + pr_debug("%s:%d bus_index != bus_id\n", + __func__, __LINE__); + + result = ps3_repository_read_bus_num_dev(bus_index, &num_dev); + + if (result) { + pr_debug("%s:%d read_bus_num_dev(%u) failed\n", + __func__, __LINE__, bus_index); + continue; + } + + pr_debug("%s:%d bus_%u: bus_type %u, bus_id %u, num_dev %u\n", + __func__, __LINE__, bus_index, bus_type, bus_id, + num_dev); + + dump_device_info(bus_index, num_dev); + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} +#endif /* defined(DEBUG) */ + +static int find_device(unsigned int bus_index, unsigned int num_dev, + unsigned int start_dev_index, enum ps3_dev_type dev_type, + struct ps3_repository_device *dev) +{ + int result = 0; + unsigned int dev_index; + + pr_debug("%s:%d: find dev_type %u\n", __func__, __LINE__, dev_type); + + dev->dev_index = UINT_MAX; + + for (dev_index = start_dev_index; dev_index < num_dev; dev_index++) { + enum ps3_dev_type x; + + result = ps3_repository_read_dev_type(bus_index, dev_index, + &x); + + if (result) { + pr_debug("%s:%d read_dev_type failed\n", + __func__, __LINE__); + return result; + } + + if (x == dev_type) + break; + } + + BUG_ON(dev_index == num_dev); + + pr_debug("%s:%d: found dev_type %u at dev_index %u\n", + __func__, __LINE__, dev_type, dev_index); + + result = ps3_repository_read_dev_id(bus_index, dev_index, + &dev->did.dev_id); + + if (result) { + pr_debug("%s:%d read_dev_id failed\n", + __func__, __LINE__); + return result; + } + + dev->dev_index = dev_index; + + pr_debug("%s:%d found: dev_id %u\n", __func__, __LINE__, + dev->did.dev_id); + + return result; +} + +int ps3_repository_find_device (enum ps3_bus_type bus_type, + enum ps3_dev_type dev_type, + const struct ps3_repository_device *start_dev, + struct ps3_repository_device *dev) +{ + int result = 0; + unsigned int bus_index; + unsigned int num_dev; + + pr_debug("%s:%d: find bus_type %u, dev_type %u\n", __func__, __LINE__, + bus_type, dev_type); + + dev->bus_index = UINT_MAX; + + for (bus_index = start_dev ? start_dev->bus_index : 0; bus_index < 10; + bus_index++) { + enum ps3_bus_type x; + + result = ps3_repository_read_bus_type(bus_index, &x); + + if (result) { + pr_debug("%s:%d read_bus_type failed\n", + __func__, __LINE__); + return result; + } + if (x == bus_type) + break; + } + + BUG_ON(bus_index == 10); + + pr_debug("%s:%d: found bus_type %u at bus_index %u\n", + __func__, __LINE__, bus_type, bus_index); + + result = ps3_repository_read_bus_num_dev(bus_index, &num_dev); + + if (result) { + pr_debug("%s:%d read_bus_num_dev failed\n", + __func__, __LINE__); + return result; + } + + result = find_device(bus_index, num_dev, start_dev + ? start_dev->dev_index + 1 : 0, dev_type, dev); + + if (result) { + pr_debug("%s:%d get_did failed\n", __func__, __LINE__); + return result; + } + + result = ps3_repository_read_bus_id(bus_index, &dev->did.bus_id); + + if (result) { + pr_debug("%s:%d read_bus_id failed\n", + __func__, __LINE__); + return result; + } + + dev->bus_index = bus_index; + + pr_debug("%s:%d found: bus_id %u, dev_id %u\n", + __func__, __LINE__, dev->did.bus_id, dev->did.dev_id); + + return result; +} + +int ps3_repository_find_interrupt(const struct ps3_repository_device *dev, + enum ps3_interrupt_type intr_type, unsigned int *interrupt_id) +{ + int result = 0; + unsigned int res_index; + + pr_debug("%s:%d: find intr_type %u\n", __func__, __LINE__, intr_type); + + *interrupt_id = UINT_MAX; + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_interrupt_type t; + unsigned int id; + + result = ps3_repository_read_dev_intr(dev->bus_index, + dev->dev_index, res_index, &t, &id); + + if (result) { + pr_debug("%s:%d read_dev_intr failed\n", + __func__, __LINE__); + return result; + } + + if (t == intr_type) { + *interrupt_id = id; + break; + } + } + + BUG_ON(res_index == 10); + + pr_debug("%s:%d: found intr_type %u at res_index %u\n", + __func__, __LINE__, intr_type, res_index); + + return result; +} + +int ps3_repository_find_region(const struct ps3_repository_device *dev, + enum ps3_region_type reg_type, u64 *bus_addr, u64 *len) +{ + int result = 0; + unsigned int res_index; + + pr_debug("%s:%d: find reg_type %u\n", __func__, __LINE__, reg_type); + + *bus_addr = *len = 0; + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_region_type t; + u64 a; + u64 l; + + result = ps3_repository_read_dev_reg(dev->bus_index, + dev->dev_index, res_index, &t, &a, &l); + + if (result) { + pr_debug("%s:%d read_dev_reg failed\n", + __func__, __LINE__); + return result; + } + + if (t == reg_type) { + *bus_addr = a; + *len = l; + break; + } + } + + BUG_ON(res_index == 10); + + pr_debug("%s:%d: found reg_type %u at res_index %u\n", + __func__, __LINE__, reg_type, res_index); + + return result; +} + +int ps3_repository_read_rm_size(unsigned int ppe_id, u64 *rm_size) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("pu", 0), + ppe_id, + make_field("rm_size", 0), + rm_size, 0); +} + +int ps3_repository_read_region_total(u64 *region_total) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("rgntotal", 0), + 0, 0, + region_total, 0); +} + +/** + * ps3_repository_read_mm_info - Read mm info for single pu system. + * @rm_base: Real mode memory base address. + * @rm_size: Real mode memory size. + * @region_total: Maximum memory region size. + */ + +int ps3_repository_read_mm_info(u64 *rm_base, u64 *rm_size, u64 *region_total) +{ + int result; + u64 ppe_id; + + lv1_get_logical_ppe_id(&ppe_id); + *rm_base = 0; + result = ps3_repository_read_rm_size(ppe_id, rm_size); + return result ? result + : ps3_repository_read_region_total(region_total); +} + +/** + * ps3_repository_read_num_spu_reserved - Number of physical spus reserved. + * @num_spu: Number of physical spus. + */ + +int ps3_repository_read_num_spu_reserved(unsigned int *num_spu_reserved) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spun", 0), + 0, 0, + &v1, 0); + *num_spu_reserved = v1; + return result; +} + +/** + * ps3_repository_read_num_spu_resource_id - Number of spu resource reservations. + * @num_resource_id: Number of spu resource ids. + */ + +int ps3_repository_read_num_spu_resource_id(unsigned int *num_resource_id) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spursvn", 0), + 0, 0, + &v1, 0); + *num_resource_id = v1; + return result; +} + +/** + * ps3_repository_read_spu_resource_id - spu resource reservation id value. + * @res_index: Resource reservation index. + * @resource_type: Resource reservation type. + * @resource_id: Resource reservation id. + */ + +int ps3_repository_read_spu_resource_id(unsigned int res_index, + enum ps3_spu_resource_type* resource_type, unsigned int *resource_id) +{ + int result; + u64 v1; + u64 v2; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spursv", 0), + res_index, + 0, + &v1, &v2); + *resource_type = v1; + *resource_id = v2; + return result; +} + +int ps3_repository_read_boot_dat_address(u64 *address) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("boot_dat", 0), + make_field("address", 0), + 0, + address, 0); +} + +int ps3_repository_read_boot_dat_size(unsigned int *size) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("boot_dat", 0), + make_field("size", 0), + 0, + &v1, 0); + *size = v1; + return result; +} + +/** + * ps3_repository_read_boot_dat_info - Get address and size of cell_ext_os_area. + * address: lpar address of cell_ext_os_area + * @size: size of cell_ext_os_area + */ + +int ps3_repository_read_boot_dat_info(u64 *lpar_addr, unsigned int *size) +{ + int result; + + *size = 0; + result = ps3_repository_read_boot_dat_address(lpar_addr); + return result ? result + : ps3_repository_read_boot_dat_size(size); +} + +int ps3_repository_read_num_be(unsigned int *num_be) +{ + int result; + u64 v1; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("ben", 0), + 0, + 0, + 0, + &v1, 0); + *num_be = v1; + return result; +} + +int ps3_repository_read_be_node_id(unsigned int be_index, u64 *node_id) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("be", be_index), + 0, + 0, + 0, + node_id, 0); +} + +int ps3_repository_read_tb_freq(u64 node_id, u64 *tb_freq) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("be", 0), + node_id, + make_field("clock", 0), + 0, + tb_freq, 0); +} + +int ps3_repository_read_be_tb_freq(unsigned int be_index, u64 *tb_freq) +{ + int result; + u64 node_id; + + *tb_freq = 0; + result = ps3_repository_read_be_node_id(0, &node_id); + return result ? result + : ps3_repository_read_tb_freq(node_id, tb_freq); +} diff --git a/arch/powerpc/platforms/ps3/setup.c b/arch/powerpc/platforms/ps3/setup.c new file mode 100644 index 00000000000..d8b5cadbe80 --- /dev/null +++ b/arch/powerpc/platforms/ps3/setup.c @@ -0,0 +1,173 @@ +/* + * PS3 platform setup routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/root_dev.h> +#include <linux/console.h> +#include <linux/kexec.h> + +#include <asm/machdep.h> +#include <asm/firmware.h> +#include <asm/time.h> +#include <asm/iommu.h> +#include <asm/udbg.h> +#include <asm/prom.h> +#include <asm/lv1call.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) do{if(0)printk(fmt);}while(0) +#endif + +static void ps3_show_cpuinfo(struct seq_file *m) +{ + seq_printf(m, "machine\t\t: %s\n", ppc_md.name); +} + +static void ps3_power_save(void) +{ + /* + * lv1_pause() puts the PPE thread into inactive state until an + * irq on an unmasked plug exists. MSR[EE] has no effect. + * flags: 0 = wake on DEC interrupt, 1 = ignore DEC interrupt. + */ + + lv1_pause(0); +} + +static void ps3_panic(char *str) +{ + DBG("%s:%d %s\n", __func__, __LINE__, str); + +#ifdef CONFIG_SMP + smp_send_stop(); +#endif + printk("\n"); + printk(" System does not reboot automatically.\n"); + printk(" Please press POWER button.\n"); + printk("\n"); + + for (;;) ; +} + +static void __init ps3_setup_arch(void) +{ + DBG(" -> %s:%d\n", __func__, __LINE__); + + ps3_spu_set_platform(); + ps3_map_htab(); + +#ifdef CONFIG_SMP + smp_init_ps3(); +#endif + +#ifdef CONFIG_DUMMY_CONSOLE + conswitchp = &dummy_con; +#endif + + ppc_md.power_save = ps3_power_save; + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +static void __init ps3_progress(char *s, unsigned short hex) +{ + printk("*** %04x : %s\n", hex, s ? s : ""); +} + +static int __init ps3_probe(void) +{ + unsigned long htab_size; + unsigned long dt_root; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + dt_root = of_get_flat_dt_root(); + if (!of_flat_dt_is_compatible(dt_root, "PS3")) + return 0; + + powerpc_firmware_features |= FW_FEATURE_PS3_POSSIBLE; + + ps3_os_area_init(); + ps3_mm_init(); + ps3_mm_vas_create(&htab_size); + ps3_hpte_init(htab_size); + + DBG(" <- %s:%d\n", __func__, __LINE__); + return 1; +} + +#if defined(CONFIG_KEXEC) +static void ps3_kexec_cpu_down(int crash_shutdown, int secondary) +{ + DBG(" -> %s:%d\n", __func__, __LINE__); + + if (secondary) { + int cpu; + for_each_online_cpu(cpu) + if (cpu) + ps3_smp_cleanup_cpu(cpu); + } else + ps3_smp_cleanup_cpu(0); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +static void ps3_machine_kexec(struct kimage *image) +{ + unsigned long ppe_id; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + lv1_get_logical_ppe_id(&ppe_id); + lv1_configure_irq_state_bitmap(ppe_id, 0, 0); + ps3_mm_shutdown(); + ps3_mm_vas_destroy(); + + default_machine_kexec(image); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} +#endif + +define_machine(ps3) { + .name = "PS3", + .probe = ps3_probe, + .setup_arch = ps3_setup_arch, + .show_cpuinfo = ps3_show_cpuinfo, + .init_IRQ = ps3_init_IRQ, + .panic = ps3_panic, + .get_boot_time = ps3_get_boot_time, + .set_rtc_time = ps3_set_rtc_time, + .get_rtc_time = ps3_get_rtc_time, + .calibrate_decr = ps3_calibrate_decr, + .progress = ps3_progress, +#if defined(CONFIG_KEXEC) + .kexec_cpu_down = ps3_kexec_cpu_down, + .machine_kexec = ps3_machine_kexec, + .machine_kexec_prepare = default_machine_kexec_prepare, + .machine_crash_shutdown = default_machine_crash_shutdown, +#endif +}; diff --git a/arch/powerpc/platforms/ps3/smp.c b/arch/powerpc/platforms/ps3/smp.c new file mode 100644 index 00000000000..11d2080607e --- /dev/null +++ b/arch/powerpc/platforms/ps3/smp.c @@ -0,0 +1,158 @@ +/* + * PS3 SMP routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/smp.h> + +#include <asm/machdep.h> +#include <asm/udbg.h> +#include <asm/ps3.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) do{if(0)printk(fmt);}while(0) +#endif + +static irqreturn_t ipi_function_handler(int irq, void *msg) +{ + smp_message_recv((int)(long)msg); + return IRQ_HANDLED; +} + +/** + * virqs - a per cpu array of virqs for ipi use + */ + +#define MSG_COUNT 4 +static DEFINE_PER_CPU(unsigned int, virqs[MSG_COUNT]); + +static const char *names[MSG_COUNT] = { + "ipi call", + "ipi reschedule", + "ipi migrate", + "ipi debug brk" +}; + +static void do_message_pass(int target, int msg) +{ + int result; + unsigned int virq; + + if (msg >= MSG_COUNT) { + DBG("%s:%d: bad msg: %d\n", __func__, __LINE__, msg); + return; + } + + virq = per_cpu(virqs, target)[msg]; + result = ps3_send_event_locally(virq); + + if (result) + DBG("%s:%d: ps3_send_event_locally(%d, %d) failed" + " (%d)\n", __func__, __LINE__, target, msg, result); +} + +static void ps3_smp_message_pass(int target, int msg) +{ + int cpu; + + if (target < NR_CPUS) + do_message_pass(target, msg); + else if (target == MSG_ALL_BUT_SELF) { + for_each_online_cpu(cpu) + if (cpu != smp_processor_id()) + do_message_pass(cpu, msg); + } else { + for_each_online_cpu(cpu) + do_message_pass(cpu, msg); + } +} + +static int ps3_smp_probe(void) +{ + return 2; +} + +static void __init ps3_smp_setup_cpu(int cpu) +{ + int result; + unsigned int *virqs = per_cpu(virqs, cpu); + int i; + + DBG(" -> %s:%d: (%d)\n", __func__, __LINE__, cpu); + + /* + * Check assumptions on virqs[] indexing. If this + * check fails, then a different mapping of PPC_MSG_ + * to index needs to be setup. + */ + + BUILD_BUG_ON(PPC_MSG_CALL_FUNCTION != 0); + BUILD_BUG_ON(PPC_MSG_RESCHEDULE != 1); + BUILD_BUG_ON(PPC_MSG_DEBUGGER_BREAK != 3); + + for (i = 0; i < MSG_COUNT; i++) { + result = ps3_alloc_event_irq(&virqs[i]); + + if (result) + continue; + + DBG("%s:%d: (%d, %d) => virq %u\n", + __func__, __LINE__, cpu, i, virqs[i]); + + + request_irq(virqs[i], ipi_function_handler, IRQF_DISABLED, + names[i], (void*)(long)i); + } + + ps3_register_ipi_debug_brk(cpu, virqs[PPC_MSG_DEBUGGER_BREAK]); + + DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu); +} + +void ps3_smp_cleanup_cpu(int cpu) +{ + unsigned int *virqs = per_cpu(virqs, cpu); + int i; + + DBG(" -> %s:%d: (%d)\n", __func__, __LINE__, cpu); + for (i = 0; i < MSG_COUNT; i++) { + ps3_free_event_irq(virqs[i]); + free_irq(virqs[i], (void*)(long)i); + virqs[i] = NO_IRQ; + } + DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu); +} + +static struct smp_ops_t ps3_smp_ops = { + .probe = ps3_smp_probe, + .message_pass = ps3_smp_message_pass, + .kick_cpu = smp_generic_kick_cpu, + .setup_cpu = ps3_smp_setup_cpu, +}; + +void smp_init_ps3(void) +{ + DBG(" -> %s\n", __func__); + smp_ops = &ps3_smp_ops; + DBG(" <- %s\n", __func__); +} diff --git a/arch/powerpc/platforms/ps3/spu.c b/arch/powerpc/platforms/ps3/spu.c new file mode 100644 index 00000000000..644532c3b7c --- /dev/null +++ b/arch/powerpc/platforms/ps3/spu.c @@ -0,0 +1,613 @@ +/* + * PS3 Platform spu routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/mmzone.h> +#include <linux/io.h> +#include <linux/mm.h> + +#include <asm/spu.h> +#include <asm/spu_priv1.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +/* spu_management_ops */ + +/** + * enum spe_type - Type of spe to create. + * @spe_type_logical: Standard logical spe. + * + * For use with lv1_construct_logical_spe(). The current HV does not support + * any types other than those listed. + */ + +enum spe_type { + SPE_TYPE_LOGICAL = 0, +}; + +/** + * struct spe_shadow - logical spe shadow register area. + * + * Read-only shadow of spe registers. + */ + +struct spe_shadow { + u8 padding_0000[0x0140]; + u64 int_status_class0_RW; /* 0x0140 */ + u64 int_status_class1_RW; /* 0x0148 */ + u64 int_status_class2_RW; /* 0x0150 */ + u8 padding_0158[0x0610-0x0158]; + u64 mfc_dsisr_RW; /* 0x0610 */ + u8 padding_0618[0x0620-0x0618]; + u64 mfc_dar_RW; /* 0x0620 */ + u8 padding_0628[0x0800-0x0628]; + u64 mfc_dsipr_R; /* 0x0800 */ + u8 padding_0808[0x0810-0x0808]; + u64 mfc_lscrr_R; /* 0x0810 */ + u8 padding_0818[0x0c00-0x0818]; + u64 mfc_cer_R; /* 0x0c00 */ + u8 padding_0c08[0x0f00-0x0c08]; + u64 spe_execution_status; /* 0x0f00 */ + u8 padding_0f08[0x1000-0x0f08]; +} __attribute__ ((packed)); + + +/** + * enum spe_ex_state - Logical spe execution state. + * @spe_ex_state_unexecutable: Uninitialized. + * @spe_ex_state_executable: Enabled, not ready. + * @spe_ex_state_executed: Ready for use. + * + * The execution state (status) of the logical spe as reported in + * struct spe_shadow:spe_execution_status. + */ + +enum spe_ex_state { + SPE_EX_STATE_UNEXECUTABLE = 0, + SPE_EX_STATE_EXECUTABLE = 2, + SPE_EX_STATE_EXECUTED = 3, +}; + +/** + * struct priv1_cache - Cached values of priv1 registers. + * @masks[]: Array of cached spe interrupt masks, indexed by class. + * @sr1: Cached mfc_sr1 register. + * @tclass_id: Cached mfc_tclass_id register. + */ + +struct priv1_cache { + u64 masks[3]; + u64 sr1; + u64 tclass_id; +}; + +/** + * struct spu_pdata - Platform state variables. + * @spe_id: HV spe id returned by lv1_construct_logical_spe(). + * @resource_id: HV spe resource id returned by + * ps3_repository_read_spe_resource_id(). + * @priv2_addr: lpar address of spe priv2 area returned by + * lv1_construct_logical_spe(). + * @shadow_addr: lpar address of spe register shadow area returned by + * lv1_construct_logical_spe(). + * @shadow: Virtual (ioremap) address of spe register shadow area. + * @cache: Cached values of priv1 registers. + */ + +struct spu_pdata { + u64 spe_id; + u64 resource_id; + u64 priv2_addr; + u64 shadow_addr; + struct spe_shadow __iomem *shadow; + struct priv1_cache cache; +}; + +static struct spu_pdata *spu_pdata(struct spu *spu) +{ + return spu->pdata; +} + +#define dump_areas(_a, _b, _c, _d, _e) \ + _dump_areas(_a, _b, _c, _d, _e, __func__, __LINE__) +static void _dump_areas(unsigned int spe_id, unsigned long priv2, + unsigned long problem, unsigned long ls, unsigned long shadow, + const char* func, int line) +{ + pr_debug("%s:%d: spe_id: %xh (%u)\n", func, line, spe_id, spe_id); + pr_debug("%s:%d: priv2: %lxh\n", func, line, priv2); + pr_debug("%s:%d: problem: %lxh\n", func, line, problem); + pr_debug("%s:%d: ls: %lxh\n", func, line, ls); + pr_debug("%s:%d: shadow: %lxh\n", func, line, shadow); +} + +static unsigned long get_vas_id(void) +{ + unsigned long id; + + lv1_get_logical_ppe_id(&id); + lv1_get_virtual_address_space_id_of_ppe(id, &id); + + return id; +} + +static int __init construct_spu(struct spu *spu) +{ + int result; + unsigned long unused; + + result = lv1_construct_logical_spe(PAGE_SHIFT, PAGE_SHIFT, PAGE_SHIFT, + PAGE_SHIFT, PAGE_SHIFT, get_vas_id(), SPE_TYPE_LOGICAL, + &spu_pdata(spu)->priv2_addr, &spu->problem_phys, + &spu->local_store_phys, &unused, + &spu_pdata(spu)->shadow_addr, + &spu_pdata(spu)->spe_id); + + if (result) { + pr_debug("%s:%d: lv1_construct_logical_spe failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + return result; +} + +static int __init add_spu_pages(unsigned long start_addr, unsigned long size) +{ + int result; + unsigned long start_pfn; + unsigned long nr_pages; + struct pglist_data *pgdata; + struct zone *zone; + + BUG_ON(!mem_init_done); + + start_pfn = start_addr >> PAGE_SHIFT; + nr_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + pgdata = NODE_DATA(0); + zone = pgdata->node_zones; + + result = __add_pages(zone, start_pfn, nr_pages); + + if (result) + pr_debug("%s:%d: __add_pages failed: (%d)\n", + __func__, __LINE__, result); + + return result; +} + +static void spu_unmap(struct spu *spu) +{ + iounmap(spu->priv2); + iounmap(spu->problem); + iounmap((__force u8 __iomem *)spu->local_store); + iounmap(spu_pdata(spu)->shadow); +} + +static int __init setup_areas(struct spu *spu) +{ + struct table {char* name; unsigned long addr; unsigned long size;}; + int result; + + /* setup pages */ + + result = add_spu_pages(spu->local_store_phys, LS_SIZE); + if (result) + goto fail_add; + + result = add_spu_pages(spu->problem_phys, sizeof(struct spu_problem)); + if (result) + goto fail_add; + + /* ioremap */ + + spu_pdata(spu)->shadow = __ioremap( + spu_pdata(spu)->shadow_addr, sizeof(struct spe_shadow), + PAGE_READONLY | _PAGE_NO_CACHE | _PAGE_GUARDED); + if (!spu_pdata(spu)->shadow) { + pr_debug("%s:%d: ioremap shadow failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + spu->local_store = ioremap(spu->local_store_phys, LS_SIZE); + if (!spu->local_store) { + pr_debug("%s:%d: ioremap local_store failed\n", + __func__, __LINE__); + goto fail_ioremap; + } + + spu->problem = ioremap(spu->problem_phys, + sizeof(struct spu_problem)); + if (!spu->problem) { + pr_debug("%s:%d: ioremap problem failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + spu->priv2 = ioremap(spu_pdata(spu)->priv2_addr, + sizeof(struct spu_priv2)); + if (!spu->priv2) { + pr_debug("%s:%d: ioremap priv2 failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + dump_areas(spu_pdata(spu)->spe_id, spu_pdata(spu)->priv2_addr, + spu->problem_phys, spu->local_store_phys, + spu_pdata(spu)->shadow_addr); + dump_areas(spu_pdata(spu)->spe_id, (unsigned long)spu->priv2, + (unsigned long)spu->problem, (unsigned long)spu->local_store, + (unsigned long)spu_pdata(spu)->shadow); + + return 0; + +fail_ioremap: + spu_unmap(spu); +fail_add: + return result; +} + +static int __init setup_interrupts(struct spu *spu) +{ + int result; + + result = ps3_alloc_spe_irq(spu_pdata(spu)->spe_id, 0, + &spu->irqs[0]); + + if (result) + goto fail_alloc_0; + + result = ps3_alloc_spe_irq(spu_pdata(spu)->spe_id, 1, + &spu->irqs[1]); + + if (result) + goto fail_alloc_1; + + result = ps3_alloc_spe_irq(spu_pdata(spu)->spe_id, 2, + &spu->irqs[2]); + + if (result) + goto fail_alloc_2; + + return result; + +fail_alloc_2: + ps3_free_spe_irq(spu->irqs[1]); +fail_alloc_1: + ps3_free_spe_irq(spu->irqs[0]); +fail_alloc_0: + spu->irqs[0] = spu->irqs[1] = spu->irqs[2] = NO_IRQ; + return result; +} + +static int __init enable_spu(struct spu *spu) +{ + int result; + + result = lv1_enable_logical_spe(spu_pdata(spu)->spe_id, + spu_pdata(spu)->resource_id); + + if (result) { + pr_debug("%s:%d: lv1_enable_logical_spe failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail_enable; + } + + result = setup_areas(spu); + + if (result) + goto fail_areas; + + result = setup_interrupts(spu); + + if (result) + goto fail_interrupts; + + return 0; + +fail_interrupts: + spu_unmap(spu); +fail_areas: + lv1_disable_logical_spe(spu_pdata(spu)->spe_id, 0); +fail_enable: + return result; +} + +static int ps3_destroy_spu(struct spu *spu) +{ + int result; + + pr_debug("%s:%d spu_%d\n", __func__, __LINE__, spu->number); + + result = lv1_disable_logical_spe(spu_pdata(spu)->spe_id, 0); + BUG_ON(result); + + ps3_free_spe_irq(spu->irqs[2]); + ps3_free_spe_irq(spu->irqs[1]); + ps3_free_spe_irq(spu->irqs[0]); + + spu->irqs[0] = spu->irqs[1] = spu->irqs[2] = NO_IRQ; + + spu_unmap(spu); + + result = lv1_destruct_logical_spe(spu_pdata(spu)->spe_id); + BUG_ON(result); + + kfree(spu->pdata); + spu->pdata = NULL; + + return 0; +} + +static int __init ps3_create_spu(struct spu *spu, void *data) +{ + int result; + + pr_debug("%s:%d spu_%d\n", __func__, __LINE__, spu->number); + + spu->pdata = kzalloc(sizeof(struct spu_pdata), + GFP_KERNEL); + + if (!spu->pdata) { + result = -ENOMEM; + goto fail_malloc; + } + + spu_pdata(spu)->resource_id = (unsigned long)data; + + /* Init cached reg values to HV defaults. */ + + spu_pdata(spu)->cache.sr1 = 0x33; + + result = construct_spu(spu); + + if (result) + goto fail_construct; + + /* For now, just go ahead and enable it. */ + + result = enable_spu(spu); + + if (result) + goto fail_enable; + + /* Make sure the spu is in SPE_EX_STATE_EXECUTED. */ + + /* need something better here!!! */ + while (in_be64(&spu_pdata(spu)->shadow->spe_execution_status) + != SPE_EX_STATE_EXECUTED) + (void)0; + + return result; + +fail_enable: +fail_construct: + ps3_destroy_spu(spu); +fail_malloc: + return result; +} + +static int __init ps3_enumerate_spus(int (*fn)(void *data)) +{ + int result; + unsigned int num_resource_id; + unsigned int i; + + result = ps3_repository_read_num_spu_resource_id(&num_resource_id); + + pr_debug("%s:%d: num_resource_id %u\n", __func__, __LINE__, + num_resource_id); + + /* + * For now, just create logical spus equal to the number + * of physical spus reserved for the partition. + */ + + for (i = 0; i < num_resource_id; i++) { + enum ps3_spu_resource_type resource_type; + unsigned int resource_id; + + result = ps3_repository_read_spu_resource_id(i, + &resource_type, &resource_id); + + if (result) + break; + + if (resource_type == PS3_SPU_RESOURCE_TYPE_EXCLUSIVE) { + result = fn((void*)(unsigned long)resource_id); + + if (result) + break; + } + } + + if (result) + printk(KERN_WARNING "%s:%d: Error initializing spus\n", + __func__, __LINE__); + + return result; +} + +const struct spu_management_ops spu_management_ps3_ops = { + .enumerate_spus = ps3_enumerate_spus, + .create_spu = ps3_create_spu, + .destroy_spu = ps3_destroy_spu, +}; + +/* spu_priv1_ops */ + +static void int_mask_and(struct spu *spu, int class, u64 mask) +{ + u64 old_mask; + + /* are these serialized by caller??? */ + old_mask = spu_int_mask_get(spu, class); + spu_int_mask_set(spu, class, old_mask & mask); +} + +static void int_mask_or(struct spu *spu, int class, u64 mask) +{ + u64 old_mask; + + old_mask = spu_int_mask_get(spu, class); + spu_int_mask_set(spu, class, old_mask | mask); +} + +static void int_mask_set(struct spu *spu, int class, u64 mask) +{ + spu_pdata(spu)->cache.masks[class] = mask; + lv1_set_spe_interrupt_mask(spu_pdata(spu)->spe_id, class, + spu_pdata(spu)->cache.masks[class]); +} + +static u64 int_mask_get(struct spu *spu, int class) +{ + return spu_pdata(spu)->cache.masks[class]; +} + +static void int_stat_clear(struct spu *spu, int class, u64 stat) +{ + /* Note that MFC_DSISR will be cleared when class1[MF] is set. */ + + lv1_clear_spe_interrupt_status(spu_pdata(spu)->spe_id, class, + stat, 0); +} + +static u64 int_stat_get(struct spu *spu, int class) +{ + u64 stat; + + lv1_get_spe_interrupt_status(spu_pdata(spu)->spe_id, class, &stat); + return stat; +} + +static void cpu_affinity_set(struct spu *spu, int cpu) +{ + /* No support. */ +} + +static u64 mfc_dar_get(struct spu *spu) +{ + return in_be64(&spu_pdata(spu)->shadow->mfc_dar_RW); +} + +static void mfc_dsisr_set(struct spu *spu, u64 dsisr) +{ + /* Nothing to do, cleared in int_stat_clear(). */ +} + +static u64 mfc_dsisr_get(struct spu *spu) +{ + return in_be64(&spu_pdata(spu)->shadow->mfc_dsisr_RW); +} + +static void mfc_sdr_setup(struct spu *spu) +{ + /* Nothing to do. */ +} + +static void mfc_sr1_set(struct spu *spu, u64 sr1) +{ + /* Check bits allowed by HV. */ + + static const u64 allowed = ~(MFC_STATE1_LOCAL_STORAGE_DECODE_MASK + | MFC_STATE1_PROBLEM_STATE_MASK); + + BUG_ON((sr1 & allowed) != (spu_pdata(spu)->cache.sr1 & allowed)); + + spu_pdata(spu)->cache.sr1 = sr1; + lv1_set_spe_privilege_state_area_1_register( + spu_pdata(spu)->spe_id, + offsetof(struct spu_priv1, mfc_sr1_RW), + spu_pdata(spu)->cache.sr1); +} + +static u64 mfc_sr1_get(struct spu *spu) +{ + return spu_pdata(spu)->cache.sr1; +} + +static void mfc_tclass_id_set(struct spu *spu, u64 tclass_id) +{ + spu_pdata(spu)->cache.tclass_id = tclass_id; + lv1_set_spe_privilege_state_area_1_register( + spu_pdata(spu)->spe_id, + offsetof(struct spu_priv1, mfc_tclass_id_RW), + spu_pdata(spu)->cache.tclass_id); +} + +static u64 mfc_tclass_id_get(struct spu *spu) +{ + return spu_pdata(spu)->cache.tclass_id; +} + +static void tlb_invalidate(struct spu *spu) +{ + /* Nothing to do. */ +} + +static void resource_allocation_groupID_set(struct spu *spu, u64 id) +{ + /* No support. */ +} + +static u64 resource_allocation_groupID_get(struct spu *spu) +{ + return 0; /* No support. */ +} + +static void resource_allocation_enable_set(struct spu *spu, u64 enable) +{ + /* No support. */ +} + +static u64 resource_allocation_enable_get(struct spu *spu) +{ + return 0; /* No support. */ +} + +const struct spu_priv1_ops spu_priv1_ps3_ops = { + .int_mask_and = int_mask_and, + .int_mask_or = int_mask_or, + .int_mask_set = int_mask_set, + .int_mask_get = int_mask_get, + .int_stat_clear = int_stat_clear, + .int_stat_get = int_stat_get, + .cpu_affinity_set = cpu_affinity_set, + .mfc_dar_get = mfc_dar_get, + .mfc_dsisr_set = mfc_dsisr_set, + .mfc_dsisr_get = mfc_dsisr_get, + .mfc_sdr_setup = mfc_sdr_setup, + .mfc_sr1_set = mfc_sr1_set, + .mfc_sr1_get = mfc_sr1_get, + .mfc_tclass_id_set = mfc_tclass_id_set, + .mfc_tclass_id_get = mfc_tclass_id_get, + .tlb_invalidate = tlb_invalidate, + .resource_allocation_groupID_set = resource_allocation_groupID_set, + .resource_allocation_groupID_get = resource_allocation_groupID_get, + .resource_allocation_enable_set = resource_allocation_enable_set, + .resource_allocation_enable_get = resource_allocation_enable_get, +}; + +void ps3_spu_set_platform(void) +{ + spu_priv1_ops = &spu_priv1_ps3_ops; + spu_management_ops = &spu_management_ps3_ops; +} diff --git a/arch/powerpc/platforms/ps3/time.c b/arch/powerpc/platforms/ps3/time.c new file mode 100644 index 00000000000..1bae8b19b36 --- /dev/null +++ b/arch/powerpc/platforms/ps3/time.c @@ -0,0 +1,104 @@ +/* + * PS3 time and rtc routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * + * 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; version 2 of the License. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> + +#include <asm/rtc.h> +#include <asm/lv1call.h> +#include <asm/ps3.h> + +#include "platform.h" + +#define dump_tm(_a) _dump_tm(_a, __func__, __LINE__) +static void _dump_tm(const struct rtc_time *tm, const char* func, int line) +{ + pr_debug("%s:%d tm_sec %d\n", func, line, tm->tm_sec); + pr_debug("%s:%d tm_min %d\n", func, line, tm->tm_min); + pr_debug("%s:%d tm_hour %d\n", func, line, tm->tm_hour); + pr_debug("%s:%d tm_mday %d\n", func, line, tm->tm_mday); + pr_debug("%s:%d tm_mon %d\n", func, line, tm->tm_mon); + pr_debug("%s:%d tm_year %d\n", func, line, tm->tm_year); + pr_debug("%s:%d tm_wday %d\n", func, line, tm->tm_wday); +} + +#define dump_time(_a) _dump_time(_a, __func__, __LINE__) +static void __attribute__ ((unused)) _dump_time(int time, const char* func, + int line) +{ + struct rtc_time tm; + + to_tm(time, &tm); + + pr_debug("%s:%d time %d\n", func, line, time); + _dump_tm(&tm, func, line); +} + +/** + * rtc_shift - Difference in seconds between 1970 and the ps3 rtc value. + */ + +static s64 rtc_shift; + +void __init ps3_calibrate_decr(void) +{ + int result; + u64 tmp; + + result = ps3_repository_read_be_tb_freq(0, &tmp); + BUG_ON(result); + + ppc_tb_freq = tmp; + ppc_proc_freq = ppc_tb_freq * 40; + + rtc_shift = ps3_os_area_rtc_diff(); +} + +static u64 read_rtc(void) +{ + int result; + u64 rtc_val; + u64 tb_val; + + result = lv1_get_rtc(&rtc_val, &tb_val); + BUG_ON(result); + + return rtc_val; +} + +int ps3_set_rtc_time(struct rtc_time *tm) +{ + u64 now = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + + rtc_shift = now - read_rtc(); + return 0; +} + +void ps3_get_rtc_time(struct rtc_time *tm) +{ + to_tm(read_rtc() + rtc_shift, tm); + tm->tm_year -= 1900; + tm->tm_mon -= 1; +} + +unsigned long __init ps3_get_boot_time(void) +{ + return read_rtc() + rtc_shift; +} |