summaryrefslogtreecommitdiffstats
path: root/arch/arc/kernel/smp.c
blob: 88674d972c9d056f33f87205aa77049c11006129 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/*
 * 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/spinlock.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/profile.h>
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/irq.h>
#include <linux/atomic.h>
#include <linux/cpumask.h>
#include <linux/reboot.h>
#include <linux/irqdomain.h>
#include <asm/processor.h>
#include <asm/setup.h>
#include <asm/mach_desc.h>

#ifndef CONFIG_ARC_HAS_LLSC
arch_spinlock_t smp_atomic_ops_lock = __ARCH_SPIN_LOCK_UNLOCKED;
arch_spinlock_t smp_bitops_lock = __ARCH_SPIN_LOCK_UNLOCKED;
#endif

struct plat_smp_ops  __weak plat_smp_ops;

/* 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)
{
}

/*
 * Called from setup_arch() before calling setup_processor()
 *
 * - Initialise the CPU possible map early - this describes the CPUs
 *   which may be present or become present in the system.
 * - Call early smp init hook. This can initialize a specific multi-core
 *   IP which is say common to several platforms (hence not part of
 *   platform specific int_early() hook)
 */
void __init smp_init_cpus(void)
{
	unsigned int i;

	for (i = 0; i < NR_CPUS; i++)
		set_cpu_possible(i, true);

	if (plat_smp_ops.init_early_smp)
		plat_smp_ops.init_early_smp();
}

/* called from init ( ) =>  process 1 */
void __init smp_prepare_cpus(unsigned int max_cpus)
{
	int i;

	/*
	 * if platform didn't set the present map already, do it now
	 * boot cpu is set to present already by init/main.c
	 */
	if (num_present_cpus() <= 1) {
		for (i = 0; i < max_cpus; i++)
			set_cpu_present(i, true);
	}
}

void __init smp_cpus_done(unsigned int max_cpus)
{

}

/*
 * Default smp boot helper for Run-on-reset case where all cores start off
 * together. Non-masters need to wait for Master to start running.
 * This is implemented using a flag in memory, which Non-masters spin-wait on.
 * Master sets it to cpu-id of core to "ungate" it.
 */
static volatile int wake_flag;

static void arc_default_smp_cpu_kick(int cpu, unsigned long pc)
{
	BUG_ON(cpu == 0);
	wake_flag = cpu;
}

void arc_platform_smp_wait_to_boot(int cpu)
{
	while (wake_flag != cpu)
		;

	wake_flag = 0;
	__asm__ __volatile__("j @first_lines_of_secondary	\n");
}


const char *arc_platform_smp_cpuinfo(void)
{
	return plat_smp_ops.info ? : "";
}

/*
 * 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 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;
	cpumask_set_cpu(cpu, mm_cpumask(mm));

	/* Some SMP H/w setup - for each cpu */
	if (plat_smp_ops.init_per_cpu)
		plat_smp_ops.init_per_cpu(cpu);

	if (machine_desc->init_per_cpu)
		machine_desc->init_per_cpu(cpu);

	notify_cpu_starting(cpu);
	set_cpu_online(cpu, true);

	pr_info("## CPU%u LIVE ##: Executing Code...\n", cpu);

	local_irq_enable();
	preempt_disable();
	cpu_startup_entry(CPUHP_AP_ONLINE_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 __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);

	if (plat_smp_ops.cpu_kick)
		plat_smp_ops.cpu_kick(cpu,
				(unsigned long)first_lines_of_secondary);
	else
		arc_default_smp_cpu_kick(cpu, (unsigned long)NULL);

	/* 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 setup_profiling_timer(unsigned int multiplier)
{
	return -EINVAL;
}

/*****************************************************************************/
/*              Inter Processor Interrupt Handling                           */
/*****************************************************************************/

enum ipi_msg_type {
	IPI_EMPTY = 0,
	IPI_RESCHEDULE = 1,
	IPI_CALL_FUNC,
	IPI_CPU_STOP,
};

/*
 * In arches with IRQ for each msg type (above), receiver can use IRQ-id  to
 * figure out what msg was sent. For those which don't (ARC has dedicated IPI
 * IRQ), the msg-type needs to be conveyed via per-cpu data
 */

static DEFINE_PER_CPU(unsigned long, ipi_data);

static void ipi_send_msg_one(int cpu, enum ipi_msg_type msg)
{
	unsigned long __percpu *ipi_data_ptr = per_cpu_ptr(&ipi_data, cpu);
	unsigned long old, new;
	unsigned long flags;

	pr_debug("%d Sending msg [%d] to %d\n", smp_processor_id(), msg, cpu);

	local_irq_save(flags);

	/*
	 * Atomically write new msg bit (in case others are writing too),
	 * and read back old value
	 */
	do {
		new = old = ACCESS_ONCE(*ipi_data_ptr);
		new |= 1U << msg;
	} while (cmpxchg(ipi_data_ptr, old, new) != old);

	/*
	 * Call the platform specific IPI kick function, but avoid if possible:
	 * Only do so if there's no pending msg from other concurrent sender(s).
	 * Otherwise, recevier will see this msg as well when it takes the
	 * IPI corresponding to that msg. This is true, even if it is already in
	 * IPI handler, because !@old means it has not yet dequeued the msg(s)
	 * so @new msg can be a free-loader
	 */
	if (plat_smp_ops.ipi_send && !old)
		plat_smp_ops.ipi_send(cpu);

	local_irq_restore(flags);
}

static void ipi_send_msg(const struct cpumask *callmap, enum ipi_msg_type msg)
{
	unsigned int cpu;

	for_each_cpu(cpu, callmap)
		ipi_send_msg_one(cpu, msg);
}

void smp_send_reschedule(int cpu)
{
	ipi_send_msg_one(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_one(cpu, IPI_CALL_FUNC);
}

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(void)
{
	machine_halt();
}

static inline int __do_IPI(unsigned long msg)
{
	int rc = 0;

	switch (msg) {
	case IPI_RESCHEDULE:
		scheduler_ipi();
		break;

	case IPI_CALL_FUNC:
		generic_smp_call_function_interrupt();
		break;

	case IPI_CPU_STOP:
		ipi_cpu_stop();
		break;

	default:
		rc = 1;
	}

	return rc;
}

/*
 * arch-common ISR to handle for inter-processor interrupts
 * Has hooks for platform specific IPI
 */
irqreturn_t do_IPI(int irq, void *dev_id)
{
	unsigned long pending;
	unsigned long __maybe_unused copy;

	pr_debug("IPI [%ld] received on cpu %d\n",
		 *this_cpu_ptr(&ipi_data), smp_processor_id());

	if (plat_smp_ops.ipi_clear)
		plat_smp_ops.ipi_clear(irq);

	/*
	 * "dequeue" the msg corresponding to this IPI (and possibly other
	 * piggybacked msg from elided IPIs: see ipi_send_msg_one() above)
	 */
	copy = pending = xchg(this_cpu_ptr(&ipi_data), 0);

	do {
		unsigned long msg = __ffs(pending);
		int rc;

		rc = __do_IPI(msg);
		if (rc)
			pr_info("IPI with bogus msg %ld in %ld\n", msg, copy);
		pending &= ~(1U << msg);
	} while (pending);

	return IRQ_HANDLED;
}

/*
 * API called by platform code to hookup arch-common ISR to their IPI IRQ
 *
 * Note: If IPI is provided by platform (vs. say ARC MCIP), their intc setup/map
 * function needs to call call irq_set_percpu_devid() for IPI IRQ, otherwise
 * request_percpu_irq() below will fail
 */
static DEFINE_PER_CPU(int, ipi_dev);

int smp_ipi_irq_setup(int cpu, irq_hw_number_t hwirq)
{
	int *dev = per_cpu_ptr(&ipi_dev, cpu);
	unsigned int virq = irq_find_mapping(NULL, hwirq);

	if (!virq)
		panic("Cannot find virq for root domain and hwirq=%lu", hwirq);

	/* Boot cpu calls request, all call enable */
	if (!cpu) {
		int rc;

		rc = request_percpu_irq(virq, do_IPI, "IPI Interrupt", dev);
		if (rc)
			panic("Percpu IRQ request failed for %u\n", virq);
	}

	enable_percpu_irq(virq, 0);

	return 0;
}