X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=drivers%2Fpci%2Fmsi.c;h=77b68eaf021e43152c4bbd257bd190a4a210473c;hb=89713422a768458a0d375f0c2f3586cd5ccde6a1;hp=88362f1bd9cfceaf9575215c96865e314e3bed8a;hpb=7fe3730de729b758e9f69b862b9255d998671b5f;p=safe%2Fjmp%2Flinux-2.6 diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index 88362f1..77b68ea 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -12,34 +12,81 @@ #include #include #include -#include #include #include #include - -#include -#include -#include +#include +#include +#include +#include #include "pci.h" #include "msi.h" static int pci_msi_enable = 1; -static void msi_set_enable(struct pci_dev *dev, int enable) +/* Arch hooks */ + +#ifndef arch_msi_check_device +int arch_msi_check_device(struct pci_dev *dev, int nvec, int type) { - int pos; - u16 control; + return 0; +} +#endif - 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); +#ifndef arch_setup_msi_irqs +int arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) +{ + struct msi_desc *entry; + int ret; + + /* + * If an architecture wants to support multiple MSI, it needs to + * override arch_setup_msi_irqs() + */ + if (type == PCI_CAP_ID_MSI && nvec > 1) + return 1; + + list_for_each_entry(entry, &dev->msi_list, list) { + ret = arch_setup_msi_irq(dev, entry); + if (ret < 0) + return ret; + if (ret > 0) + return -ENOSPC; + } + + return 0; +} +#endif + +#ifndef arch_teardown_msi_irqs +void arch_teardown_msi_irqs(struct pci_dev *dev) +{ + struct msi_desc *entry; + + list_for_each_entry(entry, &dev->msi_list, list) { + int i, nvec; + if (entry->irq == 0) + continue; + nvec = 1 << entry->msi_attrib.multiple; + for (i = 0; i < nvec; i++) + arch_teardown_msi_irq(entry->irq + i); } } +#endif + +static void msi_set_enable(struct pci_dev *dev, int pos, int enable) +{ + u16 control; + + BUG_ON(!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) { @@ -56,71 +103,107 @@ static void msix_set_enable(struct pci_dev *dev, int enable) } } -static void msix_flush_writes(unsigned int irq) +static inline __attribute_const__ u32 msi_mask(unsigned x) { - struct msi_desc *entry; + /* Don't shift by >= width of type */ + if (x >= 5) + return 0xffffffff; + return (1 << (1 << x)) - 1; +} - 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; - } +static inline __attribute_const__ u32 msi_capable_mask(u16 control) +{ + return msi_mask((control >> 1) & 7); } -static void msi_set_mask_bit(unsigned int irq, int flag) +static inline __attribute_const__ u32 msi_enabled_mask(u16 control) { - struct msi_desc *entry; + return msi_mask((control >> 4) & 7); +} - entry = get_irq_msi(irq); - BUG_ON(!entry || !entry->dev); - switch (entry->msi_attrib.type) { - case PCI_CAP_ID_MSI: - if (entry->msi_attrib.maskbit) { - int pos; - u32 mask_bits; - - 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); - } else { - msi_set_enable(entry->dev, !flag); - } - 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); - readl(entry->mask_base + offset); - break; - } - default: - BUG(); - break; +/* + * PCI 2.3 does not specify mask bits for each MSI interrupt. Attempting to + * mask all MSI interrupts by clearing the MSI enable bit does not work + * reliably as devices without an INTx disable bit will then generate a + * level IRQ which will never be cleared. + */ +static u32 __msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) +{ + u32 mask_bits = desc->masked; + + if (!desc->msi_attrib.maskbit) + return 0; + + mask_bits &= ~mask; + mask_bits |= flag; + pci_write_config_dword(desc->dev, desc->mask_pos, mask_bits); + + return mask_bits; +} + +static void msi_mask_irq(struct msi_desc *desc, u32 mask, u32 flag) +{ + desc->masked = __msi_mask_irq(desc, mask, flag); +} + +/* + * This internal function does not flush PCI writes to the device. + * All users must ensure that they read from the device before either + * assuming that the device state is up to date, or returning out of this + * file. This saves a few milliseconds when initialising devices with lots + * of MSI-X interrupts. + */ +static u32 __msix_mask_irq(struct msi_desc *desc, u32 flag) +{ + u32 mask_bits = desc->masked; + unsigned offset = desc->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; + mask_bits &= ~1; + mask_bits |= flag; + writel(mask_bits, desc->mask_base + offset); + + return mask_bits; +} + +static void msix_mask_irq(struct msi_desc *desc, u32 flag) +{ + desc->masked = __msix_mask_irq(desc, flag); +} + +static void msi_set_mask_bit(unsigned irq, u32 flag) +{ + struct msi_desc *desc = get_irq_msi(irq); + + if (desc->msi_attrib.is_msix) { + msix_mask_irq(desc, flag); + readl(desc->mask_base); /* Flush write to device */ + } else { + unsigned offset = irq - desc->dev->irq; + msi_mask_irq(desc, 1 << offset, flag << offset); } - entry->msi_attrib.masked = !!flag; } -void read_msi_msg(unsigned int irq, struct msi_msg *msg) +void mask_msi_irq(unsigned int irq) { - struct msi_desc *entry = get_irq_msi(irq); - switch(entry->msi_attrib.type) { - case PCI_CAP_ID_MSI: - { + msi_set_mask_bit(irq, 1); +} + +void unmask_msi_irq(unsigned int irq) +{ + msi_set_mask_bit(irq, 0); +} + +void read_msi_msg_desc(struct irq_desc *desc, struct msi_msg *msg) +{ + struct msi_desc *entry = get_irq_desc_msi(desc); + if (entry->msi_attrib.is_msix) { + void __iomem *base = entry->mask_base + + entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE; + + msg->address_lo = readl(base + PCI_MSIX_ENTRY_LOWER_ADDR); + msg->address_hi = readl(base + PCI_MSIX_ENTRY_UPPER_ADDR); + msg->data = readl(base + PCI_MSIX_ENTRY_DATA); + } else { struct pci_dev *dev = entry->dev; int pos = entry->msi_attrib.pos; u16 data; @@ -133,35 +216,39 @@ void read_msi_msg(unsigned int irq, struct msi_msg *msg) 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); + pci_read_config_word(dev, msi_data_reg(pos, 0), &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(); - } +void read_msi_msg(unsigned int irq, struct msi_msg *msg) +{ + struct irq_desc *desc = irq_to_desc(irq); + + read_msi_msg_desc(desc, msg); } -void write_msi_msg(unsigned int irq, struct msi_msg *msg) +void write_msi_msg_desc(struct irq_desc *desc, struct msi_msg *msg) { - struct msi_desc *entry = get_irq_msi(irq); - switch (entry->msi_attrib.type) { - case PCI_CAP_ID_MSI: - { + struct msi_desc *entry = get_irq_desc_msi(desc); + if (entry->msi_attrib.is_msix) { + 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); + writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR); + writel(msg->data, base + PCI_MSIX_ENTRY_DATA); + } else { struct pci_dev *dev = entry->dev; int pos = entry->msi_attrib.pos; + u16 msgctl; + + pci_read_config_word(dev, msi_control_reg(pos), &msgctl); + msgctl &= ~PCI_MSI_FLAGS_QSIZE; + msgctl |= entry->msi_attrib.multiple << 4; + pci_write_config_word(dev, msi_control_reg(pos), msgctl); pci_write_config_dword(dev, msi_lower_address_reg(pos), msg->address_lo); @@ -174,58 +261,60 @@ void write_msi_msg(unsigned int irq, struct msi_msg *msg) pci_write_config_word(dev, msi_data_reg(pos, 0), msg->data); } - break; - } - case PCI_CAP_ID_MSIX: - { - 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); - break; - } - default: - BUG(); } entry->msg = *msg; } -void mask_msi_irq(unsigned int irq) +void write_msi_msg(unsigned int irq, struct msi_msg *msg) { - msi_set_mask_bit(irq, 1); - msix_flush_writes(irq); + struct irq_desc *desc = irq_to_desc(irq); + + write_msi_msg_desc(desc, msg); } -void unmask_msi_irq(unsigned int irq) +static void free_msi_irqs(struct pci_dev *dev) { - msi_set_mask_bit(irq, 0); - msix_flush_writes(irq); -} + struct msi_desc *entry, *tmp; -static int msi_free_irq(struct pci_dev* dev, int irq); + list_for_each_entry(entry, &dev->msi_list, list) { + int i, nvec; + if (!entry->irq) + continue; + nvec = 1 << entry->msi_attrib.multiple; + for (i = 0; i < nvec; i++) + BUG_ON(irq_has_action(entry->irq + i)); + } + arch_teardown_msi_irqs(dev); -static struct msi_desc* alloc_msi_entry(void) -{ - struct msi_desc *entry; + list_for_each_entry_safe(entry, tmp, &dev->msi_list, list) { + if (entry->msi_attrib.is_msix) { + if (list_is_last(&entry->list, &dev->msi_list)) + iounmap(entry->mask_base); + } + list_del(&entry->list); + kfree(entry); + } +} - entry = kzalloc(sizeof(struct msi_desc), GFP_KERNEL); - if (!entry) +static struct msi_desc *alloc_msi_entry(struct pci_dev *dev) +{ + struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) return NULL; - INIT_LIST_HEAD(&entry->list); - entry->irq = 0; - entry->dev = NULL; + INIT_LIST_HEAD(&desc->list); + desc->dev = dev; - return entry; + return desc; +} + +static void pci_intx_for_msi(struct pci_dev *dev, int enable) +{ + if (!(dev->dev_flags & PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG)) + pci_intx(dev, enable); } -#ifdef CONFIG_PM static void __pci_restore_msi_state(struct pci_dev *dev) { int pos; @@ -238,16 +327,14 @@ static void __pci_restore_msi_state(struct pci_dev *dev) entry = get_irq_msi(dev->irq); pos = entry->msi_attrib.pos; - pci_intx(dev, 0); /* disable intx */ - msi_set_enable(dev, 0); + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, pos, 0); 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; + msi_mask_irq(entry, msi_capable_mask(control), entry->masked); + control &= ~PCI_MSI_FLAGS_QSIZE; + control |= (entry->msi_attrib.multiple << 4) | PCI_MSI_FLAGS_ENABLE; pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); } @@ -259,22 +346,22 @@ static void __pci_restore_msix_state(struct pci_dev *dev) if (!dev->msix_enabled) return; + BUG_ON(list_empty(&dev->msi_list)); + entry = list_first_entry(&dev->msi_list, struct msi_desc, list); + pos = entry->msi_attrib.pos; + pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); /* route the table */ - pci_intx(dev, 0); /* disable intx */ - msix_set_enable(dev, 0); + pci_intx_for_msi(dev, 0); + control |= PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); 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); + msix_mask_irq(entry, entry->masked); } - BUG_ON(list_empty(&dev->msi_list)); - entry = list_entry(dev->msi_list.next, struct msi_desc, list); - pos = entry->msi_attrib.pos; - 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); } @@ -283,74 +370,132 @@ void pci_restore_msi_state(struct pci_dev *dev) __pci_restore_msi_state(dev); __pci_restore_msix_state(dev); } -#endif /* CONFIG_PM */ +EXPORT_SYMBOL_GPL(pci_restore_msi_state); /** * msi_capability_init - configure device's MSI capability structure * @dev: pointer to the pci_dev data structure of MSI device function + * @nvec: number of interrupts to allocate * - * Setup the MSI capability structure of device function with a single - * MSI irq, regardless of device function is capable of handling - * multiple messages. A return of zero indicates the successful setup - * of an entry zero with the new MSI irq or non-zero for otherwise. - **/ -static int msi_capability_init(struct pci_dev *dev) + * Setup the MSI capability structure of the device with the requested + * number of interrupts. A return value of zero indicates the successful + * setup of an entry with the new MSI irq. A negative return value indicates + * an error, and a positive return value indicates the number of interrupts + * which could have been allocated. + */ +static int msi_capability_init(struct pci_dev *dev, int nvec) { struct msi_desc *entry; int pos, ret; u16 control; + unsigned mask; - msi_set_enable(dev, 0); /* Ensure msi is disabled as I set it up */ + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + msi_set_enable(dev, pos, 0); /* Disable MSI during set up */ - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); pci_read_config_word(dev, msi_control_reg(pos), &control); /* MSI Entry Initialization */ - entry = alloc_msi_entry(); + entry = alloc_msi_entry(dev); if (!entry) return -ENOMEM; - entry->msi_attrib.type = PCI_CAP_ID_MSI; - entry->msi_attrib.is_64 = is_64bit_address(control); - entry->msi_attrib.entry_nr = 0; - entry->msi_attrib.maskbit = is_mask_bit_support(control); - entry->msi_attrib.masked = 1; - entry->msi_attrib.default_irq = dev->irq; /* Save IOAPIC IRQ */ - entry->msi_attrib.pos = pos; - if (is_mask_bit_support(control)) { - entry->mask_base = (void __iomem *)(long)msi_mask_bits_reg(pos, - is_64bit_address(control)); - } - 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); - } + entry->msi_attrib.is_msix = 0; + entry->msi_attrib.is_64 = is_64bit_address(control); + entry->msi_attrib.entry_nr = 0; + entry->msi_attrib.maskbit = is_mask_bit_support(control); + entry->msi_attrib.default_irq = dev->irq; /* Save IOAPIC IRQ */ + entry->msi_attrib.pos = pos; + + entry->mask_pos = msi_mask_reg(pos, entry->msi_attrib.is_64); + /* All MSIs are unmasked by default, Mask them all */ + if (entry->msi_attrib.maskbit) + pci_read_config_dword(dev, entry->mask_pos, &entry->masked); + mask = msi_capable_mask(control); + msi_mask_irq(entry, mask, mask); + + list_add_tail(&entry->list, &dev->msi_list); + /* Configure MSI capability structure */ - ret = arch_setup_msi_irq(dev, entry); + ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI); if (ret) { - kfree(entry); + msi_mask_irq(entry, mask, ~mask); + free_msi_irqs(dev); return ret; } - list_add(&entry->list, &dev->msi_list); /* Set MSI enabled bits */ - pci_intx(dev, 0); /* disable intx */ - msi_set_enable(dev, 1); + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, pos, 1); dev->msi_enabled = 1; dev->irq = entry->irq; return 0; } +static void __iomem *msix_map_region(struct pci_dev *dev, unsigned pos, + unsigned nr_entries) +{ + unsigned long phys_addr; + u32 table_offset; + u8 bir; + + pci_read_config_dword(dev, msix_table_offset_reg(pos), &table_offset); + bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK); + table_offset &= ~PCI_MSIX_FLAGS_BIRMASK; + phys_addr = pci_resource_start(dev, bir) + table_offset; + + return ioremap_nocache(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); +} + +static int msix_setup_entries(struct pci_dev *dev, unsigned pos, + void __iomem *base, struct msix_entry *entries, + int nvec) +{ + struct msi_desc *entry; + int i; + + for (i = 0; i < nvec; i++) { + entry = alloc_msi_entry(dev); + if (!entry) { + if (!i) + iounmap(base); + else + free_msi_irqs(dev); + /* No enough memory. Don't try again */ + return -ENOMEM; + } + + entry->msi_attrib.is_msix = 1; + entry->msi_attrib.is_64 = 1; + entry->msi_attrib.entry_nr = entries[i].entry; + entry->msi_attrib.default_irq = dev->irq; + entry->msi_attrib.pos = pos; + entry->mask_base = base; + + list_add_tail(&entry->list, &dev->msi_list); + } + + return 0; +} + +static void msix_program_entries(struct pci_dev *dev, + struct msix_entry *entries) +{ + struct msi_desc *entry; + int i = 0; + + list_for_each_entry(entry, &dev->msi_list, list) { + int offset = entries[i].entry * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; + + entries[i].vector = entry->irq; + set_irq_msi(entry->irq, entry); + entry->masked = readl(entry->mask_base + offset); + msix_mask_irq(entry, 1); + i++; + } +} + /** * msix_capability_init - configure device's MSI-X capability * @dev: pointer to the pci_dev data structure of MSI-X device function @@ -364,76 +509,69 @@ static int msi_capability_init(struct pci_dev *dev) static int msix_capability_init(struct pci_dev *dev, struct msix_entry *entries, int nvec) { - struct msi_desc *entry; - int irq, pos, i, j, nr_entries, ret; - unsigned long phys_addr; - u32 table_offset; - u16 control; - u8 bir; + int pos, ret; + u16 control; void __iomem *base; - msix_set_enable(dev, 0);/* Ensure msix is disabled as I set it up */ + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + pci_read_config_word(dev, pos + PCI_MSIX_FLAGS, &control); - 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); + /* Ensure MSI-X is disabled while it is set up */ + control &= ~PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); - pci_read_config_dword(dev, msix_table_offset_reg(pos), &table_offset); - bir = (u8)(table_offset & PCI_MSIX_FLAGS_BIRMASK); - table_offset &= ~PCI_MSIX_FLAGS_BIRMASK; - phys_addr = pci_resource_start (dev, bir) + table_offset; - base = ioremap_nocache(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); - if (base == NULL) + /* Request & Map MSI-X table region */ + base = msix_map_region(dev, pos, multi_msix_capable(control)); + if (!base) return -ENOMEM; - /* MSI-X Table Initialization */ - for (i = 0; i < nvec; i++) { - entry = alloc_msi_entry(); - if (!entry) - break; - - j = entries[i].entry; - entry->msi_attrib.type = PCI_CAP_ID_MSIX; - entry->msi_attrib.is_64 = 1; - entry->msi_attrib.entry_nr = j; - entry->msi_attrib.maskbit = 1; - entry->msi_attrib.masked = 1; - entry->msi_attrib.default_irq = dev->irq; - entry->msi_attrib.pos = pos; - entry->dev = dev; - entry->mask_base = base; - - /* Configure MSI-X capability structure */ - ret = arch_setup_msi_irq(dev, entry); - if (ret) { - kfree(entry); - break; - } - entries[i].vector = entry->irq; - list_add(&entry->list, &dev->msi_list); - } - if (i != nvec) { - int avail = i - 1; - i--; - for (; i >= 0; i--) { - irq = (entries + i)->vector; - msi_free_irq(dev, irq); - (entries + i)->vector = 0; - } - /* If we had some success report the number of irqs + ret = msix_setup_entries(dev, pos, base, entries, nvec); + if (ret) + return ret; + + ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX); + if (ret) + goto error; + + /* + * Some devices require MSI-X to be enabled before we can touch the + * MSI-X registers. We need to mask all the vectors to prevent + * interrupts coming in before they're fully set up. + */ + control |= PCI_MSIX_FLAGS_MASKALL | PCI_MSIX_FLAGS_ENABLE; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); + + msix_program_entries(dev, entries); + + /* Set MSI-X enabled bits and unmask the function */ + pci_intx_for_msi(dev, 0); + dev->msix_enabled = 1; + + control &= ~PCI_MSIX_FLAGS_MASKALL; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); + + return 0; + +error: + if (ret < 0) { + /* + * If we had some success, report the number of irqs * we succeeded in setting up. */ - if (avail <= 0) - avail = -EBUSY; - return avail; + struct msi_desc *entry; + int avail = 0; + + list_for_each_entry(entry, &dev->msi_list, list) { + if (entry->irq != 0) + avail++; + } + if (avail != 0) + ret = avail; } - /* Set MSI-X enabled bits */ - pci_intx(dev, 0); /* disable intx */ - msix_set_enable(dev, 1); - dev->msix_enabled = 1; - return 0; + free_msi_irqs(dev); + + return ret; } /** @@ -446,7 +584,7 @@ static int msix_capability_init(struct pci_dev *dev, * to determine if MSI/-X are supported for the device. If MSI/-X is * supported return 0, else return an error code. **/ -static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type) +static int pci_msi_check_device(struct pci_dev *dev, int nvec, int type) { struct pci_bus *bus; int ret; @@ -463,8 +601,9 @@ static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type) if (nvec < 1) return -ERANGE; - /* Any bridge which does NOT route MSI transactions from it's - * secondary bus to it's primary bus must set NO_MSI flag on + /* + * Any bridge which does NOT route MSI transactions from its + * secondary bus to its 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. @@ -484,92 +623,102 @@ static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type) } /** - * pci_enable_msi - configure device's MSI capability structure - * @dev: pointer to the pci_dev data structure of MSI device function + * pci_enable_msi_block - configure device's MSI capability structure + * @dev: device to configure + * @nvec: number of interrupts to configure * - * Setup the MSI capability structure of device function with - * a single MSI irq upon its software driver call to request for - * MSI mode enabled on its hardware device function. A return of zero - * indicates the successful setup of an entry zero with the new MSI - * irq or non-zero for otherwise. - **/ -int pci_enable_msi(struct pci_dev* dev) + * Allocate IRQs for a device with the MSI capability. + * This function returns a negative errno if an error occurs. If it + * is unable to allocate the number of interrupts requested, it returns + * the number of interrupts it might be able to allocate. If it successfully + * allocates at least the number of interrupts requested, it returns 0 and + * updates the @dev's irq member to the lowest new interrupt number; the + * other interrupt numbers allocated to this device are consecutive. + */ +int pci_enable_msi_block(struct pci_dev *dev, unsigned int nvec) { - int status; + int status, pos, maxvec; + u16 msgctl; - status = pci_msi_check_device(dev, 1, PCI_CAP_ID_MSI); + pos = pci_find_capability(dev, PCI_CAP_ID_MSI); + if (!pos) + return -EINVAL; + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl); + maxvec = 1 << ((msgctl & PCI_MSI_FLAGS_QMASK) >> 1); + if (nvec > maxvec) + return maxvec; + + status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSI); if (status) return status; WARN_ON(!!dev->msi_enabled); - /* Check whether driver already requested for MSI-X irqs */ + /* Check whether driver already requested MSI-X irqs */ if (dev->msix_enabled) { - printk(KERN_INFO "PCI: %s: Can't enable MSI. " - "Device already has MSI-X enabled\n", - pci_name(dev)); + dev_info(&dev->dev, "can't enable MSI " + "(MSI-X already enabled)\n"); return -EINVAL; } - status = msi_capability_init(dev); + + status = msi_capability_init(dev, nvec); return status; } -EXPORT_SYMBOL(pci_enable_msi); +EXPORT_SYMBOL(pci_enable_msi_block); -void pci_disable_msi(struct pci_dev* dev) +void pci_msi_shutdown(struct pci_dev *dev) { - struct msi_desc *entry; - int default_irq; + struct msi_desc *desc; + u32 mask; + u16 ctrl; + unsigned pos; if (!pci_msi_enable || !dev || !dev->msi_enabled) return; - msi_set_enable(dev, 0); - pci_intx(dev, 1); /* enable intx */ - dev->msi_enabled = 0; - 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) { - return; - } + desc = list_first_entry(&dev->msi_list, struct msi_desc, list); + pos = desc->msi_attrib.pos; - default_irq = entry->msi_attrib.default_irq; - msi_free_irq(dev, entry->irq); + msi_set_enable(dev, pos, 0); + pci_intx_for_msi(dev, 1); + dev->msi_enabled = 0; + + /* Return the device with MSI unmasked as initial states */ + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &ctrl); + mask = msi_capable_mask(ctrl); + /* Keep cached state to be restored */ + __msi_mask_irq(desc, mask, ~mask); /* Restore dev->irq to its default pin-assertion irq */ - dev->irq = default_irq; + dev->irq = desc->msi_attrib.default_irq; } -EXPORT_SYMBOL(pci_disable_msi); -static int msi_free_irq(struct pci_dev* dev, int irq) +void pci_disable_msi(struct pci_dev *dev) { - struct msi_desc *entry; - int entry_nr, type; - void __iomem *base; - - BUG_ON(irq_has_action(irq)); - - entry = get_irq_msi(irq); - if (!entry || entry->dev != dev) { - return -EINVAL; - } - type = entry->msi_attrib.type; - entry_nr = entry->msi_attrib.entry_nr; - base = entry->mask_base; - list_del(&entry->list); + if (!pci_msi_enable || !dev || !dev->msi_enabled) + return; - arch_teardown_msi_irq(irq); - kfree(entry); + pci_msi_shutdown(dev); + free_msi_irqs(dev); +} +EXPORT_SYMBOL(pci_disable_msi); - if (type == PCI_CAP_ID_MSIX) { - writel(1, base + entry_nr * PCI_MSIX_ENTRY_SIZE + - PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET); +/** + * pci_msix_table_size - return the number of device's MSI-X table entries + * @dev: pointer to the pci_dev data structure of MSI-X device function + */ +int pci_msix_table_size(struct pci_dev *dev) +{ + int pos; + u16 control; - if (list_empty(&dev->msi_list)) - iounmap(base); - } + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + if (!pos) + return 0; - return 0; + pci_read_config_word(dev, msi_control_reg(pos), &control); + return multi_msix_capable(control); } /** @@ -584,27 +733,24 @@ static int msi_free_irq(struct pci_dev* dev, int irq) * indicates the successful configuration of MSI-X capability structure * with new allocated MSI-X irqs. A return of < 0 indicates a failure. * Or a return of > 0 indicates that driver request is exceeding the number - * of irqs available. Driver should use the returned value to re-send - * its request. + * of irqs or MSI-X vectors available. Driver should use the returned value to + * re-send its request. **/ -int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) +int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec) { - int status, pos, nr_entries; + int status, nr_entries; int i, j; - u16 control; if (!entries) - return -EINVAL; + return -EINVAL; status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX); if (status) return status; - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - pci_read_config_word(dev, msi_control_reg(pos), &control); - nr_entries = multi_msix_capable(control); + nr_entries = pci_msix_table_size(dev); if (nvec > nr_entries) - return -EINVAL; + return nr_entries; /* Check for any invalid entries */ for (i = 0; i < nvec; i++) { @@ -618,10 +764,9 @@ int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) WARN_ON(!!dev->msix_enabled); /* Check whether driver already requested for MSI irq */ - if (dev->msi_enabled) { - printk(KERN_INFO "PCI: %s: Can't enable MSI-X. " - "Device already has an MSI irq assigned\n", - pci_name(dev)); + if (dev->msi_enabled) { + dev_info(&dev->dev, "can't enable MSI-X " + "(MSI IRQ already assigned)\n"); return -EINVAL; } status = msix_capability_init(dev, entries, nvec); @@ -629,24 +774,31 @@ int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) } EXPORT_SYMBOL(pci_enable_msix); -static void msix_free_all_irqs(struct pci_dev *dev) +void pci_msix_shutdown(struct pci_dev *dev) { struct msi_desc *entry; - list_for_each_entry(entry, &dev->msi_list, list) - msi_free_irq(dev, entry->irq); -} - -void pci_disable_msix(struct pci_dev* dev) -{ if (!pci_msi_enable || !dev || !dev->msix_enabled) return; + /* Return the device with MSI-X masked as initial states */ + list_for_each_entry(entry, &dev->msi_list, list) { + /* Keep cached states to be restored */ + __msix_mask_irq(entry, 1); + } + msix_set_enable(dev, 0); - pci_intx(dev, 1); /* enable intx */ + pci_intx_for_msi(dev, 1); dev->msix_enabled = 0; +} + +void pci_disable_msix(struct pci_dev *dev) +{ + if (!pci_msi_enable || !dev || !dev->msix_enabled) + return; - msix_free_all_irqs(dev); + pci_msix_shutdown(dev); + free_msi_irqs(dev); } EXPORT_SYMBOL(pci_disable_msix); @@ -659,20 +811,13 @@ EXPORT_SYMBOL(pci_disable_msix); * 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) +void msi_remove_pci_irq_vectors(struct pci_dev *dev) { if (!pci_msi_enable || !dev) - return; - - 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); - } + return; - if (dev->msix_enabled) - msix_free_all_irqs(dev); + if (dev->msi_enabled || dev->msix_enabled) + free_msi_irqs(dev); } void pci_no_msi(void) @@ -680,17 +825,19 @@ void pci_no_msi(void) pci_msi_enable = 0; } -void pci_msi_init_pci_dev(struct pci_dev *dev) +/** + * pci_msi_enabled - is MSI enabled? + * + * Returns true if MSI has not been disabled by the command-line option + * pci=nomsi. + **/ +int pci_msi_enabled(void) { - INIT_LIST_HEAD(&dev->msi_list); + return pci_msi_enable; } +EXPORT_SYMBOL(pci_msi_enabled); - -/* Arch hooks */ - -int __attribute__ ((weak)) -arch_msi_check_device(struct pci_dev* dev, int nvec, int type) +void pci_msi_init_pci_dev(struct pci_dev *dev) { - return 0; + INIT_LIST_HEAD(&dev->msi_list); } -