xics.c 10.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 
 * arch/ppc/kernel/xics.c
 *
 * Copyright 2000 IBM Corporation.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 */
#include <linux/config.h>
#include <linux/types.h>
#include <linux/threads.h>
#include <linux/kernel.h>
15
#include <linux/irq.h>
16 17
#include <linux/smp.h>
#include <linux/interrupt.h>
18 19 20 21
#include <asm/prom.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/smp.h>
22
#include <asm/naca.h>
23 24
#include <asm/rtas.h>
#include "i8259.h"
25
#include <asm/xics.h>
26
#include <asm/ppcdebug.h>
27
#include <asm/machdep.h>
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

void xics_enable_irq(u_int irq);
void xics_disable_irq(u_int irq);
void xics_mask_and_ack_irq(u_int irq);
void xics_end_irq(u_int irq);
void xics_set_affinity(unsigned int irq_nr, unsigned long cpumask);

struct hw_interrupt_type xics_pic = {
	" XICS     ",
	NULL,
	NULL,
	xics_enable_irq,
	xics_disable_irq,
	xics_mask_and_ack_irq,
	xics_end_irq,
	xics_set_affinity
};

struct hw_interrupt_type xics_8259_pic = {
	" XICS/8259",
	NULL,
	NULL,
	NULL,
	NULL,
	xics_mask_and_ack_irq,
	NULL
};

#define XICS_IPI		2
#define XICS_IRQ_OFFSET		0x10
#define XICS_IRQ_SPURIOUS	0

/* Want a priority other than 0.  Various HW issues require this. */
#define	DEFAULT_PRIORITY	5

struct xics_ipl {
	union {
		u32	word;
		u8	bytes[4];
	} xirr_poll;
	union {
		u32 word;
		u8	bytes[4];
	} xirr;
	u32	dummy;
	union {
		u32	word;
		u8	bytes[4];
	} qirr;
};

struct xics_info {
	volatile struct xics_ipl *	per_cpu[NR_CPUS];
};

struct xics_info	xics_info;

unsigned long long intr_base = 0;
int xics_irq_8259_cascade = 0;
int xics_irq_8259_cascade_real = 0;
88
unsigned int default_server = 0xFF;
89 90 91 92 93
unsigned int default_distrib_server = 0;

/* RTAS service tokens */
int ibm_get_xive;
int ibm_set_xive;
94
int ibm_int_on;
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
int ibm_int_off;

struct xics_interrupt_node {
	unsigned long long addr;
	unsigned long long size;
} inodes[NR_CPUS*2];	 

typedef struct {
	int (*xirr_info_get)(int cpu);
	void (*xirr_info_set)(int cpu, int val);
	void (*cppr_info)(int cpu, u8 val);
	void (*qirr_info)(int cpu, u8 val);
} xics_ops;


static int pSeries_xirr_info_get(int n_cpu)
{
	return (xics_info.per_cpu[n_cpu]->xirr.word);
}

static void pSeries_xirr_info_set(int n_cpu, int value)
{
	xics_info.per_cpu[n_cpu]->xirr.word = value;
}

static void pSeries_cppr_info(int n_cpu, u8 value)
{
	xics_info.per_cpu[n_cpu]->xirr.bytes[0] = value;
}

static void pSeries_qirr_info(int n_cpu , u8 value)
{
	xics_info.per_cpu[n_cpu]->qirr.bytes[0] = value;
}

static xics_ops pSeries_ops = {
	pSeries_xirr_info_get,
	pSeries_xirr_info_set,
	pSeries_cppr_info,
	pSeries_qirr_info
};

static xics_ops *ops = &pSeries_ops;
extern xics_ops pSeriesLP_ops;


void
xics_enable_irq(
	u_int	virq
	)
{
	u_int		irq;
	unsigned long	status;
	long	        call_status;

	virq -= XICS_IRQ_OFFSET;
	irq = virt_irq_to_real(virq);
	if (irq == XICS_IPI)
		return;
#ifdef CONFIG_IRQ_ALL_CPUS
	call_status = rtas_call(ibm_set_xive, 3, 1, (unsigned long*)&status,
				irq, smp_threads_ready ? default_distrib_server : default_server, DEFAULT_PRIORITY);
#else
	call_status = rtas_call(ibm_set_xive, 3, 1, (unsigned long*)&status,
				irq, default_server, DEFAULT_PRIORITY);
#endif
	if( call_status != 0 ) {
		printk("xics_enable_irq: irq=%x: rtas_call failed; retn=%lx, status=%lx\n",
		       irq, call_status, status);
		return;
	}
166 167 168 169 170 171 172 173
	/* Now unmask the interrupt (often a no-op) */
	call_status = rtas_call(ibm_int_on, 1, 1, (unsigned long*)&status, 
				irq);
	if( call_status != 0 ) {
		printk("xics_disable_irq on: irq=%x: rtas_call failed, retn=%lx\n",
		       irq, call_status);
		return;
	}
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
}

void
xics_disable_irq(
	u_int	virq
	)
{
	u_int		irq;
	unsigned long 	status;
	long 	        call_status;

	virq -= XICS_IRQ_OFFSET;
	irq = virt_irq_to_real(virq);
	call_status = rtas_call(ibm_int_off, 1, 1, (unsigned long*)&status, 
				irq);
	if( call_status != 0 ) {
		printk("xics_disable_irq: irq=%x: rtas_call failed, retn=%lx\n",
		       irq, call_status);
		return;
	}
}

void
xics_end_irq(
	u_int	irq
	)
{
	int cpu = smp_processor_id();

	ops->cppr_info(cpu, 0); /* actually the value overwritten by ack */
	iosync();
	ops->xirr_info_set(cpu, ((0xff<<24) | (virt_irq_to_real(irq-XICS_IRQ_OFFSET))));
	iosync();
}

void
210
xics_mask_and_ack_irq(u_int	irq)
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
{
	int cpu = smp_processor_id();

	if( irq < XICS_IRQ_OFFSET ) {
		i8259_pic.ack(irq);
		iosync();
		ops->xirr_info_set(cpu, ((0xff<<24) | xics_irq_8259_cascade_real));
		iosync();
	}
	else {
		ops->cppr_info(cpu, 0xff);
		iosync();
	}
}

int
xics_get_irq(struct pt_regs *regs)
{
	u_int	cpu = smp_processor_id();
	u_int	vec;
	int irq;

	vec = ops->xirr_info_get(cpu);
	/*  (vec >> 24) == old priority */
	vec &= 0x00ffffff;
	/* for sanity, this had better be < NR_IRQS - 16 */
	if( vec == xics_irq_8259_cascade_real ) {
		irq = i8259_irq(cpu);
		if(irq == -1) {
			/* Spurious cascaded interrupt.  Still must ack xics */
                        xics_end_irq(XICS_IRQ_OFFSET + xics_irq_8259_cascade);
			irq = -1;
		}
	} else if( vec == XICS_IRQ_SPURIOUS ) {
		irq = -1;
246
	} else {
247
		irq = real_irq_to_virt(vec) + XICS_IRQ_OFFSET;
248
	}
249 250 251
	return irq;
}

252 253 254 255 256
struct xics_ipi_struct {
	volatile unsigned long value;
} ____cacheline_aligned;

extern struct xics_ipi_struct xics_ipi_message[NR_CPUS] __cacheline_aligned;
257 258 259 260 261 262 263

#ifdef CONFIG_SMP
void xics_ipi_action(int irq, void *dev_id, struct pt_regs *regs)
{
	int cpu = smp_processor_id();

	ops->qirr_info(cpu, 0xff);
264 265
	while (xics_ipi_message[cpu].value) {
		if (test_and_clear_bit(PPC_MSG_CALL_FUNCTION, &xics_ipi_message[cpu].value)) {
266 267 268
			mb();
			smp_message_recv(PPC_MSG_CALL_FUNCTION, regs);
		}
269
		if (test_and_clear_bit(PPC_MSG_RESCHEDULE, &xics_ipi_message[cpu].value)) {
270 271 272
			mb();
			smp_message_recv(PPC_MSG_RESCHEDULE, regs);
		}
273
#if 0
274
		if (test_and_clear_bit(PPC_MSG_MIGRATE_TASK, &xics_ipi_message[cpu].value)) {
275 276 277
			mb();
			smp_message_recv(PPC_MSG_MIGRATE_TASK, regs);
		}
278 279
#endif
#ifdef CONFIG_XMON
280
		if (test_and_clear_bit(PPC_MSG_XMON_BREAK, &xics_ipi_message[cpu].value)) {
281 282 283 284
			mb();
			smp_message_recv(PPC_MSG_XMON_BREAK, regs);
		}
#endif
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
	}
}

void xics_cause_IPI(int cpu)
{
	ops->qirr_info(cpu,0) ;
}

void xics_setup_cpu(void)
{
	int cpu = smp_processor_id();

	ops->cppr_info(cpu, 0xff);
	iosync();
}
#endif /* CONFIG_SMP */

void
xics_init_IRQ( void )
{
	int i;
	unsigned long intr_size = 0;
	struct device_node *np;
	uint *ireg, ilen, indx=0;

310 311
	ppc64_boot_msg(0x20, "XICS Init");

312 313
	ibm_get_xive = rtas_token("ibm,get-xive");
	ibm_set_xive = rtas_token("ibm,set-xive");
314
	ibm_int_on  = rtas_token("ibm,int-on");
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
	ibm_int_off = rtas_token("ibm,int-off");

	np = find_type_devices("PowerPC-External-Interrupt-Presentation");
	if (!np) {
		printk(KERN_WARNING "Can't find Interrupt Presentation\n");
		udbg_printf("Can't find Interrupt Presentation\n");
		while (1);
	}
nextnode:
	ireg = (uint *)get_property(np, "ibm,interrupt-server-ranges", 0);
	if (ireg) {
		/*
		 * set node starting index for this node
		 */
		indx = *ireg;
	}

	ireg = (uint *)get_property(np, "reg", &ilen);
	if (!ireg) {
		printk(KERN_WARNING "Can't find Interrupt Reg Property\n");
		udbg_printf("Can't find Interrupt Reg Property\n");
		while (1);
	}
	
	while (ilen) {
		inodes[indx].addr = (unsigned long long)*ireg++ << 32;
		ilen -= sizeof(uint);
		inodes[indx].addr |= *ireg++;
		ilen -= sizeof(uint);
		inodes[indx].size = (unsigned long long)*ireg++ << 32;
		ilen -= sizeof(uint);
		inodes[indx].size |= *ireg++;
		ilen -= sizeof(uint);
		indx++;
		if (indx >= NR_CPUS) break;
	}

	np = np->next;
	if ((indx < NR_CPUS) && np) goto nextnode;

	/* Find the server numbers for the boot cpu. */
	for (np = find_type_devices("cpu"); np; np = np->next) {
		ireg = (uint *)get_property(np, "reg", &ilen);
358
		if (ireg && ireg[0] == smp_processor_id()) {
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
			ireg = (uint *)get_property(np, "ibm,ppc-interrupt-gserver#s", &ilen);
			i = ilen / sizeof(int);
			if (ireg && i > 0) {
				default_server = ireg[0];
				default_distrib_server = ireg[i-1]; /* take last element */
			}
			break;
		}
	}

	intr_base = inodes[0].addr;
	intr_size = (ulong)inodes[0].size;

	np = find_type_devices("interrupt-controller");
	if (!np) {
		printk(KERN_WARNING "xics:  no ISA Interrupt Controller\n");
375
		xics_irq_8259_cascade_real = -1;
376 377 378 379 380 381 382 383 384 385 386 387
		xics_irq_8259_cascade = -1;
	} else {
		ireg = (uint *) get_property(np, "interrupts", 0);
		if (!ireg) {
			printk(KERN_WARNING "Can't find ISA Interrupts Property\n");
			udbg_printf("Can't find ISA Interrupts Property\n");
			while (1);
		}
		xics_irq_8259_cascade_real = *ireg;
		xics_irq_8259_cascade = virt_irq_create_mapping(xics_irq_8259_cascade_real);
	}

388
	if (naca->platform == PLATFORM_PSERIES) {
389
#ifdef CONFIG_SMP
390
		for (i = 0; i < NR_CPUS; ++i) {
Anton Blanchard's avatar
Anton Blanchard committed
391
			if (!cpu_possible(i))
392
				continue;
393
			xics_info.per_cpu[i] =
394 395
			  __ioremap((ulong)inodes[i].addr, 
				  (ulong)inodes[i].size, _PAGE_NO_CACHE);
396 397 398 399 400 401 402 403
		}
#else
		xics_info.per_cpu[0] = __ioremap((ulong)intr_base, intr_size, _PAGE_NO_CACHE);
#endif /* CONFIG_SMP */
#ifdef CONFIG_PPC_PSERIES
	/* actually iSeries does not use any of xics...but it has link dependencies
	 * for now, except this new one...
	 */
404
	} else if (naca->platform == PLATFORM_PSERIES_LPAR) {
405 406 407 408 409 410 411 412 413 414 415
		ops = &pSeriesLP_ops;
#endif
	}

	xics_8259_pic.enable = i8259_pic.enable;
	xics_8259_pic.disable = i8259_pic.disable;
	for (i = 0; i < 16; ++i)
		irq_desc[i].handler = &xics_8259_pic;
	for (; i < NR_IRQS; ++i)
		irq_desc[i].handler = &xics_pic;

416
	ops->cppr_info(boot_cpuid, 0xff);
417 418 419 420 421 422 423 424 425 426 427 428 429
	iosync();
	if (xics_irq_8259_cascade != -1) {
		if (request_irq(xics_irq_8259_cascade + XICS_IRQ_OFFSET, no_action,
				0, "8259 cascade", 0))
			printk(KERN_ERR "xics_init_IRQ: couldn't get 8259 cascade\n");
		i8259_init();
	}

#ifdef CONFIG_SMP
	real_irq_to_virt_map[XICS_IPI] = virt_irq_to_real_map[XICS_IPI] = XICS_IPI;
	request_irq(XICS_IPI + XICS_IRQ_OFFSET, xics_ipi_action, 0, "IPI", 0);
	irq_desc[XICS_IPI+XICS_IRQ_OFFSET].status |= IRQ_PER_CPU;
#endif
430
	ppc64_boot_msg(0x21, "XICS Done");
431 432 433 434 435 436 437 438 439
}

void xics_set_affinity(unsigned int virq, unsigned long cpumask)
{
        irq_desc_t *desc = irq_desc + virq;
	unsigned int irq;
	unsigned long flags;
	long status;
	unsigned long xics_status[2];
440
	unsigned long newmask;
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457

	virq -= XICS_IRQ_OFFSET;
	irq = virt_irq_to_real(virq);
	if (irq == XICS_IPI)
		return;

        spin_lock_irqsave(&desc->lock, flags);

	status = rtas_call(ibm_get_xive, 1, 3, (void *)&xics_status, irq);

	if (status) {
		printk("xics_set_affinity: irq=%d ibm,get-xive returns %ld\n",
			irq, status);
		goto out;
	}

	/* For the moment only implement delivery to all cpus or one cpu */
458
	if (cpumask == -1UL) {
459
		newmask = default_distrib_server;
460 461 462
	} else {
		if (!(cpumask & cpu_online_map))
			goto out;
463
		newmask = find_first_bit(&cpumask, 8*sizeof(unsigned long));
464
	}
465 466 467 468 469 470 471 472 473 474 475 476 477

	status = rtas_call(ibm_set_xive, 3, 1, NULL,
				irq, newmask, xics_status[1]);

	if (status) {
		printk("xics_set_affinity irq=%d ibm,set-xive returns %ld\n",
			irq, status);
		goto out;
	}

out:
        spin_unlock_irqrestore(&desc->lock, flags);
}