irq.c 10.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7
/*
 * Platform dependent support for SGI SN
 *
 * 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.
 *
8
 * Copyright (c) 2000-2005 Silicon Graphics, Inc.  All Rights Reserved.
L
Linus Torvalds 已提交
9 10 11
 */

#include <linux/irq.h>
12
#include <linux/spinlock.h>
L
Linus Torvalds 已提交
13 14
#include <asm/sn/addrs.h>
#include <asm/sn/arch.h>
15 16
#include <asm/sn/intr.h>
#include <asm/sn/pcibr_provider.h>
17 18
#include <asm/sn/pcibus_provider_defs.h>
#include <asm/sn/pcidev.h>
L
Linus Torvalds 已提交
19 20 21 22 23 24 25
#include <asm/sn/shub_mmr.h>
#include <asm/sn/sn_sal.h>

static void force_interrupt(int irq);
static void register_intr_pda(struct sn_irq_info *sn_irq_info);
static void unregister_intr_pda(struct sn_irq_info *sn_irq_info);

L
Len Brown 已提交
26
int sn_force_interrupt_flag = 1;
L
Linus Torvalds 已提交
27
extern int sn_ioif_inited;
28 29
static struct list_head **sn_irq_lh;
static spinlock_t sn_irq_info_lock = SPIN_LOCK_UNLOCKED; /* non-IRQ lock */
L
Linus Torvalds 已提交
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

static inline uint64_t sn_intr_alloc(nasid_t local_nasid, int local_widget,
				     u64 sn_irq_info,
				     int req_irq, nasid_t req_nasid,
				     int req_slice)
{
	struct ia64_sal_retval ret_stuff;
	ret_stuff.status = 0;
	ret_stuff.v0 = 0;

	SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT,
			(u64) SAL_INTR_ALLOC, (u64) local_nasid,
			(u64) local_widget, (u64) sn_irq_info, (u64) req_irq,
			(u64) req_nasid, (u64) req_slice);
	return ret_stuff.status;
}

static inline void sn_intr_free(nasid_t local_nasid, int local_widget,
				struct sn_irq_info *sn_irq_info)
{
	struct ia64_sal_retval ret_stuff;
	ret_stuff.status = 0;
	ret_stuff.v0 = 0;

	SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT,
			(u64) SAL_INTR_FREE, (u64) local_nasid,
			(u64) local_widget, (u64) sn_irq_info->irq_irq,
			(u64) sn_irq_info->irq_cookie, 0, 0);
}

static unsigned int sn_startup_irq(unsigned int irq)
{
	return 0;
}

static void sn_shutdown_irq(unsigned int irq)
{
}

static void sn_disable_irq(unsigned int irq)
{
}

static void sn_enable_irq(unsigned int irq)
{
}

static void sn_ack_irq(unsigned int irq)
{
79
	u64 event_occurred, mask = 0;
L
Linus Torvalds 已提交
80 81 82

	irq = irq & 0xff;
	event_occurred =
83
	    HUB_L((u64*)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED));
84
	mask = event_occurred & SH_ALL_INT_MASK;
85 86
	HUB_S((u64*)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS),
	      mask);
L
Linus Torvalds 已提交
87 88 89 90 91 92 93 94
	__set_bit(irq, (volatile void *)pda->sn_in_service_ivecs);

	move_irq(irq);
}

static void sn_end_irq(unsigned int irq)
{
	int ivec;
95
	u64 event_occurred;
L
Linus Torvalds 已提交
96 97 98

	ivec = irq & 0xff;
	if (ivec == SGI_UART_VECTOR) {
99
		event_occurred = HUB_L((u64*)LOCAL_MMR_ADDR (SH_EVENT_OCCURRED));
100
		/* If the UART bit is set here, we may have received an
L
Linus Torvalds 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113
		 * interrupt from the UART that the driver missed.  To
		 * make sure, we IPI ourselves to force us to look again.
		 */
		if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) {
			platform_send_ipi(smp_processor_id(), SGI_UART_VECTOR,
					  IA64_IPI_DM_INT, 0);
		}
	}
	__clear_bit(ivec, (volatile void *)pda->sn_in_service_ivecs);
	if (sn_force_interrupt_flag)
		force_interrupt(irq);
}

114 115
static void sn_irq_info_free(struct rcu_head *head);

L
Linus Torvalds 已提交
116 117
static void sn_set_affinity_irq(unsigned int irq, cpumask_t mask)
{
118
	struct sn_irq_info *sn_irq_info, *sn_irq_info_safe;
L
Linus Torvalds 已提交
119 120 121 122 123
	int cpuid, cpuphys;

	cpuid = first_cpu(mask);
	cpuphys = cpu_physical_id(cpuid);

124 125 126 127 128 129
	list_for_each_entry_safe(sn_irq_info, sn_irq_info_safe,
				 sn_irq_lh[irq], list) {
		uint64_t bridge;
		int local_widget, status;
		nasid_t local_nasid;
		struct sn_irq_info *new_irq_info;
130
		struct sn_pcibus_provider *pci_provider;
131 132 133 134 135 136 137 138 139 140 141

		new_irq_info = kmalloc(sizeof(struct sn_irq_info), GFP_ATOMIC);
		if (new_irq_info == NULL)
			break;
		memcpy(new_irq_info, sn_irq_info, sizeof(struct sn_irq_info));

		bridge = (uint64_t) new_irq_info->irq_bridge;
		if (!bridge) {
			kfree(new_irq_info);
			break; /* irq is not a device interrupt */
		}
L
Linus Torvalds 已提交
142

143
		local_nasid = NASID_GET(bridge);
L
Linus Torvalds 已提交
144 145 146 147 148 149

		if (local_nasid & 1)
			local_widget = TIO_SWIN_WIDGETNUM(bridge);
		else
			local_widget = SWIN_WIDGETNUM(bridge);

150 151 152 153
		/* Free the old PROM new_irq_info structure */
		sn_intr_free(local_nasid, local_widget, new_irq_info);
		/* Update kernels new_irq_info with new target info */
		unregister_intr_pda(new_irq_info);
L
Linus Torvalds 已提交
154

155
		/* allocate a new PROM new_irq_info struct */
L
Linus Torvalds 已提交
156
		status = sn_intr_alloc(local_nasid, local_widget,
157 158 159 160 161 162 163 164 165
				       __pa(new_irq_info), irq,
				       cpuid_to_nasid(cpuid),
				       cpuid_to_slice(cpuid));

		/* SAL call failed */
		if (status) {
			kfree(new_irq_info);
			break;
		}
L
Linus Torvalds 已提交
166

167 168 169
		new_irq_info->irq_cpuid = cpuid;
		register_intr_pda(new_irq_info);

170 171 172
		pci_provider = sn_pci_provider[new_irq_info->irq_bridge_type];
		if (pci_provider && pci_provider->target_interrupt)
			(pci_provider->target_interrupt)(new_irq_info);
173 174 175 176 177

		spin_lock(&sn_irq_info_lock);
		list_replace_rcu(&sn_irq_info->list, &new_irq_info->list);
		spin_unlock(&sn_irq_info_lock);
		call_rcu(&sn_irq_info->rcu, sn_irq_info_free);
L
Linus Torvalds 已提交
178 179

#ifdef CONFIG_SMP
180
		set_irq_affinity_info((irq & 0xff), cpuphys, 0);
L
Linus Torvalds 已提交
181 182 183 184 185
#endif
	}
}

struct hw_interrupt_type irq_type_sn = {
186 187 188 189 190 191 192 193
	.typename	= "SN hub",
	.startup	= sn_startup_irq,
	.shutdown	= sn_shutdown_irq,
	.enable		= sn_enable_irq,
	.disable	= sn_disable_irq,
	.ack		= sn_ack_irq,
	.end		= sn_end_irq,
	.set_affinity	= sn_set_affinity_irq
L
Linus Torvalds 已提交
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
};

unsigned int sn_local_vector_to_irq(u8 vector)
{
	return (CPU_VECTOR_TO_IRQ(smp_processor_id(), vector));
}

void sn_irq_init(void)
{
	int i;
	irq_desc_t *base_desc = irq_desc;

	for (i = 0; i < NR_IRQS; i++) {
		if (base_desc[i].handler == &no_irq_type) {
			base_desc[i].handler = &irq_type_sn;
		}
	}
}

static void register_intr_pda(struct sn_irq_info *sn_irq_info)
{
	int irq = sn_irq_info->irq_irq;
	int cpu = sn_irq_info->irq_cpuid;

	if (pdacpu(cpu)->sn_last_irq < irq) {
		pdacpu(cpu)->sn_last_irq = irq;
	}

	if (pdacpu(cpu)->sn_first_irq == 0 || pdacpu(cpu)->sn_first_irq > irq) {
		pdacpu(cpu)->sn_first_irq = irq;
	}
}

static void unregister_intr_pda(struct sn_irq_info *sn_irq_info)
{
	int irq = sn_irq_info->irq_irq;
	int cpu = sn_irq_info->irq_cpuid;
	struct sn_irq_info *tmp_irq_info;
	int i, foundmatch;

234
	rcu_read_lock();
L
Linus Torvalds 已提交
235 236
	if (pdacpu(cpu)->sn_last_irq == irq) {
		foundmatch = 0;
237 238 239 240 241
		for (i = pdacpu(cpu)->sn_last_irq - 1;
		     i && !foundmatch; i--) {
			list_for_each_entry_rcu(tmp_irq_info,
						sn_irq_lh[i],
						list) {
L
Linus Torvalds 已提交
242
				if (tmp_irq_info->irq_cpuid == cpu) {
243
					foundmatch = 1;
L
Linus Torvalds 已提交
244 245 246 247 248 249 250 251 252
					break;
				}
			}
		}
		pdacpu(cpu)->sn_last_irq = i;
	}

	if (pdacpu(cpu)->sn_first_irq == irq) {
		foundmatch = 0;
253 254 255 256 257
		for (i = pdacpu(cpu)->sn_first_irq + 1;
		     i < NR_IRQS && !foundmatch; i++) {
			list_for_each_entry_rcu(tmp_irq_info,
						sn_irq_lh[i],
						list) {
L
Linus Torvalds 已提交
258
				if (tmp_irq_info->irq_cpuid == cpu) {
259
					foundmatch = 1;
L
Linus Torvalds 已提交
260 261 262 263 264 265
					break;
				}
			}
		}
		pdacpu(cpu)->sn_first_irq = ((i == NR_IRQS) ? 0 : i);
	}
266
	rcu_read_unlock();
L
Linus Torvalds 已提交
267 268
}

269
static void sn_irq_info_free(struct rcu_head *head)
L
Linus Torvalds 已提交
270 271 272
{
	struct sn_irq_info *sn_irq_info;

273
	sn_irq_info = container_of(head, struct sn_irq_info, rcu);
L
Linus Torvalds 已提交
274 275 276 277 278 279 280 281 282
	kfree(sn_irq_info);
}

void sn_irq_fixup(struct pci_dev *pci_dev, struct sn_irq_info *sn_irq_info)
{
	nasid_t nasid = sn_irq_info->irq_nasid;
	int slice = sn_irq_info->irq_slice;
	int cpu = nasid_slice_to_cpuid(nasid, slice);

283
	pci_dev_get(pci_dev);
L
Linus Torvalds 已提交
284 285 286 287
	sn_irq_info->irq_cpuid = cpu;
	sn_irq_info->irq_pciioinfo = SN_PCIDEV_INFO(pci_dev);

	/* link it into the sn_irq[irq] list */
288 289 290
	spin_lock(&sn_irq_info_lock);
	list_add_rcu(&sn_irq_info->list, sn_irq_lh[sn_irq_info->irq_irq]);
	spin_unlock(&sn_irq_info_lock);
L
Linus Torvalds 已提交
291 292 293 294

	(void)register_intr_pda(sn_irq_info);
}

295 296 297 298 299 300 301 302 303
void sn_irq_unfixup(struct pci_dev *pci_dev)
{
	struct sn_irq_info *sn_irq_info;

	/* Only cleanup IRQ stuff if this device has a host bus context */
	if (!SN_PCIDEV_BUSSOFT(pci_dev))
		return;

	sn_irq_info = SN_PCIDEV_INFO(pci_dev)->pdi_sn_irq_info;
304 305
	if (!sn_irq_info || !sn_irq_info->irq_irq) {
		kfree(sn_irq_info);
306
		return;
307
	}
308 309 310 311 312 313 314 315 316

	unregister_intr_pda(sn_irq_info);
	spin_lock(&sn_irq_info_lock);
	list_del_rcu(&sn_irq_info->list);
	spin_unlock(&sn_irq_info_lock);
	call_rcu(&sn_irq_info->rcu, sn_irq_info_free);
	pci_dev_put(pci_dev);
}

317 318 319 320 321 322 323 324 325 326
static inline void
sn_call_force_intr_provider(struct sn_irq_info *sn_irq_info)
{
	struct sn_pcibus_provider *pci_provider;

	pci_provider = sn_pci_provider[sn_irq_info->irq_bridge_type];
	if (pci_provider && pci_provider->force_interrupt)
		(*pci_provider->force_interrupt)(sn_irq_info);
}

L
Linus Torvalds 已提交
327 328 329 330 331 332
static void force_interrupt(int irq)
{
	struct sn_irq_info *sn_irq_info;

	if (!sn_ioif_inited)
		return;
333 334

	rcu_read_lock();
335 336 337
	list_for_each_entry_rcu(sn_irq_info, sn_irq_lh[irq], list)
		sn_call_force_intr_provider(sn_irq_info);

338
	rcu_read_unlock();
L
Linus Torvalds 已提交
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
}

/*
 * Check for lost interrupts.  If the PIC int_status reg. says that
 * an interrupt has been sent, but not handled, and the interrupt
 * is not pending in either the cpu irr regs or in the soft irr regs,
 * and the interrupt is not in service, then the interrupt may have
 * been lost.  Force an interrupt on that pin.  It is possible that
 * the interrupt is in flight, so we may generate a spurious interrupt,
 * but we should never miss a real lost interrupt.
 */
static void sn_check_intr(int irq, struct sn_irq_info *sn_irq_info)
{
	uint64_t regval;
	int irr_reg_num;
	int irr_bit;
	uint64_t irr_reg;
	struct pcidev_info *pcidev_info;
	struct pcibus_info *pcibus_info;

359 360 361 362 363 364 365 366
	/*
	 * Bridge types attached to TIO (anything but PIC) do not need this WAR
	 * since they do not target Shub II interrupt registers.  If that
	 * ever changes, this check needs to accomodate.
	 */
	if (sn_irq_info->irq_bridge_type != PCIIO_ASIC_TYPE_PIC)
		return;

L
Linus Torvalds 已提交
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
	pcidev_info = (struct pcidev_info *)sn_irq_info->irq_pciioinfo;
	if (!pcidev_info)
		return;

	pcibus_info =
	    (struct pcibus_info *)pcidev_info->pdi_host_pcidev_info->
	    pdi_pcibus_info;
	regval = pcireg_intr_status_get(pcibus_info);

	irr_reg_num = irq_to_vector(irq) / 64;
	irr_bit = irq_to_vector(irq) % 64;
	switch (irr_reg_num) {
	case 0:
		irr_reg = ia64_getreg(_IA64_REG_CR_IRR0);
		break;
	case 1:
		irr_reg = ia64_getreg(_IA64_REG_CR_IRR1);
		break;
	case 2:
		irr_reg = ia64_getreg(_IA64_REG_CR_IRR2);
		break;
	case 3:
		irr_reg = ia64_getreg(_IA64_REG_CR_IRR3);
		break;
	}
	if (!test_bit(irr_bit, &irr_reg)) {
393 394 395 396 397 398
		if (!test_bit(irq, pda->sn_in_service_ivecs)) {
			regval &= 0xff;
			if (sn_irq_info->irq_int_bit & regval &
			    sn_irq_info->irq_last_intr) {
				regval &= ~(sn_irq_info->irq_int_bit & regval);
				sn_call_force_intr_provider(sn_irq_info);
L
Linus Torvalds 已提交
399 400 401 402 403 404 405 406
			}
		}
	}
	sn_irq_info->irq_last_intr = regval;
}

void sn_lb_int_war_check(void)
{
407
	struct sn_irq_info *sn_irq_info;
L
Linus Torvalds 已提交
408 409 410 411
	int i;

	if (!sn_ioif_inited || pda->sn_first_irq == 0)
		return;
412 413

	rcu_read_lock();
L
Linus Torvalds 已提交
414
	for (i = pda->sn_first_irq; i <= pda->sn_last_irq; i++) {
415
		list_for_each_entry_rcu(sn_irq_info, sn_irq_lh[i], list) {
416
			sn_check_intr(i, sn_irq_info);
L
Linus Torvalds 已提交
417 418
		}
	}
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
	rcu_read_unlock();
}

void sn_irq_lh_init(void)
{
	int i;

	sn_irq_lh = kmalloc(sizeof(struct list_head *) * NR_IRQS, GFP_KERNEL);
	if (!sn_irq_lh)
		panic("SN PCI INIT: Failed to allocate memory for PCI init\n");

	for (i = 0; i < NR_IRQS; i++) {
		sn_irq_lh[i] = kmalloc(sizeof(struct list_head), GFP_KERNEL);
		if (!sn_irq_lh[i])
			panic("SN PCI INIT: Failed IRQ memory allocation\n");

		INIT_LIST_HEAD(sn_irq_lh[i]);
	}

L
Linus Torvalds 已提交
438
}