X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=drivers%2Fpci%2Fmsi.c;h=77b68eaf021e43152c4bbd257bd190a4a210473c;hb=89713422a768458a0d375f0c2f3586cd5ccde6a1;hp=ed3f7e1a563c79c06d9808bfc59c795665fc11c5;hpb=7f3af60e5a444b287d740a84998a8f480645dadf;p=safe%2Fjmp%2Flinux-2.6 diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index ed3f7e1..77b68ea 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -12,72 +12,198 @@ #include #include #include -#include #include #include #include - -#include -#include -#include +#include +#include +#include +#include #include "pci.h" #include "msi.h" -static DEFINE_SPINLOCK(msi_lock); -static struct msi_desc* msi_desc[NR_IRQS] = { [0 ... NR_IRQS-1] = NULL }; -static struct kmem_cache* msi_cachep; - static int pci_msi_enable = 1; -static int msi_cache_init(void) +/* Arch hooks */ + +#ifndef arch_msi_check_device +int arch_msi_check_device(struct pci_dev *dev, int nvec, int type) { - msi_cachep = kmem_cache_create("msi_cache", sizeof(struct msi_desc), - 0, SLAB_HWCACHE_ALIGN, NULL, NULL); - if (!msi_cachep) - return -ENOMEM; + return 0; +} +#endif + +#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 -static void msi_set_mask_bit(unsigned int irq, int flag) +#ifndef arch_teardown_msi_irqs +void arch_teardown_msi_irqs(struct pci_dev *dev) { struct msi_desc *entry; - entry = msi_desc[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); - } - 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); - break; + 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); } - default: - BUG(); - break; +} +#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) +{ + 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); } } -void read_msi_msg(unsigned int irq, struct msi_msg *msg) +static inline __attribute_const__ u32 msi_mask(unsigned x) +{ + /* Don't shift by >= width of type */ + if (x >= 5) + return 0xffffffff; + return (1 << (1 << x)) - 1; +} + +static inline __attribute_const__ u32 msi_capable_mask(u16 control) +{ + return msi_mask((control >> 1) & 7); +} + +static inline __attribute_const__ u32 msi_enabled_mask(u16 control) +{ + return msi_mask((control >> 4) & 7); +} + +/* + * 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) { - struct msi_desc *entry = get_irq_data(irq); - switch(entry->msi_attrib.type) { - case PCI_CAP_ID_MSI: - { + 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); + } +} + +void mask_msi_irq(unsigned int irq) +{ + 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; @@ -90,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_data(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); @@ -131,398 +261,239 @@ 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); -} + struct irq_desc *desc = irq_to_desc(irq); -void unmask_msi_irq(unsigned int irq) -{ - msi_set_mask_bit(irq, 0); + write_msi_msg_desc(desc, msg); } -static int msi_free_irq(struct pci_dev* dev, int irq); -static int msi_init(void) +static void free_msi_irqs(struct pci_dev *dev) { - static int status = -ENOMEM; + struct msi_desc *entry, *tmp; - if (!status) - return status; - - if (pci_msi_quirk) { - pci_msi_enable = 0; - printk(KERN_WARNING "PCI: MSI quirk detected. MSI disabled.\n"); - status = -EINVAL; - return status; + 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)); } - status = msi_cache_init(); - if (status < 0) { - pci_msi_enable = 0; - printk(KERN_WARNING "PCI: MSI cache init failed\n"); - return status; - } + arch_teardown_msi_irqs(dev); - return status; + 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); + } } -static struct msi_desc* alloc_msi_entry(void) +static struct msi_desc *alloc_msi_entry(struct pci_dev *dev) { - struct msi_desc *entry; - - entry = kmem_cache_zalloc(msi_cachep, GFP_KERNEL); - if (!entry) + struct msi_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) return NULL; - entry->link.tail = entry->link.head = 0; /* single message */ - entry->dev = NULL; + INIT_LIST_HEAD(&desc->list); + desc->dev = dev; - return entry; + return desc; } -static void attach_msi_entry(struct msi_desc *entry, int irq) +static void pci_intx_for_msi(struct pci_dev *dev, int enable) { - unsigned long flags; - - spin_lock_irqsave(&msi_lock, flags); - msi_desc[irq] = entry; - spin_unlock_irqrestore(&msi_lock, flags); + if (!(dev->dev_flags & PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG)) + pci_intx(dev, enable); } -static int create_msi_irq(void) +static void __pci_restore_msi_state(struct pci_dev *dev) { + int pos; + u16 control; struct msi_desc *entry; - int irq; - entry = alloc_msi_entry(); - if (!entry) - return -ENOMEM; + if (!dev->msi_enabled) + return; - irq = create_irq(); - if (irq < 0) { - kmem_cache_free(msi_cachep, entry); - return -EBUSY; - } + entry = get_irq_msi(dev->irq); + pos = entry->msi_attrib.pos; - set_irq_data(irq, entry); + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, pos, 0); + write_msi_msg(dev->irq, &entry->msg); - return irq; + pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control); + 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); } -static void destroy_msi_irq(unsigned int irq) +static void __pci_restore_msix_state(struct pci_dev *dev) { + int pos; struct msi_desc *entry; - - entry = get_irq_data(irq); - set_irq_chip(irq, NULL); - set_irq_data(irq, NULL); - destroy_irq(irq); - kmem_cache_free(msi_cachep, entry); -} - -static void enable_msi_mode(struct pci_dev *dev, int pos, int type) -{ - u16 control; - - pci_read_config_word(dev, msi_control_reg(pos), &control); - if (type == PCI_CAP_ID_MSI) { - /* Set enabled bits to single MSI & enable MSI_enable bit */ - msi_enable(control, 1); - pci_write_config_word(dev, msi_control_reg(pos), control); - dev->msi_enabled = 1; - } else { - msix_enable(control); - pci_write_config_word(dev, msi_control_reg(pos), control); - dev->msix_enabled = 1; - } - - pci_intx(dev, 0); /* disable intx */ -} - -void disable_msi_mode(struct pci_dev *dev, int pos, int type) -{ u16 control; - pci_read_config_word(dev, msi_control_reg(pos), &control); - if (type == PCI_CAP_ID_MSI) { - /* Set enabled bits to single MSI & enable MSI_enable bit */ - msi_disable(control); - pci_write_config_word(dev, msi_control_reg(pos), control); - dev->msi_enabled = 0; - } else { - msix_disable(control); - pci_write_config_word(dev, msi_control_reg(pos), control); - dev->msix_enabled = 0; - } - - pci_intx(dev, 1); /* enable intx */ -} + 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); -static int msi_lookup_irq(struct pci_dev *dev, int type) -{ - int irq; - unsigned long flags; + /* route the table */ + 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); - spin_lock_irqsave(&msi_lock, flags); - for (irq = 0; irq < NR_IRQS; irq++) { - if (!msi_desc[irq] || msi_desc[irq]->dev != dev || - msi_desc[irq]->msi_attrib.type != type || - msi_desc[irq]->msi_attrib.default_irq != dev->irq) - continue; - spin_unlock_irqrestore(&msi_lock, flags); - /* This pre-assigned MSI irq for this device - already exits. Override dev->irq with this irq */ - dev->irq = irq; - return 0; + list_for_each_entry(entry, &dev->msi_list, list) { + write_msi_msg(entry->irq, &entry->msg); + msix_mask_irq(entry, entry->masked); } - spin_unlock_irqrestore(&msi_lock, flags); - return -EACCES; + control &= ~PCI_MSIX_FLAGS_MASKALL; + pci_write_config_word(dev, pos + PCI_MSIX_FLAGS, control); } -void pci_scan_msi_device(struct pci_dev *dev) +void pci_restore_msi_state(struct pci_dev *dev) { - if (!dev) - return; + __pci_restore_msi_state(dev); + __pci_restore_msix_state(dev); } +EXPORT_SYMBOL_GPL(pci_restore_msi_state); -#ifdef CONFIG_PM -int pci_save_msi_state(struct pci_dev *dev) +/** + * 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 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) { - int pos, i = 0; + struct msi_desc *entry; + int pos, ret; u16 control; - struct pci_cap_saved_state *save_state; - u32 *cap; + unsigned mask; pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - if (pos <= 0 || dev->no_msi) - return 0; + msi_set_enable(dev, pos, 0); /* Disable MSI during set up */ pci_read_config_word(dev, msi_control_reg(pos), &control); - if (!(control & PCI_MSI_FLAGS_ENABLE)) - return 0; - - save_state = kzalloc(sizeof(struct pci_cap_saved_state) + sizeof(u32) * 5, - GFP_KERNEL); - if (!save_state) { - printk(KERN_ERR "Out of memory in pci_save_msi_state\n"); + /* MSI Entry Initialization */ + entry = alloc_msi_entry(dev); + if (!entry) return -ENOMEM; - } - cap = &save_state->data[0]; - - pci_read_config_dword(dev, pos, &cap[i++]); - control = cap[0] >> 16; - pci_read_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, &cap[i++]); - if (control & PCI_MSI_FLAGS_64BIT) { - pci_read_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, &cap[i++]); - pci_read_config_dword(dev, pos + PCI_MSI_DATA_64, &cap[i++]); - } else - pci_read_config_dword(dev, pos + PCI_MSI_DATA_32, &cap[i++]); - if (control & PCI_MSI_FLAGS_MASKBIT) - pci_read_config_dword(dev, pos + PCI_MSI_MASK_BIT, &cap[i++]); - save_state->cap_nr = PCI_CAP_ID_MSI; - pci_add_saved_cap(dev, save_state); - return 0; -} -void pci_restore_msi_state(struct pci_dev *dev) -{ - int i = 0, pos; - u16 control; - struct pci_cap_saved_state *save_state; - u32 *cap; - - save_state = pci_find_saved_cap(dev, PCI_CAP_ID_MSI); - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - if (!save_state || pos <= 0) - return; - cap = &save_state->data[0]; - - control = cap[i++] >> 16; - pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, cap[i++]); - if (control & PCI_MSI_FLAGS_64BIT) { - pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI, cap[i++]); - pci_write_config_dword(dev, pos + PCI_MSI_DATA_64, cap[i++]); - } else - pci_write_config_dword(dev, pos + PCI_MSI_DATA_32, cap[i++]); - if (control & PCI_MSI_FLAGS_MASKBIT) - pci_write_config_dword(dev, pos + PCI_MSI_MASK_BIT, cap[i++]); - pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); - enable_msi_mode(dev, pos, PCI_CAP_ID_MSI); - pci_remove_saved_cap(save_state); - kfree(save_state); -} + 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; -int pci_save_msix_state(struct pci_dev *dev) -{ - int pos; - int temp; - int irq, head, tail = 0; - u16 control; - struct pci_cap_saved_state *save_state; + 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); - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (pos <= 0 || dev->no_msi) - return 0; + list_add_tail(&entry->list, &dev->msi_list); - /* save the capability */ - pci_read_config_word(dev, msi_control_reg(pos), &control); - if (!(control & PCI_MSIX_FLAGS_ENABLE)) - return 0; - save_state = kzalloc(sizeof(struct pci_cap_saved_state) + sizeof(u16), - GFP_KERNEL); - if (!save_state) { - printk(KERN_ERR "Out of memory in pci_save_msix_state\n"); - return -ENOMEM; + /* Configure MSI capability structure */ + ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI); + if (ret) { + msi_mask_irq(entry, mask, ~mask); + free_msi_irqs(dev); + return ret; } - *((u16 *)&save_state->data[0]) = control; - /* save the table */ - temp = dev->irq; - if (msi_lookup_irq(dev, PCI_CAP_ID_MSIX)) { - kfree(save_state); - return -EINVAL; - } + /* Set MSI enabled bits */ + pci_intx_for_msi(dev, 0); + msi_set_enable(dev, pos, 1); + dev->msi_enabled = 1; - irq = head = dev->irq; - while (head != tail) { - struct msi_desc *entry; + dev->irq = entry->irq; + return 0; +} - entry = msi_desc[irq]; - read_msi_msg(irq, &entry->msg_save); +static void __iomem *msix_map_region(struct pci_dev *dev, unsigned pos, + unsigned nr_entries) +{ + unsigned long phys_addr; + u32 table_offset; + u8 bir; - tail = msi_desc[irq]->link.tail; - irq = tail; - } - dev->irq = temp; + 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; - save_state->cap_nr = PCI_CAP_ID_MSIX; - pci_add_saved_cap(dev, save_state); - return 0; + return ioremap_nocache(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); } -void pci_restore_msix_state(struct pci_dev *dev) +static int msix_setup_entries(struct pci_dev *dev, unsigned pos, + void __iomem *base, struct msix_entry *entries, + int nvec) { - u16 save; - int pos; - int irq, head, tail = 0; struct msi_desc *entry; - int temp; - struct pci_cap_saved_state *save_state; + int i; - save_state = pci_find_saved_cap(dev, PCI_CAP_ID_MSIX); - if (!save_state) - return; - save = *((u16 *)&save_state->data[0]); - pci_remove_saved_cap(save_state); - kfree(save_state); - - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (pos <= 0) - return; + 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; + } - /* route the table */ - temp = dev->irq; - if (msi_lookup_irq(dev, PCI_CAP_ID_MSIX)) - return; - irq = head = dev->irq; - while (head != tail) { - entry = msi_desc[irq]; - write_msi_msg(irq, &entry->msg_save); + 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; - tail = msi_desc[irq]->link.tail; - irq = tail; + list_add_tail(&entry->list, &dev->msi_list); } - dev->irq = temp; - pci_write_config_word(dev, msi_control_reg(pos), save); - enable_msi_mode(dev, pos, PCI_CAP_ID_MSIX); + return 0; } -#endif -/** - * msi_capability_init - 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 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) +static void msix_program_entries(struct pci_dev *dev, + struct msix_entry *entries) { - int status; struct msi_desc *entry; - int pos, irq; - u16 control; - - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - pci_read_config_word(dev, msi_control_reg(pos), &control); - /* MSI Entry Initialization */ - irq = create_msi_irq(); - if (irq < 0) - return irq; - - entry = get_irq_data(irq); - entry->link.head = irq; - entry->link.tail = irq; - 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.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); - } - /* Configure MSI capability structure */ - status = arch_setup_msi_irq(irq, dev); - if (status < 0) { - destroy_msi_irq(irq); - return status; - } + int i = 0; - attach_msi_entry(entry, irq); - /* Set MSI enabled bits */ - enable_msi_mode(dev, pos, PCI_CAP_ID_MSI); + list_for_each_entry(entry, &dev->msi_list, list) { + int offset = entries[i].entry * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; - dev->irq = irq; - return 0; + entries[i].vector = entry->irq; + set_irq_msi(entry->irq, entry); + entry->masked = readl(entry->mask_base + offset); + msix_mask_irq(entry, 1); + i++; + } } /** @@ -538,105 +509,101 @@ 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 *head = NULL, *tail = NULL, *entry = NULL; - int status; - int irq, pos, i, j, nr_entries, temp = 0; - unsigned long phys_addr; - u32 table_offset; - u16 control; - u8 bir; + int pos, ret; + u16 control; void __iomem *base; - 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); + pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); + pci_read_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) + /* 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); + + /* 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++) { - irq = create_msi_irq(); - if (irq < 0) - break; - - entry = get_irq_data(irq); - j = entries[i].entry; - entries[i].vector = irq; - 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.default_irq = dev->irq; - entry->msi_attrib.pos = pos; - entry->dev = dev; - entry->mask_base = base; - if (!head) { - entry->link.head = irq; - entry->link.tail = irq; - head = entry; - } else { - entry->link.head = temp; - entry->link.tail = tail->link.tail; - tail->link.tail = irq; - head->link.head = irq; - } - temp = irq; - tail = entry; - /* Configure MSI-X capability structure */ - status = arch_setup_msi_irq(irq, dev); - if (status < 0) { - destroy_msi_irq(irq); - break; - } + ret = msix_setup_entries(dev, pos, base, entries, nvec); + if (ret) + return ret; - attach_msi_entry(entry, irq); - } - 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 = 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 */ - enable_msi_mode(dev, pos, PCI_CAP_ID_MSIX); - return 0; + free_msi_irqs(dev); + + return ret; } /** - * pci_msi_supported - check whether MSI may be enabled on device + * pci_msi_check_device - check whether MSI may be enabled on a device * @dev: pointer to the pci_dev data structure of MSI device function + * @nvec: how many MSIs have been requested ? + * @type: are we checking for MSI or MSI-X ? * * Look at global flags, the device itself, and its parent busses - * to return 0 if MSI are supported for the device. + * 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_supported(struct pci_dev * dev) +static int pci_msi_check_device(struct pci_dev *dev, int nvec, int type) { struct pci_bus *bus; + int ret; /* MSI must be globally enabled and supported by the device */ if (!pci_msi_enable || !dev || dev->no_msi) return -EINVAL; - /* Any bridge which does NOT route MSI transactions from it's - * secondary bus to it's primary bus must set NO_MSI flag on + /* + * 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; + + /* + * 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. @@ -645,131 +612,113 @@ int pci_msi_supported(struct pci_dev * dev) if (bus->bus_flags & PCI_BUS_FLAGS_NO_MSI) return -EINVAL; + ret = arch_msi_check_device(dev, nvec, type); + if (ret) + return ret; + + if (!pci_find_capability(dev, type)) + return -EINVAL; + return 0; } /** - * 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 pos, temp, status; - - if (pci_msi_supported(dev) < 0) - return -EINVAL; - - temp = dev->irq; - - status = msi_init(); - if (status < 0) - return status; + int status, pos, maxvec; + u16 msgctl; 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; - WARN_ON(!msi_lookup_irq(dev, PCI_CAP_ID_MSI)); + status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSI); + if (status) + return status; - /* Check whether driver already requested for MSI-X irqs */ - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (pos > 0 && !msi_lookup_irq(dev, PCI_CAP_ID_MSIX)) { - printk(KERN_INFO "PCI: %s: Can't enable MSI. " - "Device already has MSI-X irq assigned\n", - pci_name(dev)); - dev->irq = temp; - return -EINVAL; + WARN_ON(!!dev->msi_enabled); + + /* Check whether driver already requested MSI-X irqs */ + if (dev->msix_enabled) { + 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_block); -void pci_disable_msi(struct pci_dev* dev) +void pci_msi_shutdown(struct pci_dev *dev) { - struct msi_desc *entry; - int pos, default_irq; - u16 control; - unsigned long flags; + struct msi_desc *desc; + u32 mask; + u16 ctrl; + unsigned pos; - if (!pci_msi_enable) - return; - if (!dev) + if (!pci_msi_enable || !dev || !dev->msi_enabled) return; - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - if (!pos) - return; + BUG_ON(list_empty(&dev->msi_list)); + desc = list_first_entry(&dev->msi_list, struct msi_desc, list); + pos = desc->msi_attrib.pos; - pci_read_config_word(dev, msi_control_reg(pos), &control); - if (!(control & PCI_MSI_FLAGS_ENABLE)) - return; + msi_set_enable(dev, pos, 0); + pci_intx_for_msi(dev, 1); + dev->msi_enabled = 0; - disable_msi_mode(dev, pos, PCI_CAP_ID_MSI); + /* 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); - spin_lock_irqsave(&msi_lock, flags); - entry = msi_desc[dev->irq]; - if (!entry || !entry->dev || entry->msi_attrib.type != PCI_CAP_ID_MSI) { - spin_unlock_irqrestore(&msi_lock, flags); + /* Restore dev->irq to its default pin-assertion irq */ + dev->irq = desc->msi_attrib.default_irq; +} + +void pci_disable_msi(struct pci_dev *dev) +{ + if (!pci_msi_enable || !dev || !dev->msi_enabled) return; - } - if (irq_has_action(dev->irq)) { - spin_unlock_irqrestore(&msi_lock, flags); - printk(KERN_WARNING "PCI: %s: pci_disable_msi() called without " - "free_irq() on MSI irq %d\n", - pci_name(dev), dev->irq); - BUG_ON(irq_has_action(dev->irq)); - } else { - default_irq = entry->msi_attrib.default_irq; - spin_unlock_irqrestore(&msi_lock, flags); - msi_free_irq(dev, dev->irq); - /* Restore dev->irq to its default pin-assertion irq */ - dev->irq = default_irq; - } + pci_msi_shutdown(dev); + free_msi_irqs(dev); } +EXPORT_SYMBOL(pci_disable_msi); -static int msi_free_irq(struct pci_dev* dev, int irq) +/** + * 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) { - struct msi_desc *entry; - int head, entry_nr, type; - void __iomem *base; - unsigned long flags; - - arch_teardown_msi_irq(irq); + int pos; + u16 control; - spin_lock_irqsave(&msi_lock, flags); - entry = msi_desc[irq]; - if (!entry || entry->dev != dev) { - spin_unlock_irqrestore(&msi_lock, flags); - return -EINVAL; - } - type = entry->msi_attrib.type; - entry_nr = entry->msi_attrib.entry_nr; - head = entry->link.head; - base = entry->mask_base; - msi_desc[entry->link.head]->link.tail = entry->link.tail; - msi_desc[entry->link.tail]->link.head = entry->link.head; - entry->dev = NULL; - msi_desc[irq] = NULL; - spin_unlock_irqrestore(&msi_lock, flags); - - destroy_msi_irq(irq); - - if (type == PCI_CAP_ID_MSIX) { - writel(1, base + entry_nr * PCI_MSIX_ENTRY_SIZE + - PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET); - - if (head == irq) - 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); } /** @@ -784,30 +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 i, j, temp; - u16 control; + int status, nr_entries; + int i, j; - if (!entries || pci_msi_supported(dev) < 0) - return -EINVAL; + if (!entries) + return -EINVAL; - status = msi_init(); - if (status < 0) + status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSIX); + if (status) return status; - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (!pos) - return -EINVAL; - - 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++) { @@ -818,68 +761,46 @@ int pci_enable_msix(struct pci_dev* dev, struct msix_entry *entries, int nvec) return -EINVAL; /* duplicate entry */ } } - temp = dev->irq; - WARN_ON(!msi_lookup_irq(dev, PCI_CAP_ID_MSIX)); + WARN_ON(!!dev->msix_enabled); /* Check whether driver already requested for MSI irq */ - if (pci_find_capability(dev, PCI_CAP_ID_MSI) > 0 && - !msi_lookup_irq(dev, PCI_CAP_ID_MSI)) { - printk(KERN_INFO "PCI: %s: Can't enable MSI-X. " - "Device already has an MSI irq assigned\n", - pci_name(dev)); - dev->irq = temp; + 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); return status; } +EXPORT_SYMBOL(pci_enable_msix); -void pci_disable_msix(struct pci_dev* dev) +void pci_msix_shutdown(struct pci_dev *dev) { - int pos, temp; - u16 control; + struct msi_desc *entry; - if (!pci_msi_enable) - return; - if (!dev) + if (!pci_msi_enable || !dev || !dev->msix_enabled) return; - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (!pos) - 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); + } - pci_read_config_word(dev, msi_control_reg(pos), &control); - if (!(control & PCI_MSIX_FLAGS_ENABLE)) + msix_set_enable(dev, 0); + 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; - disable_msi_mode(dev, pos, PCI_CAP_ID_MSIX); - - temp = dev->irq; - if (!msi_lookup_irq(dev, PCI_CAP_ID_MSIX)) { - int irq, head, tail = 0, warning = 0; - unsigned long flags; - - irq = head = dev->irq; - dev->irq = temp; /* Restore pin IRQ */ - while (head != tail) { - spin_lock_irqsave(&msi_lock, flags); - tail = msi_desc[irq]->link.tail; - spin_unlock_irqrestore(&msi_lock, flags); - if (irq_has_action(irq)) - warning = 1; - else if (irq != head) /* Release MSI-X irq */ - msi_free_irq(dev, irq); - irq = tail; - } - msi_free_irq(dev, irq); - if (warning) { - printk(KERN_WARNING "PCI: %s: pci_disable_msix() called without " - "free_irq() on all MSI-X irqs\n", - pci_name(dev)); - BUG_ON(warning > 0); - } - } + pci_msix_shutdown(dev); + free_msi_irqs(dev); } +EXPORT_SYMBOL(pci_disable_msix); /** * msi_remove_pci_irq_vectors - reclaim MSI(X) irqs to unused state @@ -890,53 +811,13 @@ void pci_disable_msix(struct pci_dev* dev) * 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) { - int pos, temp; - unsigned long flags; - if (!pci_msi_enable || !dev) - return; + return; - temp = dev->irq; /* Save IOAPIC IRQ */ - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - if (pos > 0 && !msi_lookup_irq(dev, PCI_CAP_ID_MSI)) { - if (irq_has_action(dev->irq)) { - printk(KERN_WARNING "PCI: %s: msi_remove_pci_irq_vectors() " - "called without free_irq() on MSI irq %d\n", - pci_name(dev), dev->irq); - BUG_ON(irq_has_action(dev->irq)); - } else /* Release MSI irq assigned to this device */ - msi_free_irq(dev, dev->irq); - dev->irq = temp; /* Restore IOAPIC IRQ */ - } - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (pos > 0 && !msi_lookup_irq(dev, PCI_CAP_ID_MSIX)) { - int irq, head, tail = 0, warning = 0; - void __iomem *base = NULL; - - irq = head = dev->irq; - while (head != tail) { - spin_lock_irqsave(&msi_lock, flags); - tail = msi_desc[irq]->link.tail; - base = msi_desc[irq]->mask_base; - spin_unlock_irqrestore(&msi_lock, flags); - if (irq_has_action(irq)) - warning = 1; - else if (irq != head) /* Release MSI-X irq */ - msi_free_irq(dev, irq); - irq = tail; - } - msi_free_irq(dev, irq); - if (warning) { - iounmap(base); - printk(KERN_WARNING "PCI: %s: msi_remove_pci_irq_vectors() " - "called without free_irq() on all MSI-X irqs\n", - pci_name(dev)); - BUG_ON(warning > 0); - } - dev->irq = temp; /* Restore IOAPIC IRQ */ - } + if (dev->msi_enabled || dev->msix_enabled) + free_msi_irqs(dev); } void pci_no_msi(void) @@ -944,7 +825,19 @@ void pci_no_msi(void) pci_msi_enable = 0; } -EXPORT_SYMBOL(pci_enable_msi); -EXPORT_SYMBOL(pci_disable_msi); -EXPORT_SYMBOL(pci_enable_msix); -EXPORT_SYMBOL(pci_disable_msix); +/** + * 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) +{ + return pci_msi_enable; +} +EXPORT_SYMBOL(pci_msi_enabled); + +void pci_msi_init_pci_dev(struct pci_dev *dev) +{ + INIT_LIST_HEAD(&dev->msi_list); +}