aboutsummaryrefslogtreecommitdiffstats
path: root/linux-2.6-xen-sparse/arch/ia64/kernel/irq_ia64.c
blob: 5a9db93417311f16861957ebcbfb77161d7415ea (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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
/*
 * linux/arch/ia64/kernel/irq.c
 *
 * Copyright (C) 1998-2001 Hewlett-Packard Co
 *	Stephane Eranian <eranian@hpl.hp.com>
 *	David Mosberger-Tang <davidm@hpl.hp.com>
 *
 *  6/10/99: Updated to bring in sync with x86 version to facilitate
 *	     support for SMP and different interrupt controllers.
 *
 * 09/15/00 Goutham Rao <goutham.rao@intel.com> Implemented pci_irq_to_vector
 *                      PCI to vector allocation routine.
 * 04/14/2004 Ashok Raj <ashok.raj@intel.com>
 *						Added CPU Hotplug handling for IPF.
 */

#include <linux/module.h>

#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/kernel_stat.h>
#include <linux/slab.h>
#include <linux/ptrace.h>
#include <linux/random.h>	/* for rand_initialize_irq() */
#include <linux/signal.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/threads.h>
#include <linux/bitops.h>
#ifdef CONFIG_XEN
#include <linux/cpu.h>
#endif

#include <asm/delay.h>
#include <asm/intrinsics.h>
#include <asm/io.h>
#include <asm/hw_irq.h>
#include <asm/machvec.h>
#include <asm/pgtable.h>
#include <asm/system.h>

#ifdef CONFIG_PERFMON
# include <asm/perfmon.h>
#endif

#define IRQ_DEBUG	0

/* These can be overridden in platform_irq_init */
int ia64_first_device_vector = IA64_DEF_FIRST_DEVICE_VECTOR;
int ia64_last_device_vector = IA64_DEF_LAST_DEVICE_VECTOR;

/* default base addr of IPI table */
void __iomem *ipi_base_addr = ((void __iomem *)
			       (__IA64_UNCACHED_OFFSET | IA64_IPI_DEFAULT_BASE_ADDR));

/*
 * Legacy IRQ to IA-64 vector translation table.
 */
__u8 isa_irq_to_vector_map[16] = {
	/* 8259 IRQ translation, first 16 entries */
	0x2f, 0x20, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29,
	0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21
};
EXPORT_SYMBOL(isa_irq_to_vector_map);

static unsigned long ia64_vector_mask[BITS_TO_LONGS(IA64_MAX_DEVICE_VECTORS)];

int
assign_irq_vector (int irq)
{
	int pos, vector;

#ifdef CONFIG_XEN
	if (is_running_on_xen()) {
		extern int xen_assign_irq_vector(int);
		return xen_assign_irq_vector(irq);
	}
#endif
 again:
	pos = find_first_zero_bit(ia64_vector_mask, IA64_NUM_DEVICE_VECTORS);
	vector = IA64_FIRST_DEVICE_VECTOR + pos;
	if (vector > IA64_LAST_DEVICE_VECTOR)
		return -ENOSPC;
	if (test_and_set_bit(pos, ia64_vector_mask))
		goto again;
	return vector;
}

void
free_irq_vector (int vector)
{
	int pos;

	if (vector < IA64_FIRST_DEVICE_VECTOR || vector > IA64_LAST_DEVICE_VECTOR)
		return;

#ifdef CONFIG_XEN
	if (is_running_on_xen()) {
		extern void xen_free_irq_vector(int);
		xen_free_irq_vector(vector);
		return;
	}
#endif
	pos = vector - IA64_FIRST_DEVICE_VECTOR;
	if (!test_and_clear_bit(pos, ia64_vector_mask))
		printk(KERN_WARNING "%s: double free!\n", __FUNCTION__);
}

int
reserve_irq_vector (int vector)
{
	int pos;

	if (vector < IA64_FIRST_DEVICE_VECTOR ||
	    vector > IA64_LAST_DEVICE_VECTOR)
		return -EINVAL;

	pos = vector - IA64_FIRST_DEVICE_VECTOR;
	return test_and_set_bit(pos, ia64_vector_mask);
}

#ifdef CONFIG_SMP
#	define IS_RESCHEDULE(vec)	(vec == IA64_IPI_RESCHEDULE)
#else
#	define IS_RESCHEDULE(vec)	(0)
#endif
/*
 * That's where the IVT branches when we get an external
 * interrupt. This branches to the correct hardware IRQ handler via
 * function ptr.
 */
void
ia64_handle_irq (ia64_vector vector, struct pt_regs *regs)
{
	unsigned long saved_tpr;

#if IRQ_DEBUG
	{
		unsigned long bsp, sp;

		/*
		 * Note: if the interrupt happened while executing in
		 * the context switch routine (ia64_switch_to), we may
		 * get a spurious stack overflow here.  This is
		 * because the register and the memory stack are not
		 * switched atomically.
		 */
		bsp = ia64_getreg(_IA64_REG_AR_BSP);
		sp = ia64_getreg(_IA64_REG_SP);

		if ((sp - bsp) < 1024) {
			static unsigned char count;
			static long last_time;

			if (jiffies - last_time > 5*HZ)
				count = 0;
			if (++count < 5) {
				last_time = jiffies;
				printk("ia64_handle_irq: DANGER: less than "
				       "1KB of free stack space!!\n"
				       "(bsp=0x%lx, sp=%lx)\n", bsp, sp);
			}
		}
	}
#endif /* IRQ_DEBUG */

	/*
	 * Always set TPR to limit maximum interrupt nesting depth to
	 * 16 (without this, it would be ~240, which could easily lead
	 * to kernel stack overflows).
	 */
	irq_enter();
	saved_tpr = ia64_getreg(_IA64_REG_CR_TPR);
	ia64_srlz_d();
	while (vector != IA64_SPURIOUS_INT_VECTOR) {
		if (!IS_RESCHEDULE(vector)) {
			ia64_setreg(_IA64_REG_CR_TPR, vector);
			ia64_srlz_d();

			__do_IRQ(local_vector_to_irq(vector), regs);

			/*
			 * Disable interrupts and send EOI:
			 */
			local_irq_disable();
			ia64_setreg(_IA64_REG_CR_TPR, saved_tpr);
		}
		ia64_eoi();
		vector = ia64_get_ivr();
	}
	/*
	 * This must be done *after* the ia64_eoi().  For example, the keyboard softirq
	 * handler needs to be able to wait for further keyboard interrupts, which can't
	 * come through until ia64_eoi() has been done.
	 */
	irq_exit();
}

#ifdef CONFIG_HOTPLUG_CPU
/*
 * This function emulates a interrupt processing when a cpu is about to be
 * brought down.
 */
void ia64_process_pending_intr(void)
{
	ia64_vector vector;
	unsigned long saved_tpr;
	extern unsigned int vectors_in_migration[NR_IRQS];

	vector = ia64_get_ivr();

	 irq_enter();
	 saved_tpr = ia64_getreg(_IA64_REG_CR_TPR);
	 ia64_srlz_d();

	 /*
	  * Perform normal interrupt style processing
	  */
	while (vector != IA64_SPURIOUS_INT_VECTOR) {
		if (!IS_RESCHEDULE(vector)) {
			ia64_setreg(_IA64_REG_CR_TPR, vector);
			ia64_srlz_d();

			/*
			 * Now try calling normal ia64_handle_irq as it would have got called
			 * from a real intr handler. Try passing null for pt_regs, hopefully
			 * it will work. I hope it works!.
			 * Probably could shared code.
			 */
			vectors_in_migration[local_vector_to_irq(vector)]=0;
			__do_IRQ(local_vector_to_irq(vector), NULL);

			/*
			 * Disable interrupts and send EOI
			 */
			local_irq_disable();
			ia64_setreg(_IA64_REG_CR_TPR, saved_tpr);
		}
		ia64_eoi();
		vector = ia64_get_ivr();
	}
	irq_exit();
}
#endif


#ifdef CONFIG_SMP
extern irqreturn_t handle_IPI (int irq, void *dev_id, struct pt_regs *regs);

static struct irqaction ipi_irqaction = {
	.handler =	handle_IPI,
	.flags =	IRQF_DISABLED,
	.name =		"IPI"
};
#endif

#ifdef CONFIG_XEN
#include <xen/evtchn.h>
#include <xen/interface/callback.h>

static DEFINE_PER_CPU(int, timer_irq) = -1;
static DEFINE_PER_CPU(int, ipi_irq) = -1;
static DEFINE_PER_CPU(int, resched_irq) = -1;
static DEFINE_PER_CPU(int, cmc_irq) = -1;
static DEFINE_PER_CPU(int, cmcp_irq) = -1;
static DEFINE_PER_CPU(int, cpep_irq) = -1;
static char timer_name[NR_CPUS][15];
static char ipi_name[NR_CPUS][15];
static char resched_name[NR_CPUS][15];
static char cmc_name[NR_CPUS][15];
static char cmcp_name[NR_CPUS][15];
static char cpep_name[NR_CPUS][15];

struct saved_irq {
	unsigned int irq;
	struct irqaction *action;
};
/* 16 should be far optimistic value, since only several percpu irqs
 * are registered early.
 */
#define MAX_LATE_IRQ	16
static struct saved_irq saved_percpu_irqs[MAX_LATE_IRQ];
static unsigned short late_irq_cnt = 0;
static unsigned short saved_irq_cnt = 0;
static int xen_slab_ready = 0;

#ifdef CONFIG_SMP
/* Dummy stub. Though we may check RESCHEDULE_VECTOR before __do_IRQ,
 * it ends up to issue several memory accesses upon percpu data and
 * thus adds unnecessary traffic to other paths.
 */
static irqreturn_t
handle_reschedule(int irq, void *dev_id, struct pt_regs *regs)
{

	return IRQ_HANDLED;
}

static struct irqaction resched_irqaction = {
	.handler =	handle_reschedule,
	.flags =	SA_INTERRUPT,
	.name =		"RESCHED"
};
#endif

/*
 * This is xen version percpu irq registration, which needs bind
 * to xen specific evtchn sub-system. One trick here is that xen
 * evtchn binding interface depends on kmalloc because related
 * port needs to be freed at device/cpu down. So we cache the
 * registration on BSP before slab is ready and then deal them
 * at later point. For rest instances happening after slab ready,
 * we hook them to xen evtchn immediately.
 *
 * FIXME: MCA is not supported by far, and thus "nomca" boot param is
 * required.
 */
static void
xen_register_percpu_irq(unsigned int cpu, unsigned int vec,
			 struct irqaction *action, int save)
{
	irq_desc_t *desc;
	int irq = 0;

	if (xen_slab_ready) {
		switch (vec) {
		case IA64_TIMER_VECTOR:
			sprintf(timer_name[cpu], "%s%d", action->name, cpu);
			irq = bind_virq_to_irqhandler(VIRQ_ITC, cpu,
				action->handler, action->flags,
				timer_name[cpu], action->dev_id);
			per_cpu(timer_irq,cpu) = irq;
			break;
		case IA64_IPI_RESCHEDULE:
			sprintf(resched_name[cpu], "%s%d", action->name, cpu);
			irq = bind_ipi_to_irqhandler(RESCHEDULE_VECTOR, cpu,
				action->handler, action->flags,
				resched_name[cpu], action->dev_id);
			per_cpu(resched_irq,cpu) = irq;
			break;
		case IA64_IPI_VECTOR:
			sprintf(ipi_name[cpu], "%s%d", action->name, cpu);
			irq = bind_ipi_to_irqhandler(IPI_VECTOR, cpu,
				action->handler, action->flags,
				ipi_name[cpu], action->dev_id);
			per_cpu(ipi_irq,cpu) = irq;
			break;
		case IA64_CMC_VECTOR:
			sprintf(cmc_name[cpu], "%s%d", action->name, cpu);
			irq = bind_virq_to_irqhandler(VIRQ_MCA_CMC, cpu,
			                              action->handler,
			                              action->flags,
			                              cmc_name[cpu],
			                              action->dev_id);
			per_cpu(cmc_irq,cpu) = irq;
			break;
		case IA64_CMCP_VECTOR:
			sprintf(cmcp_name[cpu], "%s%d", action->name, cpu);
			irq = bind_ipi_to_irqhandler(CMCP_VECTOR, cpu,
			                             action->handler,
			                             action->flags,
			                             cmcp_name[cpu],
			                             action->dev_id);
			per_cpu(cmcp_irq,cpu) = irq;
			break;
		case IA64_CPEP_VECTOR:
			sprintf(cpep_name[cpu], "%s%d", action->name, cpu);
			irq = bind_ipi_to_irqhandler(CPEP_VECTOR, cpu,
			                             action->handler,
			                             action->flags,
			                             cpep_name[cpu],
			                             action->dev_id);
			per_cpu(cpep_irq,cpu) = irq;
			break;
		case IA64_CPE_VECTOR:
		case IA64_MCA_RENDEZ_VECTOR:
		case IA64_PERFMON_VECTOR:
		case IA64_MCA_WAKEUP_VECTOR:
		case IA64_SPURIOUS_INT_VECTOR:
			/* No need to complain, these aren't supported. */
			break;
		default:
			printk(KERN_WARNING "Percpu irq %d is unsupported "
			       "by xen!\n", vec);
			break;
		}
		BUG_ON(irq < 0);

		if (irq > 0) {
			/*
			 * Mark percpu.  Without this, migrate_irqs() will
			 * mark the interrupt for migrations and trigger it
			 * on cpu hotplug.
			 */
			desc = irq_desc + irq;
			desc->status |= IRQ_PER_CPU;
		}
	} 

	/* For BSP, we cache registered percpu irqs, and then re-walk
	 * them when initializing APs
	 */
	if (!cpu && save) {
		BUG_ON(saved_irq_cnt == MAX_LATE_IRQ);
		saved_percpu_irqs[saved_irq_cnt].irq = vec;
		saved_percpu_irqs[saved_irq_cnt].action = action;
		saved_irq_cnt++;
		if (!xen_slab_ready)
			late_irq_cnt++;
	}
}

static void
xen_bind_early_percpu_irq (void)
{
	int i;

	xen_slab_ready = 1;
	/* There's no race when accessing this cached array, since only
	 * BSP will face with such step shortly
	 */
	for (i = 0; i < late_irq_cnt; i++)
		xen_register_percpu_irq(smp_processor_id(),
					saved_percpu_irqs[i].irq,
		                        saved_percpu_irqs[i].action, 0);
}

/* FIXME: There's no obvious point to check whether slab is ready. So
 * a hack is used here by utilizing a late time hook.
 */
extern void (*late_time_init)(void);
extern char xen_event_callback;
extern void xen_init_IRQ(void);

#ifdef CONFIG_HOTPLUG_CPU
static int __devinit
unbind_evtchn_callback(struct notifier_block *nfb,
                       unsigned long action, void *hcpu)
{
	unsigned int cpu = (unsigned long)hcpu;

	if (action == CPU_DEAD) {
		/* Unregister evtchn.  */
		if (per_cpu(cpep_irq,cpu) >= 0) {
			unbind_from_irqhandler(per_cpu(cpep_irq, cpu), NULL);
			per_cpu(cpep_irq, cpu) = -1;
		}
		if (per_cpu(cmcp_irq,cpu) >= 0) {
			unbind_from_irqhandler(per_cpu(cmcp_irq, cpu), NULL);
			per_cpu(cmcp_irq, cpu) = -1;
		}
		if (per_cpu(cmc_irq,cpu) >= 0) {
			unbind_from_irqhandler(per_cpu(cmc_irq, cpu), NULL);
			per_cpu(cmc_irq, cpu) = -1;
		}
		if (per_cpu(ipi_irq,cpu) >= 0) {
			unbind_from_irqhandler (per_cpu(ipi_irq, cpu), NULL);
			per_cpu(ipi_irq, cpu) = -1;
		}
		if (per_cpu(resched_irq,cpu) >= 0) {
			unbind_from_irqhandler (per_cpu(resched_irq, cpu),
						NULL);
			per_cpu(resched_irq, cpu) = -1;
		}
		if (per_cpu(timer_irq,cpu) >= 0) {
			unbind_from_irqhandler (per_cpu(timer_irq, cpu), NULL);
			per_cpu(timer_irq, cpu) = -1;
		}
	}
	return NOTIFY_OK;
}

static struct notifier_block unbind_evtchn_notifier = {
	.notifier_call = unbind_evtchn_callback,
	.priority = 0
};
#endif

DECLARE_PER_CPU(int, ipi_to_irq[NR_IPIS]);
void xen_smp_intr_init_early(unsigned int cpu)
{
#ifdef CONFIG_SMP
	unsigned int i;

	for (i = 0; i < saved_irq_cnt; i++)
		xen_register_percpu_irq(cpu, saved_percpu_irqs[i].irq,
		                        saved_percpu_irqs[i].action, 0);
#endif
}

void xen_smp_intr_init(void)
{
#ifdef CONFIG_SMP
	unsigned int cpu = smp_processor_id();
	struct callback_register event = {
		.type = CALLBACKTYPE_event,
		.address = (unsigned long)&xen_event_callback,
	};

	if (cpu == 0) {
		/* Initialization was already done for boot cpu.  */
#ifdef CONFIG_HOTPLUG_CPU
		/* Register the notifier only once.  */
		register_cpu_notifier(&unbind_evtchn_notifier);
#endif
		return;
	}

	/* This should be piggyback when setup vcpu guest context */
	BUG_ON(HYPERVISOR_callback_op(CALLBACKOP_register, &event));
#endif /* CONFIG_SMP */
}

void
xen_irq_init(void)
{
	struct callback_register event = {
		.type = CALLBACKTYPE_event,
		.address = (unsigned long)&xen_event_callback,
	};

	xen_init_IRQ();
	BUG_ON(HYPERVISOR_callback_op(CALLBACKOP_register, &event));
	late_time_init = xen_bind_early_percpu_irq;
#ifdef CONFIG_SMP
	register_percpu_irq(IA64_IPI_RESCHEDULE, &resched_irqaction);
#endif
}

void
xen_platform_send_ipi(int cpu, int vector, int delivery_mode, int redirect)
{
	int irq = -1;

#ifdef CONFIG_SMP
	/* TODO: we need to call vcpu_up here */
	if (unlikely(vector == ap_wakeup_vector)) {
		extern void xen_send_ipi (int cpu, int vec);

		/* XXX
		 * This should be in __cpu_up(cpu) in ia64 smpboot.c
		 * like x86. But don't want to modify it,
		 * keep it untouched.
		 */
		xen_smp_intr_init_early(cpu);

		xen_send_ipi (cpu, vector);
		//vcpu_prepare_and_up(cpu);
		return;
	}
#endif

	switch (vector) {
		case IA64_IPI_VECTOR:
			irq = per_cpu(ipi_to_irq, cpu)[IPI_VECTOR];
			break;
		case IA64_IPI_RESCHEDULE:
			irq = per_cpu(ipi_to_irq, cpu)[RESCHEDULE_VECTOR];
			break;
		case IA64_CMCP_VECTOR:
			irq = per_cpu(ipi_to_irq, cpu)[CMCP_VECTOR];
			break;
		case IA64_CPEP_VECTOR:
			irq = per_cpu(ipi_to_irq, cpu)[CPEP_VECTOR];
			break;
		default:
			printk(KERN_WARNING "Unsupported IPI type 0x%x\n",
			       vector);
			irq = 0;
			break;
	}		
	
	BUG_ON(irq < 0);
	notify_remote_via_irq(irq);
	return;
}
#endif /* CONFIG_XEN */

void
register_percpu_irq (ia64_vector vec, struct irqaction *action)
{
	irq_desc_t *desc;
	unsigned int irq;

#ifdef CONFIG_XEN
	if (is_running_on_xen())
		return xen_register_percpu_irq(smp_processor_id(), 
					       vec, action, 1);
#endif

	for (irq = 0; irq < NR_IRQS; ++irq)
		if (irq_to_vector(irq) == vec) {
			desc = irq_desc + irq;
			desc->status |= IRQ_PER_CPU;
			desc->chip = &irq_type_ia64_lsapic;
			if (action)
				setup_irq(irq, action);
		}
}

void __init
init_IRQ (void)
{
	register_percpu_irq(IA64_SPURIOUS_INT_VECTOR, NULL);
#ifdef CONFIG_SMP
	register_percpu_irq(IA64_IPI_VECTOR, &ipi_irqaction);
#endif
#ifdef CONFIG_PERFMON
	pfm_init_percpu();
#endif
	platform_irq_init();
#ifdef CONFIG_XEN
	if (is_running_on_xen() && !ia64_platform_is("xen"))
		xen_irq_init();
#endif
}

void
ia64_send_ipi (int cpu, int vector, int delivery_mode, int redirect)
{
	void __iomem *ipi_addr;
	unsigned long ipi_data;
	unsigned long phys_cpu_id;

#ifdef CONFIG_XEN
	if (is_running_on_xen()) {
		xen_platform_send_ipi(cpu, vector, delivery_mode, redirect);
		return;
	}
#endif

#ifdef CONFIG_SMP
	phys_cpu_id = cpu_physical_id(cpu);
#else
	phys_cpu_id = (ia64_getreg(_IA64_REG_CR_LID) >> 16) & 0xffff;
#endif

	/*
	 * cpu number is in 8bit ID and 8bit EID
	 */

	ipi_data = (delivery_mode << 8) | (vector & 0xff);
	ipi_addr = ipi_base_addr + ((phys_cpu_id << 4) | ((redirect & 1) << 3));

	writeq(ipi_data, ipi_addr);
}