msi.c 17.3 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8
/*
 * File:	msi.c
 * Purpose:	PCI Message Signaled Interrupt (MSI)
 *
 * Copyright (C) 2003-2004 Intel
 * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)
 */

9
#include <linux/err.h>
Linus Torvalds's avatar
Linus Torvalds committed
10 11 12 13 14 15 16 17
#include <linux/mm.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/smp_lock.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
18
#include <linux/msi.h>
Linus Torvalds's avatar
Linus Torvalds committed
19 20 21 22 23 24 25 26 27 28

#include <asm/errno.h>
#include <asm/io.h>
#include <asm/smp.h>

#include "pci.h"
#include "msi.h"

static int pci_msi_enable = 1;

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
static void msi_set_enable(struct pci_dev *dev, int enable)
{
	int pos;
	u16 control;

	pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
	if (pos) {
		pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control);
		control &= ~PCI_MSI_FLAGS_ENABLE;
		if (enable)
			control |= PCI_MSI_FLAGS_ENABLE;
		pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control);
	}
}

static void msix_set_enable(struct pci_dev *dev, int enable)
{
	int pos;
	u16 control;

	pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
	if (pos) {
		pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control);
		control &= ~PCI_MSIX_FLAGS_ENABLE;
		if (enable)
			control |= PCI_MSIX_FLAGS_ENABLE;
		pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control);
	}
}

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
static void msix_flush_writes(unsigned int irq)
{
	struct msi_desc *entry;

	entry = get_irq_msi(irq);
	BUG_ON(!entry || !entry->dev);
	switch (entry->msi_attrib.type) {
	case PCI_CAP_ID_MSI:
		/* nothing to do */
		break;
	case PCI_CAP_ID_MSIX:
	{
		int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE +
			PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
		readl(entry->mask_base + offset);
		break;
	}
	default:
		BUG();
		break;
	}
}

82
static void msi_set_mask_bit(unsigned int irq, int flag)
Linus Torvalds's avatar
Linus Torvalds committed
83 84 85
{
	struct msi_desc *entry;

86
	entry = get_irq_msi(irq);
87
	BUG_ON(!entry || !entry->dev);
Linus Torvalds's avatar
Linus Torvalds committed
88 89
	switch (entry->msi_attrib.type) {
	case PCI_CAP_ID_MSI:
90
		if (entry->msi_attrib.maskbit) {
Satoru Takeuchi's avatar
Satoru Takeuchi committed
91 92
			int pos;
			u32 mask_bits;
93 94 95 96 97 98

			pos = (long)entry->mask_base;
			pci_read_config_dword(entry->dev, pos, &mask_bits);
			mask_bits &= ~(1);
			mask_bits |= flag;
			pci_write_config_dword(entry->dev, pos, mask_bits);
99 100
		} else {
			msi_set_enable(entry->dev, !flag);
101
		}
Linus Torvalds's avatar
Linus Torvalds committed
102 103 104 105 106 107
		break;
	case PCI_CAP_ID_MSIX:
	{
		int offset = entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE +
			PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET;
		writel(flag, entry->mask_base + offset);
108
		readl(entry->mask_base + offset);
Linus Torvalds's avatar
Linus Torvalds committed
109 110 111
		break;
	}
	default:
112
		BUG();
Linus Torvalds's avatar
Linus Torvalds committed
113 114
		break;
	}
115
	entry->msi_attrib.masked = !!flag;
Linus Torvalds's avatar
Linus Torvalds committed
116 117
}

118
void read_msi_msg(unsigned int irq, struct msi_msg *msg)
Linus Torvalds's avatar
Linus Torvalds committed
119
{
120
	struct msi_desc *entry = get_irq_msi(irq);
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
	switch(entry->msi_attrib.type) {
	case PCI_CAP_ID_MSI:
	{
		struct pci_dev *dev = entry->dev;
		int pos = entry->msi_attrib.pos;
		u16 data;

		pci_read_config_dword(dev, msi_lower_address_reg(pos),
					&msg->address_lo);
		if (entry->msi_attrib.is_64) {
			pci_read_config_dword(dev, msi_upper_address_reg(pos),
						&msg->address_hi);
			pci_read_config_word(dev, msi_data_reg(pos, 1), &data);
		} else {
			msg->address_hi = 0;
			pci_read_config_word(dev, msi_data_reg(pos, 1), &data);
		}
		msg->data = data;
		break;
	}
	case PCI_CAP_ID_MSIX:
	{
		void __iomem *base;
		base = entry->mask_base +
			entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE;

		msg->address_lo = readl(base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET);
		msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET);
		msg->data = readl(base + PCI_MSIX_ENTRY_DATA_OFFSET);
 		break;
 	}
 	default:
		BUG();
	}
}
Linus Torvalds's avatar
Linus Torvalds committed
156

157
void write_msi_msg(unsigned int irq, struct msi_msg *msg)
158
{
159
	struct msi_desc *entry = get_irq_msi(irq);
Linus Torvalds's avatar
Linus Torvalds committed
160 161 162
	switch (entry->msi_attrib.type) {
	case PCI_CAP_ID_MSI:
	{
163 164 165 166 167 168 169 170 171 172 173 174 175 176
		struct pci_dev *dev = entry->dev;
		int pos = entry->msi_attrib.pos;

		pci_write_config_dword(dev, msi_lower_address_reg(pos),
					msg->address_lo);
		if (entry->msi_attrib.is_64) {
			pci_write_config_dword(dev, msi_upper_address_reg(pos),
						msg->address_hi);
			pci_write_config_word(dev, msi_data_reg(pos, 1),
						msg->data);
		} else {
			pci_write_config_word(dev, msi_data_reg(pos, 0),
						msg->data);
		}
Linus Torvalds's avatar
Linus Torvalds committed
177 178 179 180
		break;
	}
	case PCI_CAP_ID_MSIX:
	{
181 182 183 184 185 186 187 188 189
		void __iomem *base;
		base = entry->mask_base +
			entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE;

		writel(msg->address_lo,
			base + PCI_MSIX_ENTRY_LOWER_ADDR_OFFSET);
		writel(msg->address_hi,
			base + PCI_MSIX_ENTRY_UPPER_ADDR_OFFSET);
		writel(msg->data, base + PCI_MSIX_ENTRY_DATA_OFFSET);
Linus Torvalds's avatar
Linus Torvalds committed
190 191 192
		break;
	}
	default:
193
		BUG();
Linus Torvalds's avatar
Linus Torvalds committed
194
	}
195
	entry->msg = *msg;
Linus Torvalds's avatar
Linus Torvalds committed
196
}
197

198
void mask_msi_irq(unsigned int irq)
Linus Torvalds's avatar
Linus Torvalds committed
199
{
200
	msi_set_mask_bit(irq, 1);
201
	msix_flush_writes(irq);
Linus Torvalds's avatar
Linus Torvalds committed
202 203
}

204
void unmask_msi_irq(unsigned int irq)
Linus Torvalds's avatar
Linus Torvalds committed
205
{
206
	msi_set_mask_bit(irq, 0);
207
	msix_flush_writes(irq);
Linus Torvalds's avatar
Linus Torvalds committed
208 209
}

210
static int msi_free_irq(struct pci_dev* dev, int irq);
Satoru Takeuchi's avatar
Satoru Takeuchi committed
211

Linus Torvalds's avatar
Linus Torvalds committed
212 213 214 215 216

static struct msi_desc* alloc_msi_entry(void)
{
	struct msi_desc *entry;

Michael Ellerman's avatar
Michael Ellerman committed
217
	entry = kzalloc(sizeof(struct msi_desc), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
218 219 220
	if (!entry)
		return NULL;

221 222
	INIT_LIST_HEAD(&entry->list);
	entry->irq = 0;
Linus Torvalds's avatar
Linus Torvalds committed
223 224 225 226 227
	entry->dev = NULL;

	return entry;
}

228
#ifdef CONFIG_PM
229
static void __pci_restore_msi_state(struct pci_dev *dev)
230
{
231
	int pos;
232
	u16 control;
233
	struct msi_desc *entry;
234

235 236 237
	if (!dev->msi_enabled)
		return;

238 239
	entry = get_irq_msi(dev->irq);
	pos = entry->msi_attrib.pos;
240

241 242
	pci_intx(dev, 0);		/* disable intx */
	msi_set_enable(dev, 0);
243 244 245 246 247 248 249 250
	write_msi_msg(dev->irq, &entry->msg);
	if (entry->msi_attrib.maskbit)
		msi_set_mask_bit(dev->irq, entry->msi_attrib.masked);

	pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control);
	control &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
	if (entry->msi_attrib.maskbit || !entry->msi_attrib.masked)
		control |= PCI_MSI_FLAGS_ENABLE;
251
	pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control);
252 253 254
}

static void __pci_restore_msix_state(struct pci_dev *dev)
255 256 257
{
	int pos;
	struct msi_desc *entry;
258
	u16 control;
259

Eric W. Biederman's avatar
Eric W. Biederman committed
260 261 262
	if (!dev->msix_enabled)
		return;

263
	/* route the table */
264 265
	pci_intx(dev, 0);		/* disable intx */
	msix_set_enable(dev, 0);
266

267 268 269
	list_for_each_entry(entry, &dev->msi_list, list) {
		write_msi_msg(entry->irq, &entry->msg);
		msi_set_mask_bit(entry->irq, entry->msi_attrib.masked);
270 271
	}

272 273
	BUG_ON(list_empty(&dev->msi_list));
	entry = list_entry(dev->msi_list.next, struct msi_desc, list);
274
	pos = entry->msi_attrib.pos;
275 276 277 278
	pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control);
	control &= ~PCI_MSIX_FLAGS_MASKALL;
	control |= PCI_MSIX_FLAGS_ENABLE;
	pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control);
279
}
280 281 282 283 284 285

void pci_restore_msi_state(struct pci_dev *dev)
{
	__pci_restore_msi_state(dev);
	__pci_restore_msix_state(dev);
}
Satoru Takeuchi's avatar
Satoru Takeuchi committed
286
#endif	/* CONFIG_PM */
287

Linus Torvalds's avatar
Linus Torvalds committed
288 289 290 291
/**
 * msi_capability_init - configure device's MSI capability structure
 * @dev: pointer to the pci_dev data structure of MSI device function
 *
292
 * Setup the MSI capability structure of device function with a single
293
 * MSI irq, regardless of device function is capable of handling
Linus Torvalds's avatar
Linus Torvalds committed
294
 * multiple messages. A return of zero indicates the successful setup
295
 * of an entry zero with the new MSI irq or non-zero for otherwise.
Linus Torvalds's avatar
Linus Torvalds committed
296 297 298 299
 **/
static int msi_capability_init(struct pci_dev *dev)
{
	struct msi_desc *entry;
300
	int pos, ret;
Linus Torvalds's avatar
Linus Torvalds committed
301 302
	u16 control;

303 304
	msi_set_enable(dev, 0);	/* Ensure msi is disabled as I set it up */

Linus Torvalds's avatar
Linus Torvalds committed
305 306 307
   	pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
	pci_read_config_word(dev, msi_control_reg(pos), &control);
	/* MSI Entry Initialization */
308 309 310
	entry = alloc_msi_entry();
	if (!entry)
		return -ENOMEM;
311

Linus Torvalds's avatar
Linus Torvalds committed
312
	entry->msi_attrib.type = PCI_CAP_ID_MSI;
313
	entry->msi_attrib.is_64 = is_64bit_address(control);
Linus Torvalds's avatar
Linus Torvalds committed
314 315
	entry->msi_attrib.entry_nr = 0;
	entry->msi_attrib.maskbit = is_mask_bit_support(control);
316
	entry->msi_attrib.masked = 1;
317
	entry->msi_attrib.default_irq = dev->irq;	/* Save IOAPIC IRQ */
318
	entry->msi_attrib.pos = pos;
Linus Torvalds's avatar
Linus Torvalds committed
319 320 321 322
	if (is_mask_bit_support(control)) {
		entry->mask_base = (void __iomem *)(long)msi_mask_bits_reg(pos,
				is_64bit_address(control));
	}
323 324 325 326 327 328 329 330 331 332 333 334 335 336
	entry->dev = dev;
	if (entry->msi_attrib.maskbit) {
		unsigned int maskbits, temp;
		/* All MSIs are unmasked by default, Mask them all */
		pci_read_config_dword(dev,
			msi_mask_bits_reg(pos, is_64bit_address(control)),
			&maskbits);
		temp = (1 << multi_msi_capable(control));
		temp = ((temp - 1) & ~temp);
		maskbits |= temp;
		pci_write_config_dword(dev,
			msi_mask_bits_reg(pos, is_64bit_address(control)),
			maskbits);
	}
Linus Torvalds's avatar
Linus Torvalds committed
337
	/* Configure MSI capability structure */
338 339
	ret = arch_setup_msi_irq(dev, entry);
	if (ret) {
Michael Ellerman's avatar
Michael Ellerman committed
340
		kfree(entry);
341
		return ret;
342
	}
343
	list_add(&entry->list, &dev->msi_list);
344

Linus Torvalds's avatar
Linus Torvalds committed
345
	/* Set MSI enabled bits	 */
346 347 348
	pci_intx(dev, 0);		/* disable intx */
	msi_set_enable(dev, 1);
	dev->msi_enabled = 1;
Linus Torvalds's avatar
Linus Torvalds committed
349

350
	dev->irq = entry->irq;
Linus Torvalds's avatar
Linus Torvalds committed
351 352 353 354 355 356
	return 0;
}

/**
 * msix_capability_init - configure device's MSI-X capability
 * @dev: pointer to the pci_dev data structure of MSI-X device function
Randy Dunlap's avatar
Randy Dunlap committed
357 358
 * @entries: pointer to an array of struct msix_entry entries
 * @nvec: number of @entries
Linus Torvalds's avatar
Linus Torvalds committed
359
 *
360
 * Setup the MSI-X capability structure of device function with a
361 362
 * single MSI-X irq. A return of zero indicates the successful setup of
 * requested MSI-X entries with allocated irqs or non-zero for otherwise.
Linus Torvalds's avatar
Linus Torvalds committed
363 364 365 366
 **/
static int msix_capability_init(struct pci_dev *dev,
				struct msix_entry *entries, int nvec)
{
367
	struct msi_desc *entry;
368
	int irq, pos, i, j, nr_entries, ret;
369 370
	unsigned long phys_addr;
	u32 table_offset;
Linus Torvalds's avatar
Linus Torvalds committed
371 372 373 374
 	u16 control;
	u8 bir;
	void __iomem *base;

375 376
	msix_set_enable(dev, 0);/* Ensure msix is disabled as I set it up */

Linus Torvalds's avatar
Linus Torvalds committed
377 378 379 380
   	pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
	/* Request & Map MSI-X table region */
 	pci_read_config_word(dev, msi_control_reg(pos), &control);
	nr_entries = multi_msix_capable(control);
381 382

 	pci_read_config_dword(dev, msix_table_offset_reg(pos), &table_offset);
Linus Torvalds's avatar
Linus Torvalds committed
383
	bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK);
384 385
	table_offset &= ~PCI_MSIX_FLAGS_BIRMASK;
	phys_addr = pci_resource_start (dev, bir) + table_offset;
Linus Torvalds's avatar
Linus Torvalds committed
386 387 388 389 390 391
	base = ioremap_nocache(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE);
	if (base == NULL)
		return -ENOMEM;

	/* MSI-X Table Initialization */
	for (i = 0; i < nvec; i++) {
392 393
		entry = alloc_msi_entry();
		if (!entry)
Linus Torvalds's avatar
Linus Torvalds committed
394 395 396 397
			break;

 		j = entries[i].entry;
		entry->msi_attrib.type = PCI_CAP_ID_MSIX;
398
		entry->msi_attrib.is_64 = 1;
Linus Torvalds's avatar
Linus Torvalds committed
399 400
		entry->msi_attrib.entry_nr = j;
		entry->msi_attrib.maskbit = 1;
401
		entry->msi_attrib.masked = 1;
402
		entry->msi_attrib.default_irq = dev->irq;
403
		entry->msi_attrib.pos = pos;
Linus Torvalds's avatar
Linus Torvalds committed
404 405
		entry->dev = dev;
		entry->mask_base = base;
406 407

		/* Configure MSI-X capability structure */
408 409
		ret = arch_setup_msi_irq(dev, entry);
		if (ret) {
Michael Ellerman's avatar
Michael Ellerman committed
410
			kfree(entry);
411 412
			break;
		}
413
 		entries[i].vector = entry->irq;
414
		list_add(&entry->list, &dev->msi_list);
Linus Torvalds's avatar
Linus Torvalds committed
415 416
	}
	if (i != nvec) {
417
		int avail = i - 1;
Linus Torvalds's avatar
Linus Torvalds committed
418 419
		i--;
		for (; i >= 0; i--) {
420 421
			irq = (entries + i)->vector;
			msi_free_irq(dev, irq);
Linus Torvalds's avatar
Linus Torvalds committed
422 423
			(entries + i)->vector = 0;
		}
424 425 426 427 428 429
		/* If we had some success report the number of irqs
		 * we succeeded in setting up.
		 */
		if (avail <= 0)
			avail = -EBUSY;
		return avail;
Linus Torvalds's avatar
Linus Torvalds committed
430 431
	}
	/* Set MSI-X enabled bits */
432 433 434
	pci_intx(dev, 0);		/* disable intx */
	msix_set_enable(dev, 1);
	dev->msix_enabled = 1;
Linus Torvalds's avatar
Linus Torvalds committed
435 436 437 438

	return 0;
}

439
/**
440
 * pci_msi_check_device - check whether MSI may be enabled on a device
441
 * @dev: pointer to the pci_dev data structure of MSI device function
442
 * @nvec: how many MSIs have been requested ?
443
 * @type: are we checking for MSI or MSI-X ?
444
 *
445
 * Look at global flags, the device itself, and its parent busses
446 447
 * to determine if MSI/-X are supported for the device. If MSI/-X is
 * supported return 0, else return an error code.
448
 **/
449
static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type)
450 451
{
	struct pci_bus *bus;
452
	int ret;
453

454
	/* MSI must be globally enabled and supported by the device */
455 456 457
	if (!pci_msi_enable || !dev || dev->no_msi)
		return -EINVAL;

458 459 460 461 462 463 464 465
	/*
	 * You can't ask to have 0 or less MSIs configured.
	 *  a) it's stupid ..
	 *  b) the list manipulation code assumes nvec >= 1.
	 */
	if (nvec < 1)
		return -ERANGE;

466 467 468 469 470 471
	/* Any bridge which does NOT route MSI transactions from it's
	 * secondary bus to it's primary bus must set NO_MSI flag on
	 * the secondary pci_bus.
	 * We expect only arch-specific PCI host bus controller driver
	 * or quirks for specific PCI bridges to be setting NO_MSI.
	 */
472 473 474 475
	for (bus = dev->bus; bus; bus = bus->parent)
		if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI)
			return -EINVAL;

476 477 478 479
	ret = arch_msi_check_device(dev, nvec, type);
	if (ret)
		return ret;

480 481 482
	if (!pci_find_capability(dev, type))
		return -EINVAL;

483 484 485
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
486 487 488 489 490
/**
 * pci_enable_msi - configure device's MSI capability structure
 * @dev: pointer to the pci_dev data structure of MSI device function
 *
 * Setup the MSI capability structure of device function with
491
 * a single MSI irq upon its software driver call to request for
Linus Torvalds's avatar
Linus Torvalds committed
492 493
 * MSI mode enabled on its hardware device function. A return of zero
 * indicates the successful setup of an entry zero with the new MSI
494
 * irq or non-zero for otherwise.
Linus Torvalds's avatar
Linus Torvalds committed
495 496 497
 **/
int pci_enable_msi(struct pci_dev* dev)
{
498
	int status;
Linus Torvalds's avatar
Linus Torvalds committed
499

500 501 502
	status = pci_msi_check_device(dev, 1, PCI_CAP_ID_MSI);
	if (status)
		return status;
Linus Torvalds's avatar
Linus Torvalds committed
503

Eric W. Biederman's avatar
Eric W. Biederman committed
504
	WARN_ON(!!dev->msi_enabled);
Linus Torvalds's avatar
Linus Torvalds committed
505

506
	/* Check whether driver already requested for MSI-X irqs */
507 508 509 510 511
	if (dev->msix_enabled) {
		printk(KERN_INFO "PCI: %s: Can't enable MSI.  "
			"Device already has MSI-X enabled\n",
			pci_name(dev));
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
512 513 514 515
	}
	status = msi_capability_init(dev);
	return status;
}
516
EXPORT_SYMBOL(pci_enable_msi);
Linus Torvalds's avatar
Linus Torvalds committed
517 518 519 520

void pci_disable_msi(struct pci_dev* dev)
{
	struct msi_desc *entry;
521
	int default_irq;
Linus Torvalds's avatar
Linus Torvalds committed
522

523
	if (!pci_msi_enable || !dev || !dev->msi_enabled)
Eric W. Biederman's avatar
Eric W. Biederman committed
524 525
		return;

526 527 528
	msi_set_enable(dev, 0);
	pci_intx(dev, 1);		/* enable intx */
	dev->msi_enabled = 0;
529

530 531 532
	BUG_ON(list_empty(&dev->msi_list));
	entry = list_entry(dev->msi_list.next, struct msi_desc, list);
	if (!entry->dev || entry->msi_attrib.type != PCI_CAP_ID_MSI) {
Linus Torvalds's avatar
Linus Torvalds committed
533 534
		return;
	}
535 536

	default_irq = entry->msi_attrib.default_irq;
537
	msi_free_irq(dev, entry->irq);
538 539 540

	/* Restore dev->irq to its default pin-assertion irq */
	dev->irq = default_irq;
Linus Torvalds's avatar
Linus Torvalds committed
541
}
542
EXPORT_SYMBOL(pci_disable_msi);
Linus Torvalds's avatar
Linus Torvalds committed
543

544
static int msi_free_irq(struct pci_dev* dev, int irq)
Linus Torvalds's avatar
Linus Torvalds committed
545 546
{
	struct msi_desc *entry;
547
	int entry_nr, type;
Linus Torvalds's avatar
Linus Torvalds committed
548 549
	void __iomem *base;

550 551
	BUG_ON(irq_has_action(irq));

552
	entry = get_irq_msi(irq);
Linus Torvalds's avatar
Linus Torvalds committed
553 554 555 556 557 558
	if (!entry || entry->dev != dev) {
		return -EINVAL;
	}
	type = entry->msi_attrib.type;
	entry_nr = entry->msi_attrib.entry_nr;
	base = entry->mask_base;
559
	list_del(&entry->list);
Linus Torvalds's avatar
Linus Torvalds committed
560

561
	arch_teardown_msi_irq(irq);
Michael Ellerman's avatar
Michael Ellerman committed
562
	kfree(entry);
Linus Torvalds's avatar
Linus Torvalds committed
563 564

	if (type == PCI_CAP_ID_MSIX) {
565 566
		writel(1, base + entry_nr * PCI_MSIX_ENTRY_SIZE +
			PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET);
Linus Torvalds's avatar
Linus Torvalds committed
567

568
		if (list_empty(&dev->msi_list))
Linus Torvalds's avatar
Linus Torvalds committed
569 570 571 572 573 574 575 576 577
			iounmap(base);
	}

	return 0;
}

/**
 * pci_enable_msix - configure device's MSI-X capability structure
 * @dev: pointer to the pci_dev data structure of MSI-X device function
578
 * @entries: pointer to an array of MSI-X entries
579
 * @nvec: number of MSI-X irqs requested for allocation by device driver
Linus Torvalds's avatar
Linus Torvalds committed
580 581
 *
 * Setup the MSI-X capability structure of device function with the number
582
 * of requested irqs upon its software driver call to request for
Linus Torvalds's avatar
Linus Torvalds committed
583 584
 * MSI-X mode enabled on its hardware device function. A return of zero
 * indicates the successful configuration of MSI-X capability structure
585
 * with new allocated MSI-X irqs. A return of < 0 indicates a failure.
Linus Torvalds's avatar
Linus Torvalds committed
586
 * Or a return of > 0 indicates that driver request is exceeding the number
587
 * of irqs available. Driver should use the returned value to re-send
Linus Torvalds's avatar
Linus Torvalds committed
588 589 590 591
 * its request.
 **/
int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec)
{
592
	int status, pos, nr_entries;
Eric W. Biederman's avatar
Eric W. Biederman committed
593
	int i, j;
Linus Torvalds's avatar
Linus Torvalds committed
594 595
	u16 control;

596
	if (!entries)
Linus Torvalds's avatar
Linus Torvalds committed
597 598
 		return -EINVAL;

599 600 601 602
	status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX);
	if (status)
		return status;

603
	pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
Linus Torvalds's avatar
Linus Torvalds committed
604 605 606 607 608 609 610 611 612 613 614 615 616 617
	pci_read_config_word(dev, msi_control_reg(pos), &control);
	nr_entries = multi_msix_capable(control);
	if (nvec > nr_entries)
		return -EINVAL;

	/* Check for any invalid entries */
	for (i = 0; i < nvec; i++) {
		if (entries[i].entry >= nr_entries)
			return -EINVAL;		/* invalid entry */
		for (j = i + 1; j < nvec; j++) {
			if (entries[i].entry == entries[j].entry)
				return -EINVAL;	/* duplicate entry */
		}
	}
Eric W. Biederman's avatar
Eric W. Biederman committed
618
	WARN_ON(!!dev->msix_enabled);
619

620
	/* Check whether driver already requested for MSI irq */
621
   	if (dev->msi_enabled) {
Linus Torvalds's avatar
Linus Torvalds committed
622
		printk(KERN_INFO "PCI: %s: Can't enable MSI-X.  "
623
		       "Device already has an MSI irq assigned\n",
Linus Torvalds's avatar
Linus Torvalds committed
624 625 626 627 628 629
		       pci_name(dev));
		return -EINVAL;
	}
	status = msix_capability_init(dev, entries, nvec);
	return status;
}
630
EXPORT_SYMBOL(pci_enable_msix);
Linus Torvalds's avatar
Linus Torvalds committed
631

632
static void msix_free_all_irqs(struct pci_dev *dev)
Linus Torvalds's avatar
Linus Torvalds committed
633
{
634
	struct msi_desc *entry;
635

636 637
	list_for_each_entry(entry, &dev->msi_list, list)
		msi_free_irq(dev, entry->irq);
638 639 640 641
}

void pci_disable_msix(struct pci_dev* dev)
{
642
	if (!pci_msi_enable || !dev || !dev->msix_enabled)
Eric W. Biederman's avatar
Eric W. Biederman committed
643 644
		return;

645 646 647
	msix_set_enable(dev, 0);
	pci_intx(dev, 1);		/* enable intx */
	dev->msix_enabled = 0;
648

649
	msix_free_all_irqs(dev);
Linus Torvalds's avatar
Linus Torvalds committed
650
}
651
EXPORT_SYMBOL(pci_disable_msix);
Linus Torvalds's avatar
Linus Torvalds committed
652 653

/**
654
 * msi_remove_pci_irq_vectors - reclaim MSI(X) irqs to unused state
Linus Torvalds's avatar
Linus Torvalds committed
655 656
 * @dev: pointer to the pci_dev data structure of MSI(X) device function
 *
657
 * Being called during hotplug remove, from which the device function
658
 * is hot-removed. All previous assigned MSI/MSI-X irqs, if
Linus Torvalds's avatar
Linus Torvalds committed
659 660 661 662 663 664 665 666
 * allocated for this device function, are reclaimed to unused state,
 * which may be used later on.
 **/
void msi_remove_pci_irq_vectors(struct pci_dev* dev)
{
	if (!pci_msi_enable || !dev)
 		return;

667 668 669 670 671 672
	if (dev->msi_enabled) {
		struct msi_desc *entry;
		BUG_ON(list_empty(&dev->msi_list));
		entry = list_entry(dev->msi_list.next, struct msi_desc, list);
		msi_free_irq(dev, entry->irq);
	}
Linus Torvalds's avatar
Linus Torvalds committed
673

674 675
	if (dev->msix_enabled)
		msix_free_all_irqs(dev);
Linus Torvalds's avatar
Linus Torvalds committed
676 677
}

678 679 680 681
void pci_no_msi(void)
{
	pci_msi_enable = 0;
}
682

683 684 685 686 687
void pci_msi_init_pci_dev(struct pci_dev *dev)
{
	INIT_LIST_HEAD(&dev->msi_list);
}

688 689 690 691 692 693 694 695 696

/* Arch hooks */

int __attribute__ ((weak))
arch_msi_check_device(struct pci_dev* dev, int nvec, int type)
{
	return 0;
}