2010-02-11 21:56:07 +00:00
/*
* 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/smp.h>
# include <linux/io.h>
2012-02-24 09:18:14 +00:00
# include <linux/of_fdt.h>
# include <asm/smp_scu.h>
# include <asm/hardware/gic.h>
# include <asm/mach/map.h>
2010-02-11 21:56:07 +00:00
# include <mach/motherboard.h>
2011-09-08 13:15:22 +01:00
# include <plat/platsmp.h>
2010-02-11 21:56:07 +00:00
2011-09-08 13:15:22 +01:00
# include "core.h"
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 10:53:12 +00:00
2012-02-24 09:18:14 +00:00
# if defined(CONFIG_OF)
static enum {
GENERIC_SCU ,
CORTEX_A9_SCU ,
} vexpress_dt_scu __initdata = GENERIC_SCU ;
static struct map_desc vexpress_dt_cortex_a9_scu_map __initdata = {
. virtual = V2T_PERIPH ,
/* .pfn set in vexpress_dt_init_cortex_a9_scu() */
. length = SZ_128 ,
. type = MT_DEVICE ,
} ;
static void * vexpress_dt_cortex_a9_scu_base __initdata ;
const static char * vexpress_dt_cortex_a9_match [ ] __initconst = {
" arm,cortex-a5-scu " ,
" arm,cortex-a9-scu " ,
NULL
} ;
static int __init vexpress_dt_find_scu ( unsigned long node ,
const char * uname , int depth , void * data )
{
if ( of_flat_dt_match ( node , vexpress_dt_cortex_a9_match ) ) {
phys_addr_t phys_addr ;
__be32 * reg = of_get_flat_dt_prop ( node , " reg " , NULL ) ;
if ( WARN_ON ( ! reg ) )
return - EINVAL ;
phys_addr = be32_to_cpup ( reg ) ;
vexpress_dt_scu = CORTEX_A9_SCU ;
vexpress_dt_cortex_a9_scu_map . pfn = __phys_to_pfn ( phys_addr ) ;
iotable_init ( & vexpress_dt_cortex_a9_scu_map , 1 ) ;
vexpress_dt_cortex_a9_scu_base = ioremap ( phys_addr , SZ_256 ) ;
if ( WARN_ON ( ! vexpress_dt_cortex_a9_scu_base ) )
return - EFAULT ;
}
return 0 ;
}
void __init vexpress_dt_smp_map_io ( void )
{
if ( initial_boot_params )
WARN_ON ( of_scan_flat_dt ( vexpress_dt_find_scu , NULL ) ) ;
}
static int __init vexpress_dt_cpus_num ( unsigned long node , const char * uname ,
int depth , void * data )
{
static int prev_depth = - 1 ;
static int nr_cpus = - 1 ;
if ( prev_depth > depth & & nr_cpus > 0 )
return nr_cpus ;
if ( nr_cpus < 0 & & strcmp ( uname , " cpus " ) = = 0 )
nr_cpus = 0 ;
if ( nr_cpus > = 0 ) {
const char * device_type = of_get_flat_dt_prop ( node ,
" device_type " , NULL ) ;
if ( device_type & & strcmp ( device_type , " cpu " ) = = 0 )
nr_cpus + + ;
}
prev_depth = depth ;
return 0 ;
}
static void __init vexpress_dt_smp_init_cpus ( void )
{
int ncores = 0 , i ;
switch ( vexpress_dt_scu ) {
case GENERIC_SCU :
ncores = of_scan_flat_dt ( vexpress_dt_cpus_num , NULL ) ;
break ;
case CORTEX_A9_SCU :
ncores = scu_get_core_count ( vexpress_dt_cortex_a9_scu_base ) ;
break ;
default :
WARN_ON ( 1 ) ;
break ;
}
if ( ncores < 2 )
return ;
if ( ncores > nr_cpu_ids ) {
pr_warn ( " SMP: %u cores greater than maximum (%u), clipping \n " ,
ncores , nr_cpu_ids ) ;
ncores = nr_cpu_ids ;
}
for ( i = 0 ; i < ncores ; + + i )
set_cpu_possible ( i , true ) ;
set_smp_cross_call ( gic_raise_softirq ) ;
}
static void __init vexpress_dt_smp_prepare_cpus ( unsigned int max_cpus )
{
int i ;
switch ( vexpress_dt_scu ) {
case GENERIC_SCU :
for ( i = 0 ; i < max_cpus ; i + + )
set_cpu_present ( i , true ) ;
break ;
case CORTEX_A9_SCU :
scu_enable ( vexpress_dt_cortex_a9_scu_base ) ;
break ;
default :
WARN_ON ( 1 ) ;
break ;
}
}
# else
static void __init vexpress_dt_smp_init_cpus ( void )
{
WARN_ON ( 1 ) ;
}
void __init vexpress_dt_smp_prepare_cpus ( unsigned int max_cpus )
{
WARN_ON ( 1 ) ;
}
# endif
2010-02-11 21:56:07 +00:00
/*
* Initialise the CPU possible map early - this describes the CPUs
* which may be present or become present in the system .
*/
2011-09-08 13:15:22 +01:00
static void __init vexpress_smp_init_cpus ( void )
2010-02-11 21:56:07 +00:00
{
2012-02-24 09:18:14 +00:00
if ( ct_desc )
ct_desc - > init_cpu_map ( ) ;
else
vexpress_dt_smp_init_cpus ( ) ;
2010-02-11 21:56:07 +00:00
}
2011-09-08 13:15:22 +01:00
static void __init vexpress_smp_prepare_cpus ( unsigned int max_cpus )
2010-02-11 21:56:07 +00:00
{
/*
* Initialise the present map , which describes the set of CPUs
* actually populated at the present time .
*/
2012-02-24 09:18:14 +00:00
if ( ct_desc )
ct_desc - > smp_enable ( max_cpus ) ;
else
vexpress_dt_smp_prepare_cpus ( max_cpus ) ;
2010-12-03 11:09:48 +00:00
2010-02-11 21:56:07 +00:00
/*
2010-12-03 11:09:48 +00: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-02-11 21:56:07 +00:00
*/
2012-01-25 15:37:29 +00:00
v2m_flags_set ( virt_to_phys ( versatile_secondary_startup ) ) ;
2010-02-11 21:56:07 +00:00
}
2011-09-08 13:15:22 +01:00
struct smp_operations __initdata vexpress_smp_ops = {
. smp_init_cpus = vexpress_smp_init_cpus ,
. smp_prepare_cpus = vexpress_smp_prepare_cpus ,
. smp_secondary_init = versatile_secondary_init ,
. smp_boot_secondary = versatile_boot_secondary ,
# ifdef CONFIG_HOTPLUG_CPU
. cpu_die = vexpress_cpu_die ,
# endif
} ;