/* * spu management operations for of based platforms * * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 * Copyright 2006 Sony Corp. * (C) Copyright 2007 TOSHIBA CORPORATION * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <linux/interrupt.h> #include <linux/list.h> #include <linux/export.h> #include <linux/ptrace.h> #include <linux/wait.h> #include <linux/mm.h> #include <linux/io.h> #include <linux/mutex.h> #include <linux/device.h> #include <asm/spu.h> #include <asm/spu_priv1.h> #include <asm/firmware.h> #include <asm/prom.h> #include "spufs/spufs.h" #include "interrupt.h" struct device_node *spu_devnode(struct spu *spu) { return spu->devnode; } EXPORT_SYMBOL_GPL(spu_devnode); static u64 __init find_spu_unit_number(struct device_node *spe) { const unsigned int *prop; int proplen; /* new device trees should provide the physical-id attribute */ prop = of_get_property(spe, "physical-id", &proplen); if (proplen == 4) return (u64)*prop; /* celleb device tree provides the unit-id */ prop = of_get_property(spe, "unit-id", &proplen); if (proplen == 4) return (u64)*prop; /* legacy device trees provide the id in the reg attribute */ prop = of_get_property(spe, "reg", &proplen); if (proplen == 4) return (u64)*prop; return 0; } static void spu_unmap(struct spu *spu) { if (!firmware_has_feature(FW_FEATURE_LPAR)) iounmap(spu->priv1); iounmap(spu->priv2); iounmap(spu->problem); iounmap((__force u8 __iomem *)spu->local_store); } static int __init spu_map_interrupts_old(struct spu *spu, struct device_node *np) { unsigned int isrc; const u32 *tmp; int nid; /* Get the interrupt source unit from the device-tree */ tmp = of_get_property(np, "isrc", NULL); if (!tmp) return -ENODEV; isrc = tmp[0]; tmp = of_get_property(np->parent->parent, "node-id", NULL); if (!tmp) { printk(KERN_WARNING "%s: can't find node-id\n", __func__); nid = spu->node; } else nid = tmp[0]; /* Add the node number */ isrc |= nid << IIC_IRQ_NODE_SHIFT; /* Now map interrupts of all 3 classes */ spu->irqs[0] = irq_create_mapping(NULL, IIC_IRQ_CLASS_0 | isrc); spu->irqs[1] = irq_create_mapping(NULL, IIC_IRQ_CLASS_1 | isrc); spu->irqs[2] = irq_create_mapping(NULL, IIC_IRQ_CLASS_2 | isrc); /* Right now, we only fail if class 2 failed */ return spu->irqs[2] == NO_IRQ ? -EINVAL : 0; } static void __iomem * __init spu_map_prop_old(struct spu *spu, struct device_node *n, const char *name) { const struct address_prop { unsigned long address; unsigned int len; } __attribute__((packed)) *prop; int proplen; prop = of_get_property(n, name, &proplen); if (prop == NULL || proplen != sizeof (struct address_prop)) return NULL; return ioremap(prop->address, prop->len); } static int __init spu_map_device_old(struct spu *spu) { struct device_node *node = spu->devnode; const char *prop; int ret; ret = -ENODEV; spu->name = of_get_property(node, "name", NULL); if (!spu->name) goto out; prop = of_get_property(node, "local-store", NULL); if (!prop) goto out; spu->local_store_phys = *(unsigned long *)prop; /* we use local store as ram, not io memory */ spu->local_store = (void __force *) spu_map_prop_old(spu, node, "local-store"); if (!spu->local_store) goto out; prop = of_get_property(node, "problem", NULL); if (!prop) goto out_unmap; spu->problem_phys = *(unsigned long *)prop; spu->problem = spu_map_prop_old(spu, node, "problem"); if (!spu->problem) goto out_unmap; spu->priv2 = spu_map_prop_old(spu, node, "priv2"); if (!spu->priv2) goto out_unmap; if (!firmware_has_feature(FW_FEATURE_LPAR)) { spu->priv1 = spu_map_prop_old(spu, node, "priv1"); if (!spu->priv1) goto out_unmap; } ret = 0; goto out; out_unmap: spu_unmap(spu); out: return ret; } static int __init spu_map_interrupts(struct spu *spu, struct device_node *np) { struct of_phandle_args oirq; int ret; int i; for (i=0; i < 3; i++) { ret = of_irq_parse_one(np, i, &oirq); if (ret) { pr_debug("spu_new: failed to get irq %d\n", i); goto err; } ret = -EINVAL; pr_debug(" irq %d no 0x%x on %s\n", i, oirq.args[0], oirq.np->full_name); spu->irqs[i] = irq_create_of_mapping(&oirq); if (spu->irqs[i] == NO_IRQ) { pr_debug("spu_new: failed to map it !\n"); goto err; } } return 0; err: pr_debug("failed to map irq %x for spu %s\n", *oirq.args, spu->name); for (; i >= 0; i--) { if (spu->irqs[i] != NO_IRQ) irq_dispose_mapping(spu->irqs[i]); } return ret; } static int spu_map_resource(struct spu *spu, int nr, void __iomem** virt, unsigned long *phys) { struct device_node *np = spu->devnode; struct resource resource = { }; unsigned long len; int ret; ret = of_address_to_resource(np, nr, &resource); if (ret) return ret; if (phys) *phys = resource.start; len = resource_size(&resource); *virt = ioremap(resource.start, len); if (!*virt) return -EINVAL; return 0; } static int __init spu_map_device(struct spu *spu) { struct device_node *np = spu->devnode; int ret = -ENODEV; spu->name = of_get_property(np, "name", NULL); if (!spu->name) goto out; ret = spu_map_resource(spu, 0, (void __iomem**)&spu->local_store, &spu->local_store_phys); if (ret) { pr_debug("spu_new: failed to map %s resource 0\n", np->full_name); goto out; } ret = spu_map_resource(spu, 1, (void __iomem**)&spu->problem, &spu->problem_phys); if (ret) { pr_debug("spu_new: failed to map %s resource 1\n", np->full_name); goto out_unmap; } ret = spu_map_resource(spu, 2, (void __iomem**)&spu->priv2, NULL); if (ret) { pr_debug("spu_new: failed to map %s resource 2\n", np->full_name); goto out_unmap; } if (!firmware_has_feature(FW_FEATURE_LPAR)) ret = spu_map_resource(spu, 3, (void __iomem**)&spu->priv1, NULL); if (ret) { pr_debug("spu_new: failed to map %s resource 3\n", np->full_name); goto out_unmap; } pr_debug("spu_new: %s maps:\n", np->full_name); pr_debug(" local store : 0x%016lx -> 0x%p\n", spu->local_store_phys, spu->local_store); pr_debug(" problem state : 0x%016lx -> 0x%p\n", spu->problem_phys, spu->problem); pr_debug(" priv2 : 0x%p\n", spu->priv2); pr_debug(" priv1 : 0x%p\n", spu->priv1); return 0; out_unmap: spu_unmap(spu); out: pr_debug("failed to map spe %s: %d\n", spu->name, ret); return ret; } static int __init of_enumerate_spus(int (*fn)(void *data)) { int ret; struct device_node *node; unsigned int n = 0; ret = -ENODEV; for (node = of_find_node_by_type(NULL, "spe"); node; node = of_find_node_by_type(node, "spe")) { ret = fn(node); if (ret) { printk(KERN_WARNING "%s: Error initializing %s\n", __func__, node->name); break; } n++; } return ret ? ret : n; } static int __init of_create_spu(struct spu *spu, void *data) { int ret; struct device_node *spe = (struct device_node *)data; static int legacy_map = 0, legacy_irq = 0; spu->devnode = of_node_get(spe); spu->spe_id = find_spu_unit_number(spe); spu->node = of_node_to_nid(spe); if (spu->node >= MAX_NUMNODES) { printk(KERN_WARNING "SPE %s on node %d ignored," " node number too big\n", spe->full_name, spu->node); printk(KERN_WARNING "Check if CONFIG_NUMA is enabled.\n"); ret = -ENODEV; goto out; } ret = spu_map_device(spu); if (ret) { if (!legacy_map) { legacy_map = 1; printk(KERN_WARNING "%s: Legacy device tree found, " "trying to map old style\n", __func__); } ret = spu_map_device_old(spu); if (ret) { printk(KERN_ERR "Unable to map %s\n", spu->name); goto out; } } ret = spu_map_interrupts(spu, spe); if (ret) { if (!legacy_irq) { legacy_irq = 1; printk(KERN_WARNING "%s: Legacy device tree found, " "trying old style irq\n", __func__); } ret = spu_map_interrupts_old(spu, spe); if (ret) { printk(KERN_ERR "%s: could not map interrupts\n", spu->name); goto out_unmap; } } pr_debug("Using SPE %s %p %p %p %p %d\n", spu->name, spu->local_store, spu->problem, spu->priv1, spu->priv2, spu->number); goto out; out_unmap: spu_unmap(spu); out: return ret; } static int of_destroy_spu(struct spu *spu) { spu_unmap(spu); of_node_put(spu->devnode); return 0; } static void enable_spu_by_master_run(struct spu_context *ctx) { ctx->ops->master_start(ctx); } static void disable_spu_by_master_run(struct spu_context *ctx) { ctx->ops->master_stop(ctx); } /* Hardcoded affinity idxs for qs20 */ #define QS20_SPES_PER_BE 8 static int qs20_reg_idxs[QS20_SPES_PER_BE] = { 0, 2, 4, 6, 7, 5, 3, 1 }; static int qs20_reg_memory[QS20_SPES_PER_BE] = { 1, 1, 0, 0, 0, 0, 0, 0 }; static struct spu *spu_lookup_reg(int node, u32 reg) { struct spu *spu; const u32 *spu_reg; list_for_each_entry(spu, &cbe_spu_info[node].spus, cbe_list) { spu_reg = of_get_property(spu_devnode(spu), "reg", NULL); if (*spu_reg == reg) return spu; } return NULL; } static void init_affinity_qs20_harcoded(void) { int node, i; struct spu *last_spu, *spu; u32 reg; for (node = 0; node < MAX_NUMNODES; node++) { last_spu = NULL; for (i = 0; i < QS20_SPES_PER_BE; i++) { reg = qs20_reg_idxs[i]; spu = spu_lookup_reg(node, reg); if (!spu) continue; spu->has_mem_affinity = qs20_reg_memory[reg]; if (last_spu) list_add_tail(&spu->aff_list, &last_spu->aff_list); last_spu = spu; } } } static int of_has_vicinity(void) { struct device_node *dn; for_each_node_by_type(dn, "spe") { if (of_find_property(dn, "vicinity", NULL)) { of_node_put(dn); return 1; } } return 0; } static struct spu *devnode_spu(int cbe, struct device_node *dn) { struct spu *spu; list_for_each_entry(spu, &cbe_spu_info[cbe].spus, cbe_list) if (spu_devnode(spu) == dn) return spu; return NULL; } static struct spu * neighbour_spu(int cbe, struct device_node *target, struct device_node *avoid) { struct spu *spu; struct device_node *spu_dn; const phandle *vic_handles; int lenp, i; list_for_each_entry(spu, &cbe_spu_info[cbe].spus, cbe_list) { spu_dn = spu_devnode(spu); if (spu_dn == avoid) continue; vic_handles = of_get_property(spu_dn, "vicinity", &lenp); for (i=0; i < (lenp / sizeof(phandle)); i++) { if (vic_handles[i] == target->phandle) return spu; } } return NULL; } static void init_affinity_node(int cbe) { struct spu *spu, *last_spu; struct device_node *vic_dn, *last_spu_dn; phandle avoid_ph; const phandle *vic_handles; const char *name; int lenp, i, added; last_spu = list_first_entry(&cbe_spu_info[cbe].spus, struct spu, cbe_list); avoid_ph = 0; for (added = 1; added < cbe_spu_info[cbe].n_spus; added++) { last_spu_dn = spu_devnode(last_spu); vic_handles = of_get_property(last_spu_dn, "vicinity", &lenp); /* * Walk through each phandle in vicinity property of the spu * (tipically two vicinity phandles per spe node) */ for (i = 0; i < (lenp / sizeof(phandle)); i++) { if (vic_handles[i] == avoid_ph) continue; vic_dn = of_find_node_by_phandle(vic_handles[i]); if (!vic_dn) continue; /* a neighbour might be spe, mic-tm, or bif0 */ name = of_get_property(vic_dn, "name", NULL); if (!name) continue; if (strcmp(name, "spe") == 0) { spu = devnode_spu(cbe, vic_dn); avoid_ph = last_spu_dn->phandle; } else { /* * "mic-tm" and "bif0" nodes do not have * vicinity property. So we need to find the * spe which has vic_dn as neighbour, but * skipping the one we came from (last_spu_dn) */ spu = neighbour_spu(cbe, vic_dn, last_spu_dn); if (!spu) continue; if (!strcmp(name, "mic-tm")) { last_spu->has_mem_affinity = 1; spu->has_mem_affinity = 1; } avoid_ph = vic_dn->phandle; } list_add_tail(&spu->aff_list, &last_spu->aff_list); last_spu = spu; break; } } } static void init_affinity_fw(void) { int cbe; for (cbe = 0; cbe < MAX_NUMNODES; cbe++) init_affinity_node(cbe); } static int __init init_affinity(void) { if (of_has_vicinity()) { init_affinity_fw(); } else { long root = of_get_flat_dt_root(); if (of_flat_dt_is_compatible(root, "IBM,CPBW-1.0")) init_affinity_qs20_harcoded(); else printk("No affinity configuration found\n"); } return 0; } const struct spu_management_ops spu_management_of_ops = { .enumerate_spus = of_enumerate_spus, .create_spu = of_create_spu, .destroy_spu = of_destroy_spu, .enable_spu = enable_spu_by_master_run, .disable_spu = disable_spu_by_master_run, .init_affinity = init_affinity, };