[RFC] xenon: add platform support This patch adds platform support for the 'Xenon' platform (Xbox 360). I believe the CELL TB bug also applies for the xenon. Microsoft describes the bug in "Xbox 360 CPU Performance Update" (which is available on http://msdn2.microsoft.com/en-us/xna/aa937787.aspx), but I have not verified it's existence. Signed-off-by: Felix Domke --- arch/powerpc/Kconfig | 9 arch/powerpc/boot/Makefile | 4 arch/powerpc/platforms/Makefile | 1 arch/powerpc/platforms/xenon/Makefile | 4 arch/powerpc/platforms/xenon/interrupt.c | 307 +++++++++++++++++++++++++++++++ arch/powerpc/platforms/xenon/interrupt.h | 9 arch/powerpc/platforms/xenon/pci.c | 189 +++++++++++++++++++ arch/powerpc/platforms/xenon/pci.h | 6 arch/powerpc/platforms/xenon/setup.c | 89 ++++++++ arch/powerpc/platforms/xenon/smp.c | 95 +++++++++ arch/powerpc/platforms/xenon/smp.h | 8 11 files changed, 721 insertions(+) Index: linux-2.6.21/arch/powerpc/Kconfig =================================================================== --- linux-2.6.21.orig/arch/powerpc/Kconfig 2007-05-01 14:12:29.000000000 +0200 +++ linux-2.6.21/arch/powerpc/Kconfig 2007-05-01 14:12:33.000000000 +0200 @@ -574,6 +574,15 @@ select USB_OHCI_BIG_ENDIAN_MMIO select USB_EHCI_BIG_ENDIAN_MMIO +config PPC_XENON + bool "Xenon" + depends on PPC_MULTIPLATFORM && PPC64 + select PPC_NATIVE + default y + help + This option enables support for the Xbox 360 game console + without a hypervisor. + config PPC_NATIVE bool depends on PPC_MULTIPLATFORM Index: linux-2.6.21/arch/powerpc/boot/Makefile =================================================================== --- linux-2.6.21.orig/arch/powerpc/boot/Makefile 2007-05-01 14:12:29.000000000 +0200 +++ linux-2.6.21/arch/powerpc/boot/Makefile 2007-05-01 14:12:33.000000000 +0200 @@ -149,6 +149,9 @@ $(obj)/zImage.initrd.miboot: vmlinux $(wrapperbits) $(call cmd,wrap_initrd,miboot) +$(obj)/zImage.xenon: vmlinux + $(OBJCOPY) -O elf32-powerpc $< $@ + $(obj)/zImage.ps3: vmlinux $(STRIP) -s -R .comment $< -o $@ @@ -161,6 +164,7 @@ image-$(CONFIG_PPC_PSERIES) += zImage.pseries image-$(CONFIG_PPC_MAPLE) += zImage.pseries image-$(CONFIG_PPC_IBM_CELL_BLADE) += zImage.pseries +image-$(CONFIG_PPC_XENON) += zImage.xenon image-$(CONFIG_PPC_PS3) += zImage.ps3 image-$(CONFIG_PPC_CELLEB) += zImage.pseries image-$(CONFIG_PPC_CHRP) += zImage.chrp Index: linux-2.6.21/arch/powerpc/platforms/Makefile =================================================================== --- linux-2.6.21.orig/arch/powerpc/platforms/Makefile 2007-05-01 14:12:29.000000000 +0200 +++ linux-2.6.21/arch/powerpc/platforms/Makefile 2007-05-01 14:12:33.000000000 +0200 @@ -21,3 +21,4 @@ obj-$(CONFIG_PPC_PS3) += ps3/ obj-$(CONFIG_PPC_CELLEB) += celleb/ obj-$(CONFIG_EMBEDDED6xx) += embedded6xx/ +obj-$(CONFIG_PPC_XENON) += xenon/ Index: linux-2.6.21/arch/powerpc/platforms/xenon/Makefile =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/Makefile 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,4 @@ +obj-$(CONFIG_PPC_XENON) += setup.o interrupt.o pci.o +ifeq ($(CONFIG_SMP),y) +obj-$(CONFIG_PPC_XENON) += smp.o +endif Index: linux-2.6.21/arch/powerpc/platforms/xenon/interrupt.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/interrupt.c 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,307 @@ +/* + * Xenon interrupt controller, + * + * Maintained by: Felix Domke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License v2 + * as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "interrupt.h" + +static void *iic_base, + *bridge_base, // ea000000 + *biu, // e1000000 + *graphics; // ec800000 +static struct irq_host *host; + +#define XENON_NR_IRQS 128 + +#define PRIO_IPI_4 0x08 +#define PRIO_IPI_3 0x10 +#define PRIO_SMM 0x14 +#define PRIO_SFCX 0x18 +#define PRIO_SATA_HDD 0x20 +#define PRIO_SATA_CDROM 0x24 +#define PRIO_OHCI_0 0x2c +#define PRIO_EHCI_0 0x30 +#define PRIO_OHCI_1 0x34 +#define PRIO_EHCI_1 0x38 +#define PRIO_XMA 0x40 +#define PRIO_AUDIO 0x44 +#define PRIO_ENET 0x4C +#define PRIO_XPS 0x54 +#define PRIO_GRAPHICS 0x58 +#define PRIO_PROFILER 0x60 +#define PRIO_BIU 0x64 +#define PRIO_IOC 0x68 +#define PRIO_FSB 0x6c +#define PRIO_IPI_2 0x70 +#define PRIO_CLOCK 0x74 +#define PRIO_IPI_1 0x78 + +/* bridge (PCI) IRQ -> CPU IRQ */ +static int xenon_pci_irq_map[] = { + PRIO_CLOCK, PRIO_SATA_CDROM, PRIO_SATA_HDD, PRIO_SMM, + PRIO_OHCI_0, PRIO_EHCI_0, PRIO_OHCI_1, PRIO_EHCI_1, + -1, -1, PRIO_ENET, PRIO_XMA, + PRIO_AUDIO, PRIO_SFCX, -1, -1}; + +static void disconnect_pci_irq(int prio) +{ + int i; + + for (i=0; i<0x10; ++i) + if (xenon_pci_irq_map[i] == prio) + writel(0, bridge_base + 0x10 + i * 4); +} + + /* connects an PCI IRQ to CPU #0 */ +static void connect_pci_irq(int prio) +{ + int i; + + for (i=0; i<0x10; ++i) + if (xenon_pci_irq_map[i] == prio) + writel(0x0800180 | (xenon_pci_irq_map[i]/4), bridge_base + 0x10 + i * 4); +} + +static void iic_mask(unsigned int irq) +{ + disconnect_pci_irq(irq); +} + +static void iic_unmask(unsigned int irq) +{ + int i; + connect_pci_irq(irq); + for (i=0; i<6; ++i) + out_be64(iic_base + i * 0x1000 + 0x68, 0); +} + +void xenon_init_irq_on_cpu(int cpu) +{ + /* init that cpu's interrupt controller */ + out_be64(iic_base + cpu * 0x1000 + 0x70, 0x7c); + out_be64(iic_base + cpu * 0x1000 + 0x8, 0); /* irql */ + out_be64(iic_base + cpu * 0x1000, 1<host_data != NULL && node == h->host_data; +} + +static struct irq_host_ops xenon_irq_host_ops = { + .map = xenon_irq_host_map, + .match = xenon_irq_host_match, +}; + +void __init xenon_iic_init_IRQ(void) +{ + int i; + struct device_node *dn; + struct resource res; + + /* search for our interrupt controller inside the device tree */ + for (dn = NULL; + (dn = of_find_node_by_name(dn, "interrupt-controller")) != NULL;) { + if (!device_is_compatible(dn, "xenon")) + continue; + + if (of_address_to_resource(dn, 0, &res)) + { + printk(KERN_WARNING "xenon IIC: Can't resolve addresses\n"); + of_node_put(dn); + return; + } + + irq_set_virq_count(0x80); + iic_base = ioremap_nocache(res.start, 0x10000); + + host = irq_alloc_host(IRQ_HOST_MAP_NOMAP, 0, &xenon_irq_host_ops, 0); + host->host_data = of_node_get(dn); + BUG_ON(host == NULL); + irq_set_default_host(host); + } + + ppc_md.get_irq = iic_get_irq; + + bridge_base = ioremap_nocache(0xea000000, 0x10000); + biu = ioremap_nocache(0xe1000000, 0x2000000); + graphics = ioremap_nocache(0xec800000, 0x10000); + + /* initialize interrupts */ + writel(0, bridge_base); + writel(0x40000000, bridge_base + 4); + + writel(0x40000000, biu + 0x40074); + writel(0xea000050, biu + 0x40078); + + writel(0, bridge_base + 0xc); + writel(0x3, bridge_base); + + /* disconnect all PCI IRQs until they are requested */ + for (i=0; i<0x10; ++i) + writel(0, bridge_base + 0x10 + i * 4); + + xenon_init_irq_on_cpu(0); +} + +#ifdef CONFIG_SMP + +static int ipi_to_prio(int ipi) +{ + switch (ipi) + { + case PPC_MSG_CALL_FUNCTION: + return PRIO_IPI_1; + break; + case PPC_MSG_RESCHEDULE: + return PRIO_IPI_2; + break; + case PPC_MSG_DEBUGGER_BREAK: + return PRIO_IPI_4; + break; + default: + BUG(); + } + return 0; +} + +void xenon_cause_IPI(int target, int msg) +{ + int ipi_prio; + + ipi_prio = ipi_to_prio(msg); + + out_be64(iic_base + 0x10 + hard_smp_processor_id() * 0x1000, (0x10000< + * based on: + * Copyright (C) 2004 Benjamin Herrenschmuidt (benh@kernel.crashing.org), + * IBM 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; either version + * 2 of the License, or (at your option) any later version. + */ + +// #define DEBUG + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +#define OFFSET(devfn) ((devfn+256)<<12) + +static int xenon_pci_read_config(struct pci_bus *bus, unsigned int devfn, + int offset, int len, u32 *val) +{ + struct pci_controller *hose; + void* addr; + + hose = pci_bus_to_host(bus); + if (hose == NULL) + return PCIBIOS_DEVICE_NOT_FOUND; + + DBG("xenon_pci_read_config,slot %d, func %d\n", PCI_SLOT(devfn), PCI_FUNC(devfn)); + + if (PCI_SLOT(devfn) >= 32) + return PCIBIOS_DEVICE_NOT_FOUND; + if (PCI_SLOT(devfn) == 3) + return PCIBIOS_DEVICE_NOT_FOUND; + if (PCI_SLOT(devfn) == 6) + return PCIBIOS_DEVICE_NOT_FOUND; + if (PCI_SLOT(devfn) >= 0xB) + return PCIBIOS_DEVICE_NOT_FOUND; + if (PCI_FUNC(devfn) >= 2) + return PCIBIOS_DEVICE_NOT_FOUND; + DBG("xenon_pci_read_config, %p, devfn=%d, offset=%d, len=%d\n", bus, devfn, offset, len); + + addr = ((void*)hose->cfg_addr) + OFFSET(devfn) + offset; + + /* + * Note: the caller has already checked that offset is + * suitably aligned and that len is 1, 2 or 4. + */ + switch (len) { + case 1: + *val = in_8((u8 *)addr); + break; + case 2: + *val = in_le16((u16 *)addr); + break; + default: + *val = in_le32((u32 *)addr); + break; + } + DBG("->%08x\n", (int)*val); + return PCIBIOS_SUCCESSFUL; +} + +static int xenon_pci_write_config(struct pci_bus *bus, unsigned int devfn, + int offset, int len, u32 val) +{ + struct pci_controller *hose; + void *addr; + + hose = pci_bus_to_host(bus); + if (hose == NULL) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (PCI_SLOT(devfn) >= 32) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (PCI_SLOT(devfn) == 3) + return PCIBIOS_DEVICE_NOT_FOUND; + DBG("xenon_pci_write_config, %p, devfn=%d, offset=%x, len=%d, val=%08x\n", bus, devfn, offset, len, val); + + addr = ((void*)hose->cfg_addr) + OFFSET(devfn) + offset; + if (len == 4) + DBG("was: %08x\n", readl(addr)); + if (len == 2) + DBG("was: %04x\n", readw(addr)); + if (len == 1) + DBG("was: %02x\n", readb(addr)); + /* + * Note: the caller has already checked that offset is + * suitably aligned and that len is 1, 2 or 4. + */ + switch (len) { + case 1: + writeb(val, addr); + break; + case 2: + writew(val, addr); + break; + default: + writel(val, addr); + break; + } + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops xenon_pci_ops = +{ + xenon_pci_read_config, + xenon_pci_write_config +}; + +void __init xenon_pci_init(void) +{ + struct pci_controller *hose; + struct device_node *np, *root; + struct device_node *dev = NULL; + + root = of_find_node_by_path("/"); + if (root == NULL) { + printk(KERN_CRIT "xenon_pci_init: can't find root of device tree\n"); + return; + } + for (np = NULL; (np = of_get_next_child(root, np)) != NULL;) { + if (np->name == NULL) + continue; + if (strcmp(np->name, "pci") == 0) { + of_node_get(np); + dev = np; + } + } + of_node_put(root); + + if (!dev) + { + printk("couldn't find PCI node!\n"); + return; + } + + hose = pcibios_alloc_controller(dev); + if (hose == NULL) + { + printk("pcibios_alloc_controller failed!\n"); + return; + } + + hose->first_busno = 0; + hose->last_busno = 0; + + hose->ops = &xenon_pci_ops; + hose->cfg_addr = ioremap(0xd0000000, 0x1000000); + + pci_process_bridge_OF_ranges(hose, dev, 1); + pci_setup_phb_io(hose, 1); + + /* Setup the linkage between OF nodes and PHBs */ + pci_devs_phb_init(); + + /* Tell pci.c to not change any resource allocations. */ + pci_probe_only = 1; + + of_node_put(dev); + DBG("PCI initialized\n"); + + pci_io_base = 0; + + ppc_md.pci_dma_dev_setup = NULL; + ppc_md.pci_dma_bus_setup = NULL; + pci_dma_ops = &dma_direct_ops; +} Index: linux-2.6.21/arch/powerpc/platforms/xenon/setup.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/setup.c 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,89 @@ +/* + * linux/arch/powerpc/platforms/xenon/xenon_setup.c + * + * Maintained by: Felix Domke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +// #define DEBUG + +#include +#include +#include +#include + +#include +#include +#include +#include "interrupt.h" +#include "pci.h" +#include "smp.h" + +static void xenon_show_cpuinfo(struct seq_file *m) +{ + struct device_node *root; + const char *model = ""; + + root = of_find_node_by_path("/"); + if (root) + model = get_property(root, "model", NULL); + seq_printf(m, "machine\t\t: %s\n", model); + of_node_put(root); +} + +static void __init xenon_init_irq(void) +{ + xenon_iic_init_IRQ(); +} + +static void __init xenon_setup_arch(void) +{ +#ifdef CONFIG_SMP + smp_init_xenon(); +#endif + /* init to some ~sane value until calibrate_delay() runs */ + loops_per_jiffy = 50000000; + + if (ROOT_DEV == 0) + ROOT_DEV = Root_SDA1; + + xenon_pci_init(); +#ifdef CONFIG_DUMMY_CONSOLE + conswitchp = &dummy_con; +#endif +} + +static int __init xenon_probe(void) +{ + unsigned long root = of_get_flat_dt_root(); + + if (!of_flat_dt_is_compatible(root, "XENON")) + return 0; + + hpte_init_native(); + + return 1; +} + +static int xenon_check_legacy_ioport(unsigned int baseport) +{ + return -ENODEV; +} + +define_machine(xenon) { + .name = "Xenon", + .probe = xenon_probe, + .setup_arch = xenon_setup_arch, + .show_cpuinfo = xenon_show_cpuinfo, + .calibrate_decr = generic_calibrate_decr, + .check_legacy_ioport = xenon_check_legacy_ioport, + .init_IRQ = xenon_init_irq, +#ifdef CONFIG_KEXEC + .machine_kexec = default_machine_kexec, + .machine_kexec_prepare = default_machine_kexec_prepare, + .machine_crash_shutdown = default_machine_crash_shutdown, +#endif +}; Index: linux-2.6.21/arch/powerpc/platforms/xenon/smp.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/smp.c 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,95 @@ +/* + * SMP support for Xenon machines. + * + * Based on CBE's smp.c. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +// #define DEBUG + +#include +#include +#include +#include +#include "interrupt.h" + +static int __init smp_xenon_probe(void) +{ + xenon_request_IPIs(); + + return cpus_weight(cpu_possible_map); +} + +static void __devinit smp_xenon_setup_cpu(int cpu) +{ + if (cpu != boot_cpuid) + xenon_init_irq_on_cpu(cpu); +} + +static void __devinit smp_xenon_kick_cpu(int nr) +{ + BUG_ON(nr < 0 || nr >= NR_CPUS); + + pr_debug("smp_xenon_kick_cpu %d\n", nr); + + /* + * The processor is currently spinning, waiting for the + * cpu_start field to become non-zero After we set cpu_start, + * the processor will continue on to secondary_start + */ + paca[nr].cpu_start = 1; +} + +static int smp_xenon_cpu_bootable(unsigned int nr) +{ + /* Special case - we inhibit secondary thread startup + * during boot if the user requests it. Odd-numbered + * cpus are assumed to be secondary threads. + */ + if (system_state < SYSTEM_RUNNING && + cpu_has_feature(CPU_FTR_SMT) && + !smt_enabled_at_boot && nr % 2 != 0) + return 0; + + return 1; +} + +extern void xenon_cause_IPI(int target, int msg); + +static void smp_xenon_message_pass(int target, int msg) +{ + unsigned int i; + + if (target < NR_CPUS) { + xenon_cause_IPI(target, msg); + } else { + for_each_online_cpu(i) { + if (target == MSG_ALL_BUT_SELF + && i == smp_processor_id()) + continue; + xenon_cause_IPI(i, msg); + } + } +} + +static struct smp_ops_t xenon_smp_ops = { + .message_pass = smp_xenon_message_pass, + .probe = smp_xenon_probe, + .kick_cpu = smp_xenon_kick_cpu, + .setup_cpu = smp_xenon_setup_cpu, + .cpu_bootable = smp_xenon_cpu_bootable, +}; + +/* This is called very early */ +void __init smp_init_xenon(void) +{ + pr_debug(" -> smp_init_xenon()\n"); + + smp_ops = &xenon_smp_ops; + + pr_debug(" <- smp_init_xenon()\n"); +} Index: linux-2.6.21/arch/powerpc/platforms/xenon/pci.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/pci.h 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,6 @@ +#ifndef XENON_PCI_H +#define XENON_PCI_H + +extern void __init xenon_pci_init(void); + +#endif Index: linux-2.6.21/arch/powerpc/platforms/xenon/smp.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/arch/powerpc/platforms/xenon/smp.h 2007-05-01 14:12:33.000000000 +0200 @@ -0,0 +1,8 @@ +#ifndef XENON_SMP_H +#define XENON_SMP_H + +#ifdef CONFIG_SMP +extern void smp_init_xenon(void); +#endif + +#endif