/* * ARC700 Simulation-only Extensions for SMP * * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Vineet Gupta - 2012 : split off arch common and plat specific SMP * Rajeshwar Ranga - 2007 : Interrupt Distribution Unit API's */ #include <linux/smp.h> #include <linux/irq.h> #include <plat/smp.h> #define IDU_INTERRUPT_0 16 static char smp_cpuinfo_buf[128]; /* *------------------------------------------------------------------- * Platform specific callbacks expected by arch SMP code *------------------------------------------------------------------- */ /* * Master kick starting another CPU */ static void iss_model_smp_wakeup_cpu(int cpu, unsigned long pc) { /* setup the start PC */ write_aux_reg(ARC_AUX_XTL_REG_PARAM, pc); /* Trigger WRITE_PC cmd for this cpu */ write_aux_reg(ARC_AUX_XTL_REG_CMD, (ARC_XTL_CMD_WRITE_PC | (cpu << 8))); /* Take the cpu out of Halt */ write_aux_reg(ARC_AUX_XTL_REG_CMD, (ARC_XTL_CMD_CLEAR_HALT | (cpu << 8))); } static inline int get_hw_config_num_irq(void) { uint32_t val = read_aux_reg(ARC_REG_VECBASE_BCR); switch (val & 0x03) { case 0: return 16; case 1: return 32; case 2: return 8; default: return 0; } return 0; } /* * Any SMP specific init any CPU does when it comes up. * Here we setup the CPU to enable Inter-Processor-Interrupts * Called for each CPU * -Master : init_IRQ() * -Other(s) : start_kernel_secondary() */ void iss_model_init_smp(unsigned int cpu) { /* Check if CPU is configured for more than 16 interrupts */ if (NR_IRQS <= 16 || get_hw_config_num_irq() <= 16) panic("[arcfpga] IRQ system can't support IDU IPI\n"); idu_disable(); /**************************************************************** * IDU provides a set of Common IRQs, each of which can be dynamically * attached to (1|many|all) CPUs. * The Common IRQs [0-15] are mapped as CPU pvt [16-31] * * Here we use a simple 1:1 mapping: * A CPU 'x' is wired to Common IRQ 'x'. * So an IDU ASSERT on IRQ 'x' will trigger Interupt on CPU 'x', which * makes up for our simple IPI plumbing. * * TBD: Have a dedicated multicast IRQ for sending IPIs to all CPUs * w/o having to do one-at-a-time ******************************************************************/ /* * Claim an IRQ which would trigger IPI on this CPU. * In IDU parlance it involves setting up a cpu bitmask for the IRQ * The bitmap here contains only 1 CPU (self). */ idu_irq_set_tgtcpu(cpu, 0x1 << cpu); /* Set the IRQ destination to use the bitmask above */ idu_irq_set_mode(cpu, 7, /* XXX: IDU_IRQ_MOD_TCPU_ALLRECP: ISS bug */ IDU_IRQ_MODE_PULSE_TRIG); idu_enable(); /* Attach the arch-common IPI ISR to our IDU IRQ */ smp_ipi_irq_setup(cpu, IDU_INTERRUPT_0 + cpu); } static void iss_model_ipi_send(int cpu) { idu_irq_assert(cpu); } static void iss_model_ipi_clear(int irq) { idu_irq_clear(IDU_INTERRUPT_0 + smp_processor_id()); } void iss_model_init_early_smp(void) { #define IS_AVAIL1(var, str) ((var) ? str : "") struct bcr_mp mp; READ_BCR(ARC_REG_MP_BCR, mp); sprintf(smp_cpuinfo_buf, "Extn [ISS-SMP]: v%d, arch(%d) %s %s %s\n", mp.ver, mp.mp_arch, IS_AVAIL1(mp.scu, "SCU"), IS_AVAIL1(mp.idu, "IDU"), IS_AVAIL1(mp.sdu, "SDU")); plat_smp_ops.info = smp_cpuinfo_buf; plat_smp_ops.cpu_kick = iss_model_smp_wakeup_cpu; plat_smp_ops.ipi_send = iss_model_ipi_send; plat_smp_ops.ipi_clear = iss_model_ipi_clear; } /* *------------------------------------------------------------------- * Low level Platform IPI Providers *------------------------------------------------------------------- */ /* Set the Mode for the Common IRQ */ void idu_irq_set_mode(uint8_t irq, uint8_t dest_mode, uint8_t trig_mode) { uint32_t par = IDU_IRQ_MODE_PARAM(dest_mode, trig_mode); IDU_SET_PARAM(par); IDU_SET_COMMAND(irq, IDU_IRQ_WMODE); } /* Set the target cpu Bitmask for Common IRQ */ void idu_irq_set_tgtcpu(uint8_t irq, uint32_t mask) { IDU_SET_PARAM(mask); IDU_SET_COMMAND(irq, IDU_IRQ_WBITMASK); } /* Get the Interrupt Acknowledged status for IRQ (as CPU Bitmask) */ bool idu_irq_get_ack(uint8_t irq) { uint32_t val; IDU_SET_COMMAND(irq, IDU_IRQ_ACK); val = IDU_GET_PARAM(); return val & (1 << irq); } /* * Get the Interrupt Pending status for IRQ (as CPU Bitmask) * -Pending means CPU has not yet noticed the IRQ (e.g. disabled) * -After Interrupt has been taken, the IPI expcitily needs to be * cleared, to be acknowledged. */ bool idu_irq_get_pend(uint8_t irq) { uint32_t val; IDU_SET_COMMAND(irq, IDU_IRQ_PEND); val = IDU_GET_PARAM(); return val & (1 << irq); }