mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 22:02:28 +00:00
c473b2c6f6
__get_cpu_var() is used for multiple purposes in the kernel source. One of them is address calculation via the form &__get_cpu_var(x). This calculates the address for the instance of the percpu variable of the current processor based on an offset. Other use cases are for storing and retrieving data from the current processors percpu area. __get_cpu_var() can be used as an lvalue when writing data or on the right side of an assignment. __get_cpu_var() is defined as : #define __get_cpu_var(var) (*this_cpu_ptr(&(var))) __get_cpu_var() always only does an address determination. However, store and retrieve operations could use a segment prefix (or global register on other platforms) to avoid the address calculation. this_cpu_write() and this_cpu_read() can directly take an offset into a percpu area and use optimized assembly code to read and write per cpu variables. This patch converts __get_cpu_var into either an explicit address calculation using this_cpu_ptr() or into a use of this_cpu operations that use the offset. Thereby address calculations are avoided and less registers are used when code is generated. At the end of the patch set all uses of __get_cpu_var have been removed so the macro is removed too. The patch set includes passes over all arches as well. Once these operations are used throughout then specialized macros can be defined in non -x86 arches as well in order to optimize per cpu access by f.e. using a global register that may be set to the per cpu base. Transformations done to __get_cpu_var() 1. Determine the address of the percpu instance of the current processor. DEFINE_PER_CPU(int, y); int *x = &__get_cpu_var(y); Converts to int *x = this_cpu_ptr(&y); 2. Same as #1 but this time an array structure is involved. DEFINE_PER_CPU(int, y[20]); int *x = __get_cpu_var(y); Converts to int *x = this_cpu_ptr(y); 3. Retrieve the content of the current processors instance of a per cpu variable. DEFINE_PER_CPU(int, y); int x = __get_cpu_var(y) Converts to int x = __this_cpu_read(y); 4. Retrieve the content of a percpu struct DEFINE_PER_CPU(struct mystruct, y); struct mystruct x = __get_cpu_var(y); Converts to memcpy(&x, this_cpu_ptr(&y), sizeof(x)); 5. Assignment to a per cpu variable DEFINE_PER_CPU(int, y) __get_cpu_var(y) = x; Converts to __this_cpu_write(y, x); 6. Increment/Decrement etc of a per cpu variable DEFINE_PER_CPU(int, y); __get_cpu_var(y)++ Converts to __this_cpu_inc(y) Signed-off-by: Christoph Lameter <cl@linux.com> Tested-by: Geert Uytterhoeven <geert@linux-m68k.org> [compilation only] Cc: Paul Mundt <lethal@linux-sh.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
470 lines
9.6 KiB
C
470 lines
9.6 KiB
C
/*
|
|
* arch/sh/kernel/smp.c
|
|
*
|
|
* SMP support for the SuperH processors.
|
|
*
|
|
* Copyright (C) 2002 - 2010 Paul Mundt
|
|
* Copyright (C) 2006 - 2007 Akio Idehara
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/cache.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/atomic.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/smp.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/sections.h>
|
|
#include <asm/setup.h>
|
|
|
|
int __cpu_number_map[NR_CPUS]; /* Map physical to logical */
|
|
int __cpu_logical_map[NR_CPUS]; /* Map logical to physical */
|
|
|
|
struct plat_smp_ops *mp_ops = NULL;
|
|
|
|
/* State of each CPU */
|
|
DEFINE_PER_CPU(int, cpu_state) = { 0 };
|
|
|
|
void register_smp_ops(struct plat_smp_ops *ops)
|
|
{
|
|
if (mp_ops)
|
|
printk(KERN_WARNING "Overriding previously set SMP ops\n");
|
|
|
|
mp_ops = ops;
|
|
}
|
|
|
|
static inline void smp_store_cpu_info(unsigned int cpu)
|
|
{
|
|
struct sh_cpuinfo *c = cpu_data + cpu;
|
|
|
|
memcpy(c, &boot_cpu_data, sizeof(struct sh_cpuinfo));
|
|
|
|
c->loops_per_jiffy = loops_per_jiffy;
|
|
}
|
|
|
|
void __init smp_prepare_cpus(unsigned int max_cpus)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
|
|
init_new_context(current, &init_mm);
|
|
current_thread_info()->cpu = cpu;
|
|
mp_ops->prepare_cpus(max_cpus);
|
|
|
|
#ifndef CONFIG_HOTPLUG_CPU
|
|
init_cpu_present(cpu_possible_mask);
|
|
#endif
|
|
}
|
|
|
|
void __init smp_prepare_boot_cpu(void)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
|
|
__cpu_number_map[0] = cpu;
|
|
__cpu_logical_map[0] = cpu;
|
|
|
|
set_cpu_online(cpu, true);
|
|
set_cpu_possible(cpu, true);
|
|
|
|
per_cpu(cpu_state, cpu) = CPU_ONLINE;
|
|
}
|
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
void native_cpu_die(unsigned int cpu)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
smp_rmb();
|
|
if (per_cpu(cpu_state, cpu) == CPU_DEAD) {
|
|
if (system_state == SYSTEM_RUNNING)
|
|
pr_info("CPU %u is now offline\n", cpu);
|
|
|
|
return;
|
|
}
|
|
|
|
msleep(100);
|
|
}
|
|
|
|
pr_err("CPU %u didn't die...\n", cpu);
|
|
}
|
|
|
|
int native_cpu_disable(unsigned int cpu)
|
|
{
|
|
return cpu == 0 ? -EPERM : 0;
|
|
}
|
|
|
|
void play_dead_common(void)
|
|
{
|
|
idle_task_exit();
|
|
irq_ctx_exit(raw_smp_processor_id());
|
|
mb();
|
|
|
|
__this_cpu_write(cpu_state, CPU_DEAD);
|
|
local_irq_disable();
|
|
}
|
|
|
|
void native_play_dead(void)
|
|
{
|
|
play_dead_common();
|
|
}
|
|
|
|
int __cpu_disable(void)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
int ret;
|
|
|
|
ret = mp_ops->cpu_disable(cpu);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Take this CPU offline. Once we clear this, we can't return,
|
|
* and we must not schedule until we're ready to give up the cpu.
|
|
*/
|
|
set_cpu_online(cpu, false);
|
|
|
|
/*
|
|
* OK - migrate IRQs away from this CPU
|
|
*/
|
|
migrate_irqs();
|
|
|
|
/*
|
|
* Stop the local timer for this CPU.
|
|
*/
|
|
local_timer_stop(cpu);
|
|
|
|
/*
|
|
* Flush user cache and TLB mappings, and then remove this CPU
|
|
* from the vm mask set of all processes.
|
|
*/
|
|
flush_cache_all();
|
|
local_flush_tlb_all();
|
|
|
|
clear_tasks_mm_cpumask(cpu);
|
|
|
|
return 0;
|
|
}
|
|
#else /* ... !CONFIG_HOTPLUG_CPU */
|
|
int native_cpu_disable(unsigned int cpu)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
void native_cpu_die(unsigned int cpu)
|
|
{
|
|
/* We said "no" in __cpu_disable */
|
|
BUG();
|
|
}
|
|
|
|
void native_play_dead(void)
|
|
{
|
|
BUG();
|
|
}
|
|
#endif
|
|
|
|
asmlinkage void start_secondary(void)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
struct mm_struct *mm = &init_mm;
|
|
|
|
enable_mmu();
|
|
atomic_inc(&mm->mm_count);
|
|
atomic_inc(&mm->mm_users);
|
|
current->active_mm = mm;
|
|
enter_lazy_tlb(mm, current);
|
|
local_flush_tlb_all();
|
|
|
|
per_cpu_trap_init();
|
|
|
|
preempt_disable();
|
|
|
|
notify_cpu_starting(cpu);
|
|
|
|
local_irq_enable();
|
|
|
|
/* Enable local timers */
|
|
local_timer_setup(cpu);
|
|
calibrate_delay();
|
|
|
|
smp_store_cpu_info(cpu);
|
|
|
|
set_cpu_online(cpu, true);
|
|
per_cpu(cpu_state, cpu) = CPU_ONLINE;
|
|
|
|
cpu_startup_entry(CPUHP_ONLINE);
|
|
}
|
|
|
|
extern struct {
|
|
unsigned long sp;
|
|
unsigned long bss_start;
|
|
unsigned long bss_end;
|
|
void *start_kernel_fn;
|
|
void *cpu_init_fn;
|
|
void *thread_info;
|
|
} stack_start;
|
|
|
|
int __cpu_up(unsigned int cpu, struct task_struct *tsk)
|
|
{
|
|
unsigned long timeout;
|
|
|
|
per_cpu(cpu_state, cpu) = CPU_UP_PREPARE;
|
|
|
|
/* Fill in data in head.S for secondary cpus */
|
|
stack_start.sp = tsk->thread.sp;
|
|
stack_start.thread_info = tsk->stack;
|
|
stack_start.bss_start = 0; /* don't clear bss for secondary cpus */
|
|
stack_start.start_kernel_fn = start_secondary;
|
|
|
|
flush_icache_range((unsigned long)&stack_start,
|
|
(unsigned long)&stack_start + sizeof(stack_start));
|
|
wmb();
|
|
|
|
mp_ops->start_cpu(cpu, (unsigned long)_stext);
|
|
|
|
timeout = jiffies + HZ;
|
|
while (time_before(jiffies, timeout)) {
|
|
if (cpu_online(cpu))
|
|
break;
|
|
|
|
udelay(10);
|
|
barrier();
|
|
}
|
|
|
|
if (cpu_online(cpu))
|
|
return 0;
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
void __init smp_cpus_done(unsigned int max_cpus)
|
|
{
|
|
unsigned long bogosum = 0;
|
|
int cpu;
|
|
|
|
for_each_online_cpu(cpu)
|
|
bogosum += cpu_data[cpu].loops_per_jiffy;
|
|
|
|
printk(KERN_INFO "SMP: Total of %d processors activated "
|
|
"(%lu.%02lu BogoMIPS).\n", num_online_cpus(),
|
|
bogosum / (500000/HZ),
|
|
(bogosum / (5000/HZ)) % 100);
|
|
}
|
|
|
|
void smp_send_reschedule(int cpu)
|
|
{
|
|
mp_ops->send_ipi(cpu, SMP_MSG_RESCHEDULE);
|
|
}
|
|
|
|
void smp_send_stop(void)
|
|
{
|
|
smp_call_function(stop_this_cpu, 0, 0);
|
|
}
|
|
|
|
void arch_send_call_function_ipi_mask(const struct cpumask *mask)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_cpu(cpu, mask)
|
|
mp_ops->send_ipi(cpu, SMP_MSG_FUNCTION);
|
|
}
|
|
|
|
void arch_send_call_function_single_ipi(int cpu)
|
|
{
|
|
mp_ops->send_ipi(cpu, SMP_MSG_FUNCTION_SINGLE);
|
|
}
|
|
|
|
void smp_timer_broadcast(const struct cpumask *mask)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_cpu(cpu, mask)
|
|
mp_ops->send_ipi(cpu, SMP_MSG_TIMER);
|
|
}
|
|
|
|
static void ipi_timer(void)
|
|
{
|
|
irq_enter();
|
|
local_timer_interrupt();
|
|
irq_exit();
|
|
}
|
|
|
|
void smp_message_recv(unsigned int msg)
|
|
{
|
|
switch (msg) {
|
|
case SMP_MSG_FUNCTION:
|
|
generic_smp_call_function_interrupt();
|
|
break;
|
|
case SMP_MSG_RESCHEDULE:
|
|
scheduler_ipi();
|
|
break;
|
|
case SMP_MSG_FUNCTION_SINGLE:
|
|
generic_smp_call_function_single_interrupt();
|
|
break;
|
|
case SMP_MSG_TIMER:
|
|
ipi_timer();
|
|
break;
|
|
default:
|
|
printk(KERN_WARNING "SMP %d: %s(): unknown IPI %d\n",
|
|
smp_processor_id(), __func__, msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Not really SMP stuff ... */
|
|
int setup_profiling_timer(unsigned int multiplier)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void flush_tlb_all_ipi(void *info)
|
|
{
|
|
local_flush_tlb_all();
|
|
}
|
|
|
|
void flush_tlb_all(void)
|
|
{
|
|
on_each_cpu(flush_tlb_all_ipi, 0, 1);
|
|
}
|
|
|
|
static void flush_tlb_mm_ipi(void *mm)
|
|
{
|
|
local_flush_tlb_mm((struct mm_struct *)mm);
|
|
}
|
|
|
|
/*
|
|
* The following tlb flush calls are invoked when old translations are
|
|
* being torn down, or pte attributes are changing. For single threaded
|
|
* address spaces, a new context is obtained on the current cpu, and tlb
|
|
* context on other cpus are invalidated to force a new context allocation
|
|
* at switch_mm time, should the mm ever be used on other cpus. For
|
|
* multithreaded address spaces, intercpu interrupts have to be sent.
|
|
* Another case where intercpu interrupts are required is when the target
|
|
* mm might be active on another cpu (eg debuggers doing the flushes on
|
|
* behalf of debugees, kswapd stealing pages from another process etc).
|
|
* Kanoj 07/00.
|
|
*/
|
|
void flush_tlb_mm(struct mm_struct *mm)
|
|
{
|
|
preempt_disable();
|
|
|
|
if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
|
|
smp_call_function(flush_tlb_mm_ipi, (void *)mm, 1);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < num_online_cpus(); i++)
|
|
if (smp_processor_id() != i)
|
|
cpu_context(i, mm) = 0;
|
|
}
|
|
local_flush_tlb_mm(mm);
|
|
|
|
preempt_enable();
|
|
}
|
|
|
|
struct flush_tlb_data {
|
|
struct vm_area_struct *vma;
|
|
unsigned long addr1;
|
|
unsigned long addr2;
|
|
};
|
|
|
|
static void flush_tlb_range_ipi(void *info)
|
|
{
|
|
struct flush_tlb_data *fd = (struct flush_tlb_data *)info;
|
|
|
|
local_flush_tlb_range(fd->vma, fd->addr1, fd->addr2);
|
|
}
|
|
|
|
void flush_tlb_range(struct vm_area_struct *vma,
|
|
unsigned long start, unsigned long end)
|
|
{
|
|
struct mm_struct *mm = vma->vm_mm;
|
|
|
|
preempt_disable();
|
|
if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
|
|
struct flush_tlb_data fd;
|
|
|
|
fd.vma = vma;
|
|
fd.addr1 = start;
|
|
fd.addr2 = end;
|
|
smp_call_function(flush_tlb_range_ipi, (void *)&fd, 1);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < num_online_cpus(); i++)
|
|
if (smp_processor_id() != i)
|
|
cpu_context(i, mm) = 0;
|
|
}
|
|
local_flush_tlb_range(vma, start, end);
|
|
preempt_enable();
|
|
}
|
|
|
|
static void flush_tlb_kernel_range_ipi(void *info)
|
|
{
|
|
struct flush_tlb_data *fd = (struct flush_tlb_data *)info;
|
|
|
|
local_flush_tlb_kernel_range(fd->addr1, fd->addr2);
|
|
}
|
|
|
|
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
|
|
{
|
|
struct flush_tlb_data fd;
|
|
|
|
fd.addr1 = start;
|
|
fd.addr2 = end;
|
|
on_each_cpu(flush_tlb_kernel_range_ipi, (void *)&fd, 1);
|
|
}
|
|
|
|
static void flush_tlb_page_ipi(void *info)
|
|
{
|
|
struct flush_tlb_data *fd = (struct flush_tlb_data *)info;
|
|
|
|
local_flush_tlb_page(fd->vma, fd->addr1);
|
|
}
|
|
|
|
void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
|
|
{
|
|
preempt_disable();
|
|
if ((atomic_read(&vma->vm_mm->mm_users) != 1) ||
|
|
(current->mm != vma->vm_mm)) {
|
|
struct flush_tlb_data fd;
|
|
|
|
fd.vma = vma;
|
|
fd.addr1 = page;
|
|
smp_call_function(flush_tlb_page_ipi, (void *)&fd, 1);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < num_online_cpus(); i++)
|
|
if (smp_processor_id() != i)
|
|
cpu_context(i, vma->vm_mm) = 0;
|
|
}
|
|
local_flush_tlb_page(vma, page);
|
|
preempt_enable();
|
|
}
|
|
|
|
static void flush_tlb_one_ipi(void *info)
|
|
{
|
|
struct flush_tlb_data *fd = (struct flush_tlb_data *)info;
|
|
local_flush_tlb_one(fd->addr1, fd->addr2);
|
|
}
|
|
|
|
void flush_tlb_one(unsigned long asid, unsigned long vaddr)
|
|
{
|
|
struct flush_tlb_data fd;
|
|
|
|
fd.addr1 = asid;
|
|
fd.addr2 = vaddr;
|
|
|
|
smp_call_function(flush_tlb_one_ipi, (void *)&fd, 1);
|
|
local_flush_tlb_one(asid, vaddr);
|
|
}
|