2011-02-14 10:33:10 +03:00
/* linux/arch/arm/mach-exynos4/platsmp.c
2010-07-26 16:08:52 +04:00
*
2011-02-14 10:33:10 +03:00
* Copyright ( c ) 2010 - 2011 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
2010-07-26 16:08:52 +04:00
*
* Cloned from linux / arch / arm / mach - vexpress / platsmp . c
*
* Copyright ( C ) 2002 ARM Ltd .
* All Rights Reserved
*
* 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 .
*/
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/jiffies.h>
# include <linux/smp.h>
# include <linux/io.h>
# include <asm/cacheflush.h>
2011-04-03 16:01:30 +04:00
# include <asm/hardware/gic.h>
2010-07-26 16:08:52 +04:00
# include <asm/smp_scu.h>
# include <asm/unified.h>
# include <mach/hardware.h>
# include <mach/regs-clock.h>
2011-02-14 10:33:10 +03:00
extern void exynos4_secondary_startup ( void ) ;
2010-07-26 16:08:52 +04:00
/*
* control for which core is the next to come out of the secondary
* boot " holding pen "
*/
volatile int __cpuinitdata pen_release = - 1 ;
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
/*
* 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 ;
smp_wmb ( ) ;
__cpuc_flush_dcache_area ( ( void * ) & pen_release , sizeof ( pen_release ) ) ;
outer_clean_range ( __pa ( & pen_release ) , __pa ( & pen_release + 1 ) ) ;
}
2010-07-26 16:08:52 +04:00
static void __iomem * scu_base_addr ( void )
{
return ( void __iomem * ) ( S5P_VA_SCU ) ;
}
static DEFINE_SPINLOCK ( boot_lock ) ;
void __cpuinit platform_secondary_init ( unsigned int cpu )
{
/*
* if any interrupts are already enabled for the primary
* core ( e . g . timer irq ) , then they will not have been enabled
* for us : do so
*/
2010-12-04 19:01:03 +03:00
gic_secondary_init ( 0 ) ;
2010-07-26 16:08:52 +04:00
/*
* let the primary processor know we ' re out of the
* pen , then head off into the C entry point
*/
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
write_pen_release ( - 1 ) ;
2010-07-26 16:08:52 +04:00
/*
* Synchronise with the boot thread .
*/
spin_lock ( & boot_lock ) ;
spin_unlock ( & boot_lock ) ;
}
int __cpuinit boot_secondary ( unsigned int cpu , struct task_struct * idle )
{
unsigned long timeout ;
/*
* Set synchronisation state between this boot processor
* and the secondary one
*/
spin_lock ( & boot_lock ) ;
/*
* 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 .
*
* Note that " pen_release " is the hardware CPU ID , whereas
* " cpu " is Linux ' s internal ID .
*/
ARM: Fix subtle race in CPU pen_release hotplug code
There is a subtle race in the CPU hotplug code, where a CPU which has
been offlined can online itself before being requested, which results
in things going astray on the next online/offline cycle.
What happens in the normal online/offline/online cycle is:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads -1
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
However, as the write of -1 of pen_release is not fully flushed back to
memory, and the checking of pen_release is done with caches disabled,
this allows CPU3 the opportunity to read the old value of pen_release:
CPU0 CPU3
requests boot of CPU3
pen_release = 3
flush cache line
checks pen_release, reads 3
starts boot
pen_release = -1
... requests CPU3 offline ...
... dies ...
checks pen_release, reads 3
starts boot
pen_release = -1
requests boot of CPU3
pen_release = 3
flush cache line
Fix this by grouping the write of pen_release along with its cache line
flushing code to ensure that any update to pen_release is always pushed
out to physical memory.
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-12-18 13:53:12 +03:00
write_pen_release ( cpu ) ;
2010-07-26 16:08:52 +04:00
/*
* Send the secondary CPU a soft interrupt , thereby causing
* the boot monitor to read the system wide flags register ,
* and branch to the address found there .
*/
2011-04-03 16:01:30 +04:00
gic_raise_softirq ( cpumask_of ( cpu ) , 1 ) ;
2010-07-26 16:08:52 +04:00
timeout = jiffies + ( 1 * HZ ) ;
while ( time_before ( jiffies , timeout ) ) {
smp_rmb ( ) ;
if ( pen_release = = - 1 )
break ;
udelay ( 10 ) ;
}
/*
* now the secondary core is starting up let it run its
* calibrations , then wait for it to finish
*/
spin_unlock ( & boot_lock ) ;
return pen_release ! = - 1 ? - ENOSYS : 0 ;
}
/*
* 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 )
{
void __iomem * scu_base = scu_base_addr ( ) ;
unsigned int i , ncores ;
ncores = scu_base ? scu_get_core_count ( scu_base ) : 1 ;
/* sanity check */
if ( ncores > NR_CPUS ) {
printk ( KERN_WARNING
2011-02-14 10:33:10 +03:00
" EXYNOS4: no. of cores (%d) greater than configured "
2010-07-26 16:08:52 +04:00
" maximum of %d - clipping \n " ,
ncores , NR_CPUS ) ;
ncores = NR_CPUS ;
}
for ( i = 0 ; i < ncores ; i + + )
set_cpu_possible ( i , true ) ;
2011-04-03 16:01:30 +04:00
set_smp_cross_call ( gic_raise_softirq ) ;
2010-07-26 16:08:52 +04:00
}
2010-12-03 14:09:48 +03:00
void __init platform_smp_prepare_cpus ( unsigned int max_cpus )
2010-07-26 16:08:52 +04:00
{
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 ) ;
2010-12-03 14:09:48 +03:00
scu_enable ( scu_base_addr ( ) ) ;
2010-07-26 16:08:52 +04:00
/*
2010-12-03 14:09:48 +03:00
* Write the address of secondary startup into the
* system - wide flags register . The boot monitor waits
* until it receives a soft interrupt , and then the
* secondary CPU branches to this address .
2010-07-26 16:08:52 +04:00
*/
2011-02-14 10:33:10 +03:00
__raw_writel ( BSYM ( virt_to_phys ( exynos4_secondary_startup ) ) , S5P_VA_SYSRAM ) ;
2010-07-26 16:08:52 +04:00
}