From a0ad1511d5805b95ac4c454d7904c670a1696055 Mon Sep 17 00:00:00 2001 From: Kapil Hali Date: Wed, 14 Oct 2015 13:47:00 -0400 Subject: [PATCH] ARM: BCM: Add SMP support for Broadcom NSP Add SMP support for Broadcom's Northstar Plus SoC, cpu enable method and pen_release procedures. This changes also consolidates iProc family's - BCM NSP and BCM Kona, SMP handling in a common file. Northstar Plus SoC is based on ARM Cortex-A9 revision r3p0 which requires configuration for ARM Errata 764369 for SMP. This change adds the needed configuration option. Signed-off-by: Kapil Hali --- arch/arm/mach-bcm/Makefile | 2 +- arch/arm/mach-bcm/bcm_nsp.h | 19 +++ arch/arm/mach-bcm/headsmp.S | 37 +++++ arch/arm/mach-bcm/kona_smp.c | 202 --------------------------- arch/arm/mach-bcm/platsmp.c | 326 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 383 insertions(+), 203 deletions(-) create mode 100644 arch/arm/mach-bcm/bcm_nsp.h create mode 100644 arch/arm/mach-bcm/headsmp.S delete mode 100644 arch/arm/mach-bcm/kona_smp.c create mode 100644 arch/arm/mach-bcm/platsmp.c --- a/arch/arm/mach-bcm/Makefile +++ b/arch/arm/mach-bcm/Makefile @@ -20,7 +20,7 @@ obj-$(CONFIG_ARCH_BCM_281XX) += board_bc obj-$(CONFIG_ARCH_BCM_21664) += board_bcm21664.o # BCM281XX and BCM21664 SMP support -obj-$(CONFIG_ARCH_BCM_MOBILE_SMP) += kona_smp.o +obj-$(CONFIG_ARCH_BCM_MOBILE_SMP) += platsmp.o # BCM281XX and BCM21664 L2 cache control obj-$(CONFIG_ARCH_BCM_MOBILE_L2_CACHE) += kona_l2_cache.o --- /dev/null +++ b/arch/arm/mach-bcm/bcm_nsp.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 Broadcom 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __BCM_NSP_H +#define __BCM_NSP_H + +extern void nsp_secondary_startup(void); + +#endif /* __BCM_NSP_H */ --- /dev/null +++ b/arch/arm/mach-bcm/headsmp.S @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Broadcom 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +/* + * iProc specific entry point for secondary CPUs. This provides + * a "holding pen" into which all secondary cores are held until + * we are ready for them to initialise. + */ +ENTRY(nsp_secondary_startup) + mrc p15, 0, r0, c0, c0, 5 + and r0, r0, #15 + adr r4, 1f + ldmia r4, {r5, r6} + sub r4, r4, r5 + add r6, r6, r4 +pen: ldr r7, [r6] + cmp r7, r0 + bne pen + + b secondary_startup + +1: .long . + .long pen_release + +ENDPROC(nsp_secondary_startup) --- a/arch/arm/mach-bcm/kona_smp.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2014 Broadcom Corporation - * Copyright 2014 Linaro Limited - * - * 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. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -/* Size of mapped Cortex A9 SCU address space */ -#define CORTEX_A9_SCU_SIZE 0x58 - -#define SECONDARY_TIMEOUT_NS NSEC_PER_MSEC /* 1 msec (in nanoseconds) */ -#define BOOT_ADDR_CPUID_MASK 0x3 - -/* Name of device node property defining secondary boot register location */ -#define OF_SECONDARY_BOOT "secondary-boot-reg" - -/* I/O address of register used to coordinate secondary core startup */ -static u32 secondary_boot; - -/* - * Enable the Cortex A9 Snoop Control Unit - * - * By the time this is called we already know there are multiple - * cores present. We assume we're running on a Cortex A9 processor, - * so any trouble getting the base address register or getting the - * SCU base is a problem. - * - * Return 0 if successful or an error code otherwise. - */ -static int __init scu_a9_enable(void) -{ - unsigned long config_base; - void __iomem *scu_base; - - if (!scu_a9_has_base()) { - pr_err("no configuration base address register!\n"); - return -ENXIO; - } - - /* Config base address register value is zero for uniprocessor */ - config_base = scu_a9_get_base(); - if (!config_base) { - pr_err("hardware reports only one core\n"); - return -ENOENT; - } - - scu_base = ioremap((phys_addr_t)config_base, CORTEX_A9_SCU_SIZE); - if (!scu_base) { - pr_err("failed to remap config base (%lu/%u) for SCU\n", - config_base, CORTEX_A9_SCU_SIZE); - return -ENOMEM; - } - - scu_enable(scu_base); - - iounmap(scu_base); /* That's the last we'll need of this */ - - return 0; -} - -static void __init bcm_smp_prepare_cpus(unsigned int max_cpus) -{ - static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; - struct device_node *node; - int ret; - - BUG_ON(secondary_boot); /* We're called only once */ - - /* - * This function is only called via smp_ops->smp_prepare_cpu(). - * That only happens if a "/cpus" device tree node exists - * and has an "enable-method" property that selects the SMP - * operations defined herein. - */ - node = of_find_node_by_path("/cpus"); - BUG_ON(!node); - - /* - * Our secondary enable method requires a "secondary-boot-reg" - * property to specify a register address used to request the - * ROM code boot a secondary code. If we have any trouble - * getting this we fall back to uniprocessor mode. - */ - if (of_property_read_u32(node, OF_SECONDARY_BOOT, &secondary_boot)) { - pr_err("%s: missing/invalid " OF_SECONDARY_BOOT " property\n", - node->name); - ret = -ENOENT; /* Arrange to disable SMP */ - goto out; - } - - /* - * Enable the SCU on Cortex A9 based SoCs. If -ENOENT is - * returned, the SoC reported a uniprocessor configuration. - * We bail on any other error. - */ - ret = scu_a9_enable(); -out: - of_node_put(node); - if (ret) { - /* Update the CPU present map to reflect uniprocessor mode */ - BUG_ON(ret != -ENOENT); - pr_warn("disabling SMP\n"); - init_cpu_present(&only_cpu_0); - } -} - -/* - * The ROM code has the secondary cores looping, waiting for an event. - * When an event occurs each core examines the bottom two bits of the - * secondary boot register. When a core finds those bits contain its - * own core id, it performs initialization, including computing its boot - * address by clearing the boot register value's bottom two bits. The - * core signals that it is beginning its execution by writing its boot - * address back to the secondary boot register, and finally jumps to - * that address. - * - * So to start a core executing we need to: - * - Encode the (hardware) CPU id with the bottom bits of the secondary - * start address. - * - Write that value into the secondary boot register. - * - Generate an event to wake up the secondary CPU(s). - * - Wait for the secondary boot register to be re-written, which - * indicates the secondary core has started. - */ -static int bcm_boot_secondary(unsigned int cpu, struct task_struct *idle) -{ - void __iomem *boot_reg; - phys_addr_t boot_func; - u64 start_clock; - u32 cpu_id; - u32 boot_val; - bool timeout = false; - - cpu_id = cpu_logical_map(cpu); - if (cpu_id & ~BOOT_ADDR_CPUID_MASK) { - pr_err("bad cpu id (%u > %u)\n", cpu_id, BOOT_ADDR_CPUID_MASK); - return -EINVAL; - } - - if (!secondary_boot) { - pr_err("required secondary boot register not specified\n"); - return -EINVAL; - } - - boot_reg = ioremap_nocache((phys_addr_t)secondary_boot, sizeof(u32)); - if (!boot_reg) { - pr_err("unable to map boot register for cpu %u\n", cpu_id); - return -ENOSYS; - } - - /* - * Secondary cores will start in secondary_startup(), - * defined in "arch/arm/kernel/head.S" - */ - boot_func = virt_to_phys(secondary_startup); - BUG_ON(boot_func & BOOT_ADDR_CPUID_MASK); - BUG_ON(boot_func > (phys_addr_t)U32_MAX); - - /* The core to start is encoded in the low bits */ - boot_val = (u32)boot_func | cpu_id; - writel_relaxed(boot_val, boot_reg); - - sev(); - - /* The low bits will be cleared once the core has started */ - start_clock = local_clock(); - while (!timeout && readl_relaxed(boot_reg) == boot_val) - timeout = local_clock() - start_clock > SECONDARY_TIMEOUT_NS; - - iounmap(boot_reg); - - if (!timeout) - return 0; - - pr_err("timeout waiting for cpu %u to start\n", cpu_id); - - return -ENOSYS; -} - -static struct smp_operations bcm_smp_ops __initdata = { - .smp_prepare_cpus = bcm_smp_prepare_cpus, - .smp_boot_secondary = bcm_boot_secondary, -}; -CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method", - &bcm_smp_ops); --- /dev/null +++ b/arch/arm/mach-bcm/platsmp.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * Copyright 2014 Linaro Limited + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "bcm_nsp.h" + +/* Size of mapped Cortex A9 SCU address space */ +#define CORTEX_A9_SCU_SIZE 0x58 + +#define SECONDARY_TIMEOUT_NS NSEC_PER_MSEC /* 1 msec (in nanoseconds) */ +#define BOOT_ADDR_CPUID_MASK 0x3 + +/* Name of device node property defining secondary boot register location */ +#define OF_SECONDARY_BOOT "secondary-boot-reg" + +/* I/O address of register used to coordinate secondary core startup */ +static u32 secondary_boot; + +static DEFINE_SPINLOCK(boot_lock); + +/* + * Write pen_release in a way that is guaranteed to be visible to all + * observers, irrespective of whether they're taking part in coherency + * or not. This is necessary for the hotplug code to work reliably. + */ +static void write_pen_release(int val) +{ + pen_release = val; + /* + * Ensure write to pen_release is visible to the other cores, + * here - primary core + */ + smp_wmb(); + sync_cache_w(&pen_release); +} + +/* + * Enable the Cortex A9 Snoop Control Unit + * + * By the time this is called we already know there are multiple + * cores present. We assume we're running on a Cortex A9 processor, + * so any trouble getting the base address register or getting the + * SCU base is a problem. + * + * Return 0 if successful or an error code otherwise. + */ +static int __init scu_a9_enable(void) +{ + unsigned long config_base; + void __iomem *scu_base; + + if (!scu_a9_has_base()) { + pr_err("no configuration base address register!\n"); + return -ENXIO; + } + + /* Config base address register value is zero for uniprocessor */ + config_base = scu_a9_get_base(); + if (!config_base) { + pr_err("hardware reports only one core\n"); + return -ENOENT; + } + + scu_base = ioremap((phys_addr_t)config_base, CORTEX_A9_SCU_SIZE); + if (!scu_base) { + pr_err("failed to remap config base (%lu/%u) for SCU\n", + config_base, CORTEX_A9_SCU_SIZE); + return -ENOMEM; + } + + scu_enable(scu_base); + + iounmap(scu_base); /* That's the last we'll need of this */ + + return 0; +} + +static int nsp_write_lut(void (*secondary_startup) (void)) +{ + void __iomem *sku_rom_lut; + phys_addr_t secondary_startup_phy; + + if (!secondary_boot) { + pr_warn("required secondary boot register not specified\n"); + return -EINVAL; + } + + sku_rom_lut = ioremap_nocache((phys_addr_t)secondary_boot, + sizeof(secondary_boot)); + if (!sku_rom_lut) { + pr_warn("unable to ioremap SKU-ROM LUT register\n"); + return -ENOMEM; + } + + secondary_startup_phy = virt_to_phys(secondary_startup); + BUG_ON(secondary_startup_phy > (phys_addr_t)U32_MAX); + + writel_relaxed(secondary_startup_phy, sku_rom_lut); + /* + * Ensure the write is visible to the secondary core. + */ + smp_wmb(); + + iounmap(sku_rom_lut); + + return 0; +} + +static void nsp_secondary_init(unsigned int cpu) +{ + /* + * Let the primary cpu know we are out of holding pen. + */ + write_pen_release(-1); + + /* + * Synchronise with the boot thread. + */ + spin_lock(&boot_lock); + spin_unlock(&boot_lock); +} + +static void __init bcm_smp_prepare_cpus(unsigned int max_cpus) +{ + static cpumask_t only_cpu_0 = { CPU_BITS_CPU0 }; + struct device_node *node; + int ret; + + BUG_ON(secondary_boot); /* We're called only once */ + + /* + * This function is only called via smp_ops->smp_prepare_cpu(). + * That only happens if a "/cpus" device tree node exists + * and has an "enable-method" property that selects the SMP + * operations defined herein. + */ + node = of_find_node_by_path("/cpus"); + BUG_ON(!node); + + /* + * Our secondary enable method requires a "secondary-boot-reg" + * property to specify a register address used to request the + * ROM code boot a secondary core. If we have any trouble + * getting this we fall back to uniprocessor mode. + */ + if (of_property_read_u32(node, OF_SECONDARY_BOOT, &secondary_boot)) { + pr_warn("%s: missing/invalid " OF_SECONDARY_BOOT " property\n", + node->name); + ret = -ENOENT; /* Arrange to disable SMP */ + goto out; + } + + /* + * Enable the SCU on Cortex A9 based SoCs. If -ENOENT is + * returned, the SoC reported a uniprocessor configuration. + * We bail on any other error. + */ + ret = scu_a9_enable(); +out: + of_node_put(node); + if (ret) { + /* Update the CPU present map to reflect uniprocessor mode */ + pr_warn("disabling SMP\n"); + init_cpu_present(&only_cpu_0); + } +} + +/* + * The ROM code has the secondary cores looping, waiting for an event. + * When an event occurs each core examines the bottom two bits of the + * secondary boot register. When a core finds those bits contain its + * own core id, it performs initialization, including computing its boot + * address by clearing the boot register value's bottom two bits. The + * core signals that it is beginning its execution by writing its boot + * address back to the secondary boot register, and finally jumps to + * that address. + * + * So to start a core executing we need to: + * - Encode the (hardware) CPU id with the bottom bits of the secondary + * start address. + * - Write that value into the secondary boot register. + * - Generate an event to wake up the secondary CPU(s). + * - Wait for the secondary boot register to be re-written, which + * indicates the secondary core has started. + */ +static int kona_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + void __iomem *boot_reg; + phys_addr_t boot_func; + u64 start_clock; + u32 cpu_id; + u32 boot_val; + bool timeout = false; + + cpu_id = cpu_logical_map(cpu); + if (cpu_id & ~BOOT_ADDR_CPUID_MASK) { + pr_err("bad cpu id (%u > %u)\n", cpu_id, BOOT_ADDR_CPUID_MASK); + return -EINVAL; + } + + if (!secondary_boot) { + pr_err("required secondary boot register not specified\n"); + return -EINVAL; + } + + boot_reg = ioremap_nocache((phys_addr_t)secondary_boot, sizeof(u32)); + if (!boot_reg) { + pr_err("unable to map boot register for cpu %u\n", cpu_id); + return -ENOMEM; + } + + /* + * Secondary cores will start in secondary_startup(), + * defined in "arch/arm/kernel/head.S" + */ + boot_func = virt_to_phys(secondary_startup); + BUG_ON(boot_func & BOOT_ADDR_CPUID_MASK); + BUG_ON(boot_func > (phys_addr_t)U32_MAX); + + /* The core to start is encoded in the low bits */ + boot_val = (u32)boot_func | cpu_id; + writel_relaxed(boot_val, boot_reg); + + sev(); + + /* The low bits will be cleared once the core has started */ + start_clock = local_clock(); + while (!timeout && readl_relaxed(boot_reg) == boot_val) + timeout = local_clock() - start_clock > SECONDARY_TIMEOUT_NS; + + iounmap(boot_reg); + + if (!timeout) + return 0; + + pr_err("timeout waiting for cpu %u to start\n", cpu_id); + + return -ENXIO; +} + +static int nsp_boot_secondary(unsigned int cpu, struct task_struct *idle) +{ + unsigned long timeout; + int ret; + + /* + * After wake up, secondary core branches to the startup + * address programmed at SKU ROM LUT location. + */ + ret = nsp_write_lut(nsp_secondary_startup); + if (ret) { + pr_err("unable to write startup addr to SKU ROM LUT\n"); + goto out; + } + + /* + * The secondary processor is waiting to be released from + * the holding pen - release it, then wait for it to flag + * that it has been released by resetting pen_release. + */ + spin_lock(&boot_lock); + + write_pen_release(cpu_logical_map(cpu)); + /* + * Send an Event to wake up the secondary core which is in + * WFE state. Updated pen_release should also be visible to + * the secondary core. + */ + dsb_sev(); + + timeout = jiffies + (1 * HZ); + while (time_before(jiffies, timeout)) { + /* Make sure loads on other CPU is visible */ + smp_rmb(); + if (pen_release == -1) + break; + + udelay(10); + } + + spin_unlock(&boot_lock); + + ret = pen_release != -1 ? -ENXIO : 0; + +out: + return ret; +} + +static struct smp_operations bcm_smp_ops __initdata = { + .smp_prepare_cpus = bcm_smp_prepare_cpus, + .smp_boot_secondary = kona_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_bcm281xx, "brcm,bcm11351-cpu-method", + &bcm_smp_ops); + +struct smp_operations nsp_smp_ops __initdata = { + .smp_prepare_cpus = bcm_smp_prepare_cpus, + .smp_secondary_init = nsp_secondary_init, + .smp_boot_secondary = nsp_boot_secondary, +}; +CPU_METHOD_OF_DECLARE(bcm_smp_nsp, "brcm,bcm-nsp-smp", &nsp_smp_ops);