ARC: SMP support

ARC common code to enable a SMP system + ISS provided SMP extensions.

ARC700 natively lacks SMP support, hence some of the core features are
are only enabled if SoCs have the necessary h/w pixie-dust. This
includes:
-Inter Processor Interrupts (IPI)
-Cache coherency
-load-locked/store-conditional
...

The low level exception handling would be completely broken in SMP
because we don't have hardware assisted stack switching. Thus a fair bit
of this code is repurposing the MMU_SCRATCH reg for event handler
prologues to keep them re-entrant.

Many thanks to Rajeshwar Ranga for his initial "major" contributions to
SMP Port (back in 2008), and to Noam Camus and Gilad Ben-Yossef for help
with resurrecting that in 3.2 kernel (2012).

Note that this platform code is again singleton design pattern - so
multiple SMP platforms won't build at the moment - this deficiency is
addressed in subsequent patches within this series.

Signed-off-by: Vineet Gupta <vgupta@synopsys.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Rajeshwar Ranga <rajeshwar.ranga@gmail.com>
Cc: Noam Camus <noamc@ezchip.com>
Cc: Gilad Ben-Yossef <gilad@benyossef.com>
This commit is contained in:
Vineet Gupta 2013-01-18 15:12:23 +05:30
parent 0ef88a54aa
commit 41195d236e
23 changed files with 960 additions and 2 deletions

View File

@ -116,9 +116,42 @@ config CPU_BIG_ENDIAN
help
Build kernel for Big Endian Mode of ARC CPU
config SMP
bool "Symmetric Multi-Processing (Incomplete)"
default n
select USE_GENERIC_SMP_HELPERS
help
This enables support for systems with more than one CPU. If you have
a system with only one CPU, like most personal computers, say N. If
you have a system with more than one CPU, say Y.
if SMP
config ARC_HAS_COH_CACHES
def_bool n
config ARC_HAS_COH_LLSC
def_bool n
config ARC_HAS_COH_RTSC
def_bool n
config ARC_HAS_REENTRANT_IRQ_LV2
def_bool n
endif
config NR_CPUS
int "Maximum number of CPUs (2-32)"
range 2 32
depends on SMP
default "2"
menuconfig ARC_CACHE
bool "Enable Cache Support"
default y
# if SMP, cache enabled ONLY if ARC implementation has cache coherency
depends on !SMP || ARC_HAS_COH_CACHES
if ARC_CACHE
@ -213,6 +246,8 @@ config ARC_COMPACT_IRQ_LEVELS
default n
# Timer HAS to be high priority, for any other high priority config
select ARC_IRQ3_LV2
# if SMP, LV2 enabled ONLY if ARC implementation has LV2 re-entrancy
depends on !SMP || ARC_HAS_REENTRANT_IRQ_LV2
if ARC_COMPACT_IRQ_LEVELS
@ -261,6 +296,8 @@ config ARC_HAS_RTSC
bool "Insn: RTSC (64-bit r/o cycle counter)"
default y
depends on ARC_CPU_REL_4_10
# if SMP, enable RTSC only if counter is coherent across cores
depends on !SMP || ARC_HAS_COH_RTSC
endmenu # "ARC CPU Configuration"
@ -309,7 +346,7 @@ menuconfig ARC_DBG
config ARC_DBG_TLB_PARANOIA
bool "Paranoia Checks in Low Level TLB Handlers"
depends on ARC_DBG
depends on ARC_DBG && !SMP
default n
config ARC_DBG_TLB_MISS_COUNT

View File

@ -133,3 +133,6 @@ archclean:
# Thus forcing all exten calls in this file to be long calls
export CFLAGS_decompress_inflate.o = -mmedium-calls
export CFLAGS_initramfs.o = -mmedium-calls
ifdef CONFIG_SMP
export CFLAGS_core.o = -mmedium-calls
endif

View File

@ -389,11 +389,19 @@
* to be saved again on kernel mode stack, as part of ptregs.
*-------------------------------------------------------------*/
.macro EXCPN_PROLOG_FREEUP_REG reg
#ifdef CONFIG_SMP
sr \reg, [ARC_REG_SCRATCH_DATA0]
#else
st \reg, [@ex_saved_reg1]
#endif
.endm
.macro EXCPN_PROLOG_RESTORE_REG reg
#ifdef CONFIG_SMP
lr \reg, [ARC_REG_SCRATCH_DATA0]
#else
ld \reg, [@ex_saved_reg1]
#endif
.endm
/*--------------------------------------------------------------
@ -508,7 +516,11 @@
/* restore original r9 , saved in int1_saved_reg
* It will be saved on stack in macro: SAVE_CALLER_SAVED
*/
#ifdef CONFIG_SMP
lr r9, [ARC_REG_SCRATCH_DATA0]
#else
ld r9, [@int1_saved_reg]
#endif
/* now we are ready to save the remaining context :) */
st orig_r8_IS_IRQ1, [sp, 8] /* Event Type */
@ -639,6 +651,41 @@
bmsk \reg, \reg, 7
.endm
#ifdef CONFIG_SMP
/*-------------------------------------------------
* Retrieve the current running task on this CPU
* 1. Determine curr CPU id.
* 2. Use it to index into _current_task[ ]
*/
.macro GET_CURR_TASK_ON_CPU reg
GET_CPU_ID \reg
ld.as \reg, [@_current_task, \reg]
.endm
/*-------------------------------------------------
* Save a new task as the "current" task on this CPU
* 1. Determine curr CPU id.
* 2. Use it to index into _current_task[ ]
*
* Coded differently than GET_CURR_TASK_ON_CPU (which uses LD.AS)
* because ST r0, [r1, offset] can ONLY have s9 @offset
* while LD can take s9 (4 byte insn) or LIMM (8 byte insn)
*/
.macro SET_CURR_TASK_ON_CPU tsk, tmp
GET_CPU_ID \tmp
add2 \tmp, @_current_task, \tmp
st \tsk, [\tmp]
#ifdef CONFIG_ARC_CURR_IN_REG
mov r25, \tsk
#endif
.endm
#else /* Uniprocessor implementation of macros */
.macro GET_CURR_TASK_ON_CPU reg
ld \reg, [@_current_task]
.endm
@ -650,6 +697,8 @@
#endif
.endm
#endif /* SMP / UNI */
/* ------------------------------------------------------------------
* Get the ptr to some field of Current Task at @off in task struct
* -Uses r25 for Current task ptr if that is enabled

View File

@ -147,8 +147,10 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
struct task_struct *tsk)
{
#ifndef CONFIG_SMP
/* PGD cached in MMU reg to avoid 3 mem lookups: task->mm->pgd */
write_aux_reg(ARC_REG_SCRATCH_DATA0, next->pgd);
#endif
/*
* Get a new ASID if task doesn't have a valid one. Possible when
@ -197,7 +199,9 @@ static inline void destroy_context(struct mm_struct *mm)
static inline void activate_mm(struct mm_struct *prev, struct mm_struct *next)
{
#ifndef CONFIG_SMP
write_aux_reg(ARC_REG_SCRATCH_DATA0, next->pgd);
#endif
/* Unconditionally get a new ASID */
get_new_mmu_context(next);

View File

@ -6,4 +6,13 @@
* published by the Free Software Foundation.
*/
/*
* xchg() based mutex fast path maintains a state of 0 or 1, as opposed to
* atomic dec based which can "count" any number of lock contenders.
* This ideally needs to be fixed in core, but for now switching to dec ver.
*/
#if defined(CONFIG_SMP) && (CONFIG_NR_CPUS > 2)
#include <asm-generic/mutex-dec.h>
#else
#include <asm-generic/mutex-xchg.h>
#endif

View File

@ -354,11 +354,15 @@ static inline void set_pte_at(struct mm_struct *mm, unsigned long addr,
* Thus use this macro only when you are certain that "current" is current
* e.g. when dealing with signal frame setup code etc
*/
#ifndef CONFIG_SMP
#define pgd_offset_fast(mm, addr) \
({ \
pgd_t *pgd_base = (pgd_t *) read_aux_reg(ARC_REG_SCRATCH_DATA0); \
pgd_base + pgd_index(addr); \
})
#else
#define pgd_offset_fast(mm, addr) pgd_offset(mm, addr)
#endif
extern void paging_init(void);
extern pgd_t swapper_pg_dir[] __aligned(PAGE_SIZE);

View File

@ -58,7 +58,15 @@ unsigned long thread_saved_pc(struct task_struct *t);
/* Prepare to copy thread state - unlazy all lazy status */
#define prepare_to_copy(tsk) do { } while (0)
/*
* A lot of busy-wait loops in SMP are based off of non-volatile data otherwise
* get optimised away by gcc
*/
#ifdef CONFIG_SMP
#define cpu_relax() __asm__ __volatile__ ("" : : : "memory")
#else
#define cpu_relax() do { } while (0)
#endif
#define copy_segments(tsk, mm) do { } while (0)
#define release_segments(mm) do { } while (0)

View File

@ -9,6 +9,69 @@
#ifndef __ASM_ARC_SMP_H
#define __ASM_ARC_SMP_H
#ifdef CONFIG_SMP
#include <linux/types.h>
#include <linux/init.h>
#include <linux/threads.h>
#define raw_smp_processor_id() (current_thread_info()->cpu)
/* including cpumask.h leads to cyclic deps hence this Forward declaration */
struct cpumask;
/*
* APIs provided by arch SMP code to generic code
*/
extern void arch_send_call_function_single_ipi(int cpu);
extern void arch_send_call_function_ipi_mask(const struct cpumask *mask);
/*
* APIs provided by arch SMP code to rest of arch code
*/
extern void __init smp_init_cpus(void);
extern void __init first_lines_of_secondary(void);
/*
* API expected BY platform smp code (FROM arch smp code)
*
* smp_ipi_irq_setup:
* Takes @cpu and @irq to which the arch-common ISR is hooked up
*/
extern int smp_ipi_irq_setup(int cpu, int irq);
/*
* APIs expected FROM platform smp code
*
* arc_platform_smp_cpuinfo:
* returns a string containing info for /proc/cpuinfo
*
* arc_platform_smp_init_cpu:
* Called from start_kernel_secondary to do any CPU local setup
* such as starting a timer, setting up IPI etc
*
* arc_platform_smp_wait_to_boot:
* Called from early bootup code for non-Master CPUs to "park" them
*
* arc_platform_smp_wakeup_cpu:
* Called from __cpu_up (Master CPU) to kick start another one
*
* arc_platform_ipi_send:
* Takes @cpumask to which IPI(s) would be sent.
* The actual msg-id/buffer is manager in arch-common code
*
* arc_platform_ipi_clear:
* Takes @cpu which got IPI at @irq to do any IPI clearing
*/
extern const char *arc_platform_smp_cpuinfo(void);
extern void arc_platform_smp_init_cpu(void);
extern void arc_platform_smp_wait_to_boot(int cpu);
extern void arc_platform_smp_wakeup_cpu(int cpu, unsigned long pc);
extern void arc_platform_ipi_send(const struct cpumask *callmap);
extern void arc_platform_ipi_clear(int cpu, int irq);
#endif /* CONFIG_SMP */
/*
* ARC700 doesn't support atomic Read-Modify-Write ops.
* Originally Interrupts had to be disabled around code to gaurantee atomicity.
@ -18,10 +81,52 @@
*
* (1) These insn were introduced only in 4.10 release. So for older released
* support needed.
*
* (2) In a SMP setup, the LLOCK/SCOND atomiticity across CPUs needs to be
* gaurantted by the platform (not something which core handles).
* Assuming a platform won't, SMP Linux needs to use spinlocks + local IRQ
* disabling for atomicity.
*
* However exported spinlock API is not usable due to cyclic hdr deps
* (even after system.h disintegration upstream)
* asm/bitops.h -> linux/spinlock.h -> linux/preempt.h
* -> linux/thread_info.h -> linux/bitops.h -> asm/bitops.h
*
* So the workaround is to use the lowest level arch spinlock API.
* The exported spinlock API is smart enough to be NOP for !CONFIG_SMP,
* but same is not true for ARCH backend, hence the need for 2 variants
*/
#ifndef CONFIG_ARC_HAS_LLSC
#include <linux/irqflags.h>
#ifdef CONFIG_SMP
#include <asm/spinlock.h>
extern arch_spinlock_t smp_atomic_ops_lock;
extern arch_spinlock_t smp_bitops_lock;
#define atomic_ops_lock(flags) do { \
local_irq_save(flags); \
arch_spin_lock(&smp_atomic_ops_lock); \
} while (0)
#define atomic_ops_unlock(flags) do { \
arch_spin_unlock(&smp_atomic_ops_lock); \
local_irq_restore(flags); \
} while (0)
#define bitops_lock(flags) do { \
local_irq_save(flags); \
arch_spin_lock(&smp_bitops_lock); \
} while (0)
#define bitops_unlock(flags) do { \
arch_spin_unlock(&smp_bitops_lock); \
local_irq_restore(flags); \
} while (0)
#else /* !CONFIG_SMP */
#define atomic_ops_lock(flags) local_irq_save(flags)
#define atomic_ops_unlock(flags) local_irq_restore(flags)
@ -29,6 +134,8 @@
#define bitops_lock(flags) local_irq_save(flags)
#define bitops_unlock(flags) local_irq_restore(flags)
#endif /* !CONFIG_SMP */
#endif /* !CONFIG_ARC_HAS_LLSC */
#endif

View File

@ -13,6 +13,7 @@ obj-y += signal.o traps.o sys.o troubleshoot.o stacktrace.o clk.o
obj-y += devtree.o
obj-$(CONFIG_MODULES) += arcksyms.o module.o
obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_ARC_FPU_SAVE_RESTORE) += fpu.o
CFLAGS_fpu.o += -mdpfp

View File

@ -58,7 +58,18 @@ __switch_to(struct task_struct *prev_task, struct task_struct *next_task)
* For SMP extra work to get to &_current_task[cpu]
* (open coded SET_CURR_TASK_ON_CPU)
*/
#ifndef CONFIG_SMP
"st %2, [@_current_task] \n\t"
#else
"lr r24, [identity] \n\t"
"lsr r24, r24, 8 \n\t"
"bmsk r24, r24, 7 \n\t"
"add2 r24, @_current_task, r24 \n\t"
"st %2, [r24] \n\t"
#endif
#ifdef CONFIG_ARC_CURR_IN_REG
"mov r25, %2 \n\t"
#endif
/* get ksp of incoming task from tsk->thread.ksp */
"ld.as sp, [%2, %1] \n\t"

View File

@ -232,7 +232,11 @@ ARC_EXIT handle_interrupt_level2
ARC_ENTRY handle_interrupt_level1
/* free up r9 as scratchpad */
#ifdef CONFIG_SMP
sr r9, [ARC_REG_SCRATCH_DATA0]
#else
st r9, [@int1_saved_reg]
#endif
;Which mode (user/kernel) was the system in when intr occured
lr r9, [status32_l1]

View File

@ -27,6 +27,15 @@ stext:
; Don't clobber r0-r4 yet. It might have bootloader provided info
;-------------------------------------------------------------------
#ifdef CONFIG_SMP
; Only Boot (Master) proceeds. Others wait in platform dependent way
; IDENTITY Reg [ 3 2 1 0 ]
; (cpu-id) ^^^ => Zero for UP ARC700
; => #Core-ID if SMP (Master 0)
GET_CPU_ID r5
cmp r5, 0
jnz arc_platform_smp_wait_to_boot
#endif
; Clear BSS before updating any globals
; XXX: use ZOL here
mov r5, __bss_start
@ -76,3 +85,27 @@ stext:
GET_TSK_STACK_BASE r9, sp ; r9 = tsk, sp = stack base(output)
j start_kernel ; "C" entry point
#ifdef CONFIG_SMP
;----------------------------------------------------------------
; First lines of code run by secondary before jumping to 'C'
;----------------------------------------------------------------
.section .init.text, "ax",@progbits
.type first_lines_of_secondary, @function
.globl first_lines_of_secondary
first_lines_of_secondary:
; setup per-cpu idle task as "current" on this CPU
ld r0, [@secondary_idle_tsk]
SET_CURR_TASK_ON_CPU r0, r1
; setup stack (fp, sp)
mov fp, 0
; set it's stack base to tsk->thread_info bottom
GET_TSK_STACK_BASE r0, sp
j start_kernel_secondary
#endif

View File

@ -124,6 +124,11 @@ void __init init_IRQ(void)
{
init_onchip_IRQ();
plat_init_IRQ();
#ifdef CONFIG_SMP
/* Master CPU can initialize it's side of IPI */
arc_platform_smp_init_cpu();
#endif
}
/*

View File

@ -86,6 +86,10 @@ void __init setup_arch(char **cmdline_p)
setup_processor();
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
setup_arch_memory();
unflatten_device_tree();

320
arch/arc/kernel/smp.c Normal file
View File

@ -0,0 +1,320 @@
/*
* 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.
*
* RajeshwarR: Dec 11, 2007
* -- Added support for Inter Processor Interrupts
*
* Vineetg: Nov 1st, 2007
* -- Initial Write (Borrowed heavily from ARM)
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/profile.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/percpu.h>
#include <linux/cpumask.h>
#include <linux/spinlock_types.h>
#include <linux/reboot.h>
#include <asm/processor.h>
#include <asm/setup.h>
arch_spinlock_t smp_atomic_ops_lock = __ARCH_SPIN_LOCK_UNLOCKED;
arch_spinlock_t smp_bitops_lock = __ARCH_SPIN_LOCK_UNLOCKED;
/* XXX: per cpu ? Only needed once in early seconday boot */
struct task_struct *secondary_idle_tsk;
/* Called from start_kernel */
void __init smp_prepare_boot_cpu(void)
{
}
/*
* Initialise the CPU possible map early - this describes the CPUs
* which may be present or become present in the system.
*/
void __init smp_init_cpus(void)
{
unsigned int i;
for (i = 0; i < NR_CPUS; i++)
set_cpu_possible(i, true);
}
/* called from init ( ) => process 1 */
void __init smp_prepare_cpus(unsigned int max_cpus)
{
int i;
/*
* Initialise the present map, which describes the set of CPUs
* actually populated at the present time.
*/
for (i = 0; i < max_cpus; i++)
set_cpu_present(i, true);
}
void __init smp_cpus_done(unsigned int max_cpus)
{
}
/*
* After power-up, a non Master CPU needs to wait for Master to kick start it
*
* The default implementation halts
*
* This relies on platform specific support allowing Master to directly set
* this CPU's PC (to be @first_lines_of_secondary() and kick start it.
*
* In lack of such h/w assist, platforms can override this function
* - make this function busy-spin on a token, eventually set by Master
* (from arc_platform_smp_wakeup_cpu())
* - Once token is available, jump to @first_lines_of_secondary
* (using inline asm).
*
* Alert: can NOT use stack here as it has not been determined/setup for CPU.
* If it turns out to be elaborate, it's better to code it in assembly
*
*/
void __attribute__((weak)) arc_platform_smp_wait_to_boot(int cpu)
{
/*
* As a hack for debugging - since debugger will single-step over the
* FLAG insn - wrap the halt itself it in a self loop
*/
__asm__ __volatile__(
"1: \n"
" flag 1 \n"
" b 1b \n");
}
/*
* The very first "C" code executed by secondary
* Called from asm stub in head.S
* "current"/R25 already setup by low level boot code
*/
void __cpuinit start_kernel_secondary(void)
{
struct mm_struct *mm = &init_mm;
unsigned int cpu = smp_processor_id();
/* MMU, Caches, Vector Table, Interrupts etc */
setup_processor();
atomic_inc(&mm->mm_users);
atomic_inc(&mm->mm_count);
current->active_mm = mm;
notify_cpu_starting(cpu);
set_cpu_online(cpu, true);
pr_info("## CPU%u LIVE ##: Executing Code...\n", cpu);
arc_platform_smp_init_cpu();
arc_local_timer_setup(cpu);
local_irq_enable();
preempt_disable();
cpu_idle();
}
/*
* Called from kernel_init( ) -> smp_init( ) - for each CPU
*
* At this point, Secondary Processor is "HALT"ed:
* -It booted, but was halted in head.S
* -It was configured to halt-on-reset
* So need to wake it up.
*
* Essential requirements being where to run from (PC) and stack (SP)
*/
int __cpuinit __cpu_up(unsigned int cpu, struct task_struct *idle)
{
unsigned long wait_till;
secondary_idle_tsk = idle;
pr_info("Idle Task [%d] %p", cpu, idle);
pr_info("Trying to bring up CPU%u ...\n", cpu);
arc_platform_smp_wakeup_cpu(cpu,
(unsigned long)first_lines_of_secondary);
/* wait for 1 sec after kicking the secondary */
wait_till = jiffies + HZ;
while (time_before(jiffies, wait_till)) {
if (cpu_online(cpu))
break;
}
if (!cpu_online(cpu)) {
pr_info("Timeout: CPU%u FAILED to comeup !!!\n", cpu);
return -1;
}
secondary_idle_tsk = NULL;
return 0;
}
/*
* not supported here
*/
int __init setup_profiling_timer(unsigned int multiplier)
{
return -EINVAL;
}
/*****************************************************************************/
/* Inter Processor Interrupt Handling */
/*****************************************************************************/
/*
* structures for inter-processor calls
* A Collection of single bit ipi messages
*
*/
/*
* TODO_rajesh investigate tlb message types.
* IPI Timer not needed because each ARC has an individual Interrupting Timer
*/
enum ipi_msg_type {
IPI_NOP = 0,
IPI_RESCHEDULE = 1,
IPI_CALL_FUNC,
IPI_CALL_FUNC_SINGLE,
IPI_CPU_STOP
};
struct ipi_data {
unsigned long bits;
};
static DEFINE_PER_CPU(struct ipi_data, ipi_data);
static void ipi_send_msg(const struct cpumask *callmap, enum ipi_msg_type msg)
{
unsigned long flags;
unsigned int cpu;
local_irq_save(flags);
for_each_cpu(cpu, callmap) {
struct ipi_data *ipi = &per_cpu(ipi_data, cpu);
set_bit(msg, &ipi->bits);
}
/* Call the platform specific cross-CPU call function */
arc_platform_ipi_send(callmap);
local_irq_restore(flags);
}
void smp_send_reschedule(int cpu)
{
ipi_send_msg(cpumask_of(cpu), IPI_RESCHEDULE);
}
void smp_send_stop(void)
{
struct cpumask targets;
cpumask_copy(&targets, cpu_online_mask);
cpumask_clear_cpu(smp_processor_id(), &targets);
ipi_send_msg(&targets, IPI_CPU_STOP);
}
void arch_send_call_function_single_ipi(int cpu)
{
ipi_send_msg(cpumask_of(cpu), IPI_CALL_FUNC_SINGLE);
}
void arch_send_call_function_ipi_mask(const struct cpumask *mask)
{
ipi_send_msg(mask, IPI_CALL_FUNC);
}
/*
* ipi_cpu_stop - handle IPI from smp_send_stop()
*/
static void ipi_cpu_stop(unsigned int cpu)
{
machine_halt();
}
static inline void __do_IPI(unsigned long *ops, struct ipi_data *ipi, int cpu)
{
unsigned long msg = 0;
do {
msg = find_next_bit(ops, BITS_PER_LONG, msg+1);
switch (msg) {
case IPI_RESCHEDULE:
scheduler_ipi();
break;
case IPI_CALL_FUNC:
generic_smp_call_function_interrupt();
break;
case IPI_CALL_FUNC_SINGLE:
generic_smp_call_function_single_interrupt();
break;
case IPI_CPU_STOP:
ipi_cpu_stop(cpu);
break;
}
} while (msg < BITS_PER_LONG);
}
/*
* arch-common ISR to handle for inter-processor interrupts
* Has hooks for platform specific IPI
*/
irqreturn_t do_IPI(int irq, void *dev_id)
{
int cpu = smp_processor_id();
struct ipi_data *ipi = &per_cpu(ipi_data, cpu);
unsigned long ops;
arc_platform_ipi_clear(cpu, irq);
/*
* XXX: is this loop really needed
* And do we need to move ipi_clean inside
*/
while ((ops = xchg(&ipi->bits, 0)) != 0)
__do_IPI(&ops, ipi, cpu);
return IRQ_HANDLED;
}
/*
* API called by platform code to hookup arch-common ISR to their IPI IRQ
*/
static DEFINE_PER_CPU(int, ipi_dev);
int smp_ipi_irq_setup(int cpu, int irq)
{
int *dev_id = &per_cpu(ipi_dev, smp_processor_id());
return request_percpu_irq(irq, do_IPI, "IPI Interrupt", dev_id);
}

View File

@ -474,6 +474,12 @@ void __init arc_mmu_init(void)
/* Enable the MMU */
write_aux_reg(ARC_REG_PID, MMU_ENABLE);
/* In smp we use this reg for interrupt 1 scratch */
#ifndef CONFIG_SMP
/* swapper_pg_dir is the pgd for the kernel, used by vmalloc */
write_aux_reg(ARC_REG_SCRATCH_DATA0, swapper_pg_dir);
#endif
}
/*

View File

@ -57,9 +57,15 @@
.global ex_saved_reg1
.align 1 << L1_CACHE_SHIFT ; IMP: Must be Cache Line aligned
.type ex_saved_reg1, @object
#ifdef CONFIG_SMP
.size ex_saved_reg1, (CONFIG_NR_CPUS << L1_CACHE_SHIFT)
ex_saved_reg1:
.zero (CONFIG_NR_CPUS << L1_CACHE_SHIFT)
#else
.size ex_saved_reg1, 16
ex_saved_reg1:
.zero 16
#endif
;============================================================================
; Troubleshooting Stuff
@ -116,7 +122,13 @@ ex_saved_reg1:
lr r2, [efa]
#ifndef CONFIG_SMP
lr r1, [ARC_REG_SCRATCH_DATA0] ; current pgd
#else
GET_CURR_TASK_ON_CPU r1
ld r1, [r1, TASK_ACT_MM]
ld r1, [r1, MM_PGD]
#endif
lsr r0, r2, PGDIR_SHIFT ; Bits for indexing into PGD
ld.as r1, [r1, r0] ; PGD entry corresp to faulting addr
@ -192,12 +204,28 @@ ex_saved_reg1:
; ".size ex_saved_reg1, 16"
; [All of this dance is to avoid stack switching for each TLB Miss, since we
; only need to save only a handful of regs, as opposed to complete reg file]
;
; For ARC700 SMP, the "global" obviously can't be used for free up the FIRST
; core reg as it will not be SMP safe.
; Thus scratch AUX reg is used (and no longer used to cache task PGD).
; To save the rest of 3 regs - per cpu, the global is made "per-cpu".
; Epilogue thus has to locate the "per-cpu" storage for regs.
; To avoid cache line bouncing the per-cpu global is aligned/sized per
; L1_CACHE_SHIFT, despite fundamentally needing to be 12 bytes only. Hence
; ".size ex_saved_reg1, (CONFIG_NR_CPUS << L1_CACHE_SHIFT)"
; As simple as that....
.macro TLBMISS_FREEUP_REGS
#ifdef CONFIG_SMP
sr r0, [ARC_REG_SCRATCH_DATA0] ; freeup r0 to code with
GET_CPU_ID r0 ; get to per cpu scratch mem,
lsl r0, r0, L1_CACHE_SHIFT ; cache line wide per cpu
add r0, @ex_saved_reg1, r0
#else
st r0, [@ex_saved_reg1]
mov_s r0, @ex_saved_reg1
#endif
st_s r1, [r0, 4]
st_s r2, [r0, 8]
st_s r3, [r0, 12]
@ -210,11 +238,21 @@ ex_saved_reg1:
;-----------------------------------------------------------------
.macro TLBMISS_RESTORE_REGS
#ifdef CONFIG_SMP
GET_CPU_ID r0 ; get to per cpu scratch mem
lsl r0, r0, L1_CACHE_SHIFT ; each is cache line wide
add r0, @ex_saved_reg1, r0
ld_s r3, [r0,12]
ld_s r2, [r0, 8]
ld_s r1, [r0, 4]
lr r0, [ARC_REG_SCRATCH_DATA0]
#else
mov_s r0, @ex_saved_reg1
ld_s r3, [r0,12]
ld_s r2, [r0, 8]
ld_s r1, [r0, 4]
ld_s r0, [r0]
#endif
.endm
.section .text, "ax",@progbits ;Fast Path Code, candidate for ICCM

View File

@ -13,6 +13,7 @@ choice
config ARC_BOARD_ANGEL4
bool "ARC Angel4"
select ISS_SMP_EXTN if SMP
help
ARC Angel4 FPGA Ref Platform (Xilinx Virtex Based)
@ -21,6 +22,19 @@ config ARC_BOARD_ML509
help
ARC ML509 FPGA Ref Platform (Xilinx Virtex-5 Based)
config ISS_SMP_EXTN
bool "ARC SMP Extensions (ISS Models only)"
default n
depends on SMP
select ARC_HAS_COH_RTSC
help
SMP Extensions to ARC700, in a "simulation only" Model, supported in
ARC ISS (Instruction Set Simulator).
The SMP extensions include:
-IDU (Interrupt Distribution Unit)
-XTL (To enable CPU start/stop/set-PC for another CPU)
It doesn't provide coherent Caches and/or Atomic Ops (LLOCK/SCOND)
endchoice
config ARC_SERIAL_BAUD

View File

@ -7,3 +7,4 @@
#
obj-y := platform.o irq.o
obj-$(CONFIG_SMP) += smp.o

View File

@ -12,7 +12,11 @@
#ifndef __PLAT_IRQ_H
#define __PLAT_IRQ_H
#define NR_IRQS 16
#ifdef CONFIG_SMP
#define NR_IRQS 32
#else
#define NR_IRQS 16
#endif
#define UART0_IRQ 5
#define UART1_IRQ 10
@ -24,4 +28,8 @@
#define PCI_IRQ 14
#define PS2_IRQ 15
#ifdef CONFIG_SMP
#define IDU_INTERRUPT_0 16
#endif
#endif

View File

@ -0,0 +1,115 @@
/*
* 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.
*
* Rajeshwar Ranga: Interrupt Distribution Unit API's
*/
#ifndef __PLAT_ARCFPGA_SMP_H
#define __PLAT_ARCFPGA_SMP_H
#ifdef CONFIG_SMP
#include <linux/types.h>
#include <asm/arcregs.h>
#define ARC_AUX_IDU_REG_CMD 0x2000
#define ARC_AUX_IDU_REG_PARAM 0x2001
#define ARC_AUX_XTL_REG_CMD 0x2002
#define ARC_AUX_XTL_REG_PARAM 0x2003
#define ARC_REG_MP_BCR 0x2021
#define ARC_XTL_CMD_WRITE_PC 0x04
#define ARC_XTL_CMD_CLEAR_HALT 0x02
/*
* Build Configuration Register which identifies the sub-components
*/
struct bcr_mp {
#ifdef CONFIG_CPU_BIG_ENDIAN
unsigned int mp_arch:16, pad:5, sdu:1, idu:1, scu:1, ver:8;
#else
unsigned int ver:8, scu:1, idu:1, sdu:1, pad:5, mp_arch:16;
#endif
};
/* IDU supports 256 common interrupts */
#define NR_IDU_IRQS 256
/*
* The Aux Regs layout is same bit-by-bit in both BE/LE modes.
* However when casted as a bitfield encoded "C" struct, gcc treats it as
* memory, generating different code for BE/LE, requiring strcture adj (see
* include/asm/arcregs.h)
*
* However when manually "carving" the value for a Aux, no special handling
* of BE is needed because of the property discribed above
*/
#define IDU_SET_COMMAND(irq, cmd) \
do { \
uint32_t __val; \
__val = (((irq & 0xFF) << 8) | (cmd & 0xFF)); \
write_aux_reg(ARC_AUX_IDU_REG_CMD, __val); \
} while (0)
#define IDU_SET_PARAM(par) write_aux_reg(ARC_AUX_IDU_REG_PARAM, par)
#define IDU_GET_PARAM() read_aux_reg(ARC_AUX_IDU_REG_PARAM)
/* IDU Commands */
#define IDU_DISABLE 0x00
#define IDU_ENABLE 0x01
#define IDU_IRQ_CLEAR 0x02
#define IDU_IRQ_ASSERT 0x03
#define IDU_IRQ_WMODE 0x04
#define IDU_IRQ_STATUS 0x05
#define IDU_IRQ_ACK 0x06
#define IDU_IRQ_PEND 0x07
#define IDU_IRQ_RMODE 0x08
#define IDU_IRQ_WBITMASK 0x09
#define IDU_IRQ_RBITMASK 0x0A
#define idu_enable() IDU_SET_COMMAND(0, IDU_ENABLE)
#define idu_disable() IDU_SET_COMMAND(0, IDU_DISABLE)
#define idu_irq_assert(irq) IDU_SET_COMMAND((irq), IDU_IRQ_ASSERT)
#define idu_irq_clear(irq) IDU_SET_COMMAND((irq), IDU_IRQ_CLEAR)
/* IDU Interrupt Mode - Destination Encoding */
#define IDU_IRQ_MOD_DISABLE 0x00
#define IDU_IRQ_MOD_ROUND_RECP 0x01
#define IDU_IRQ_MOD_TCPU_FIRSTRECP 0x02
#define IDU_IRQ_MOD_TCPU_ALLRECP 0x03
/* IDU Interrupt Mode - Triggering Mode */
#define IDU_IRQ_MODE_LEVEL_TRIG 0x00
#define IDU_IRQ_MODE_PULSE_TRIG 0x01
#define IDU_IRQ_MODE_PARAM(dest_mode, trig_mode) \
(((trig_mode & 0x01) << 15) | (dest_mode & 0xFF))
struct idu_irq_config {
uint8_t irq;
uint8_t dest_mode;
uint8_t trig_mode;
};
struct idu_irq_status {
uint8_t irq;
bool enabled;
bool status;
bool ack;
bool pend;
uint8_t next_rr;
};
extern void idu_irq_set_tgtcpu(uint8_t irq, uint32_t mask);
extern void idu_irq_set_mode(uint8_t irq, uint8_t dest_mode, uint8_t trig_mode);
#endif /* CONFIG_SMP */
#endif

View File

@ -9,7 +9,17 @@
*/
#include <linux/interrupt.h>
#include <asm/irq.h>
void __init plat_init_IRQ(void)
{
/*
* SMP Hack because UART IRQ hardwired to cpu0 (boot-cpu) but if the
* request_irq() comes from any other CPU, the low level IRQ unamsking
* essential for getting Interrupts won't be enabled on cpu0, locking
* up the UART state machine.
*/
#ifdef CONFIG_SMP
arch_unmask_irq(UART0_IRQ);
#endif
}

167
arch/arc/plat-arcfpga/smp.c Normal file
View File

@ -0,0 +1,167 @@
/*
* 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 <asm/irq.h>
#include <plat/smp.h>
static char smp_cpuinfo_buf[128];
/*
*-------------------------------------------------------------------
* Platform specific callbacks expected by arch SMP code
*-------------------------------------------------------------------
*/
const char *arc_platform_smp_cpuinfo(void)
{
#define IS_AVAIL1(var, str) ((var) ? str : "")
struct bcr_mp mp;
READ_BCR(ARC_REG_MP_BCR, mp);
sprintf(smp_cpuinfo_buf, "Extn [700-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"));
return smp_cpuinfo_buf;
}
/*
* Master kick starting another CPU
*/
void arc_platform_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)));
}
/*
* 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 arc_platform_smp_init_cpu(void)
{
int cpu = smp_processor_id();
/* 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);
}
void arc_platform_ipi_send(const struct cpumask *callmap)
{
unsigned int cpu;
for_each_cpu(cpu, callmap)
idu_irq_assert(cpu);
}
void arc_platform_ipi_clear(int cpu, int irq)
{
idu_irq_clear(IDU_INTERRUPT_0 + cpu);
}
/*
*-------------------------------------------------------------------
* 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);
}