]> Pileus Git - ~andy/linux/blobdiff - drivers/pci/msi.c
Merge tag 'sound-3.14-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai...
[~andy/linux] / drivers / pci / msi.c
index 3fcd67a16677afc4b73495347b38e2fc418b7a04..955ab7990c5bd7045f2bf896e6d4c65989176089 100644 (file)
@@ -116,7 +116,7 @@ void __weak arch_teardown_msi_irqs(struct pci_dev *dev)
        return default_teardown_msi_irqs(dev);
 }
 
-void default_restore_msi_irqs(struct pci_dev *dev, int irq)
+static void default_restore_msi_irq(struct pci_dev *dev, int irq)
 {
        struct msi_desc *entry;
 
@@ -134,9 +134,9 @@ void default_restore_msi_irqs(struct pci_dev *dev, int irq)
                write_msi_msg(irq, &entry->msg);
 }
 
-void __weak arch_restore_msi_irqs(struct pci_dev *dev, int irq)
+void __weak arch_restore_msi_irqs(struct pci_dev *dev)
 {
-       return default_restore_msi_irqs(dev, irq);
+       return default_restore_msi_irqs(dev);
 }
 
 static void msi_set_enable(struct pci_dev *dev, int enable)
@@ -262,6 +262,15 @@ void unmask_msi_irq(struct irq_data *data)
        msi_set_mask_bit(data, 0);
 }
 
+void default_restore_msi_irqs(struct pci_dev *dev)
+{
+       struct msi_desc *entry;
+
+       list_for_each_entry(entry, &dev->msi_list, list) {
+               default_restore_msi_irq(dev, entry->irq);
+       }
+}
+
 void __read_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
 {
        BUG_ON(entry->dev->current_state != PCI_D0);
@@ -363,6 +372,9 @@ void write_msi_msg(unsigned int irq, struct msi_msg *msg)
 static void free_msi_irqs(struct pci_dev *dev)
 {
        struct msi_desc *entry, *tmp;
+       struct attribute **msi_attrs;
+       struct device_attribute *dev_attr;
+       int count = 0;
 
        list_for_each_entry(entry, &dev->msi_list, list) {
                int i, nvec;
@@ -398,6 +410,22 @@ static void free_msi_irqs(struct pci_dev *dev)
                list_del(&entry->list);
                kfree(entry);
        }
+
+       if (dev->msi_irq_groups) {
+               sysfs_remove_groups(&dev->dev.kobj, dev->msi_irq_groups);
+               msi_attrs = dev->msi_irq_groups[0]->attrs;
+               list_for_each_entry(entry, &dev->msi_list, list) {
+                       dev_attr = container_of(msi_attrs[count],
+                                               struct device_attribute, attr);
+                       kfree(dev_attr->attr.name);
+                       kfree(dev_attr);
+                       ++count;
+               }
+               kfree(msi_attrs);
+               kfree(dev->msi_irq_groups[0]);
+               kfree(dev->msi_irq_groups);
+               dev->msi_irq_groups = NULL;
+       }
 }
 
 static struct msi_desc *alloc_msi_entry(struct pci_dev *dev)
@@ -430,7 +458,7 @@ static void __pci_restore_msi_state(struct pci_dev *dev)
 
        pci_intx_for_msi(dev, 0);
        msi_set_enable(dev, 0);
-       arch_restore_msi_irqs(dev, dev->irq);
+       arch_restore_msi_irqs(dev);
 
        pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control);
        msi_mask_irq(entry, msi_capable_mask(control), entry->masked);
@@ -455,8 +483,8 @@ static void __pci_restore_msix_state(struct pci_dev *dev)
        control |= PCI_MSIX_FLAGS_ENABLE | PCI_MSIX_FLAGS_MASKALL;
        pci_write_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, control);
 
+       arch_restore_msi_irqs(dev);
        list_for_each_entry(entry, &dev->msi_list, list) {
-               arch_restore_msi_irqs(dev, entry->irq);
                msix_mask_irq(entry, entry->masked);
        }
 
@@ -471,95 +499,103 @@ void pci_restore_msi_state(struct pci_dev *dev)
 }
 EXPORT_SYMBOL_GPL(pci_restore_msi_state);
 
-
-#define to_msi_attr(obj) container_of(obj, struct msi_attribute, attr)
-#define to_msi_desc(obj) container_of(obj, struct msi_desc, kobj)
-
-struct msi_attribute {
-       struct attribute        attr;
-       ssize_t (*show)(struct msi_desc *entry, struct msi_attribute *attr,
-                       char *buf);
-       ssize_t (*store)(struct msi_desc *entry, struct msi_attribute *attr,
-                        const char *buf, size_t count);
-};
-
-static ssize_t show_msi_mode(struct msi_desc *entry, struct msi_attribute *atr,
+static ssize_t msi_mode_show(struct device *dev, struct device_attribute *attr,
                             char *buf)
 {
-       return sprintf(buf, "%s\n", entry->msi_attrib.is_msix ? "msix" : "msi");
-}
-
-static ssize_t msi_irq_attr_show(struct kobject *kobj,
-                                struct attribute *attr, char *buf)
-{
-       struct msi_attribute *attribute = to_msi_attr(attr);
-       struct msi_desc *entry = to_msi_desc(kobj);
-
-       if (!attribute->show)
-               return -EIO;
-
-       return attribute->show(entry, attribute, buf);
-}
-
-static const struct sysfs_ops msi_irq_sysfs_ops = {
-       .show = msi_irq_attr_show,
-};
-
-static struct msi_attribute mode_attribute =
-       __ATTR(mode, S_IRUGO, show_msi_mode, NULL);
-
+       struct pci_dev *pdev = to_pci_dev(dev);
+       struct msi_desc *entry;
+       unsigned long irq;
+       int retval;
 
-static struct attribute *msi_irq_default_attrs[] = {
-       &mode_attribute.attr,
-       NULL
-};
+       retval = kstrtoul(attr->attr.name, 10, &irq);
+       if (retval)
+               return retval;
 
-static void msi_kobj_release(struct kobject *kobj)
-{
-       struct msi_desc *entry = to_msi_desc(kobj);
-
-       pci_dev_put(entry->dev);
+       list_for_each_entry(entry, &pdev->msi_list, list) {
+               if (entry->irq == irq) {
+                       return sprintf(buf, "%s\n",
+                                      entry->msi_attrib.is_msix ? "msix" : "msi");
+               }
+       }
+       return -ENODEV;
 }
 
-static struct kobj_type msi_irq_ktype = {
-       .release = msi_kobj_release,
-       .sysfs_ops = &msi_irq_sysfs_ops,
-       .default_attrs = msi_irq_default_attrs,
-};
-
 static int populate_msi_sysfs(struct pci_dev *pdev)
 {
+       struct attribute **msi_attrs;
+       struct attribute *msi_attr;
+       struct device_attribute *msi_dev_attr;
+       struct attribute_group *msi_irq_group;
+       const struct attribute_group **msi_irq_groups;
        struct msi_desc *entry;
-       struct kobject *kobj;
-       int ret;
+       int ret = -ENOMEM;
+       int num_msi = 0;
        int count = 0;
 
-       pdev->msi_kset = kset_create_and_add("msi_irqs", NULL, &pdev->dev.kobj);
-       if (!pdev->msi_kset)
-               return -ENOMEM;
+       /* Determine how many msi entries we have */
+       list_for_each_entry(entry, &pdev->msi_list, list) {
+               ++num_msi;
+       }
+       if (!num_msi)
+               return 0;
 
+       /* Dynamically create the MSI attributes for the PCI device */
+       msi_attrs = kzalloc(sizeof(void *) * (num_msi + 1), GFP_KERNEL);
+       if (!msi_attrs)
+               return -ENOMEM;
        list_for_each_entry(entry, &pdev->msi_list, list) {
-               kobj = &entry->kobj;
-               kobj->kset = pdev->msi_kset;
-               pci_dev_get(pdev);
-               ret = kobject_init_and_add(kobj, &msi_irq_ktype, NULL,
-                                    "%u", entry->irq);
-               if (ret)
-                       goto out_unroll;
-
-               count++;
+               char *name = kmalloc(20, GFP_KERNEL);
+               if (!name)
+                       goto error_attrs;
+
+               msi_dev_attr = kzalloc(sizeof(*msi_dev_attr), GFP_KERNEL);
+               if (!msi_dev_attr) {
+                       kfree(name);
+                       goto error_attrs;
+               }
+
+               sprintf(name, "%d", entry->irq);
+               sysfs_attr_init(&msi_dev_attr->attr);
+               msi_dev_attr->attr.name = name;
+               msi_dev_attr->attr.mode = S_IRUGO;
+               msi_dev_attr->show = msi_mode_show;
+               msi_attrs[count] = &msi_dev_attr->attr;
+               ++count;
        }
 
+       msi_irq_group = kzalloc(sizeof(*msi_irq_group), GFP_KERNEL);
+       if (!msi_irq_group)
+               goto error_attrs;
+       msi_irq_group->name = "msi_irqs";
+       msi_irq_group->attrs = msi_attrs;
+
+       msi_irq_groups = kzalloc(sizeof(void *) * 2, GFP_KERNEL);
+       if (!msi_irq_groups)
+               goto error_irq_group;
+       msi_irq_groups[0] = msi_irq_group;
+
+       ret = sysfs_create_groups(&pdev->dev.kobj, msi_irq_groups);
+       if (ret)
+               goto error_irq_groups;
+       pdev->msi_irq_groups = msi_irq_groups;
+
        return 0;
 
-out_unroll:
-       list_for_each_entry(entry, &pdev->msi_list, list) {
-               if (!count)
-                       break;
-               kobject_del(&entry->kobj);
-               kobject_put(&entry->kobj);
-               count--;
+error_irq_groups:
+       kfree(msi_irq_groups);
+error_irq_group:
+       kfree(msi_irq_group);
+error_attrs:
+       count = 0;
+       msi_attr = msi_attrs[count];
+       while (msi_attr) {
+               msi_dev_attr = container_of(msi_attr, struct device_attribute, attr);
+               kfree(msi_attr->name);
+               kfree(msi_dev_attr);
+               ++count;
+               msi_attr = msi_attrs[count];
        }
+       kfree(msi_attrs);
        return ret;
 }
 
@@ -729,7 +765,7 @@ static int msix_capability_init(struct pci_dev *dev,
 
        ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSIX);
        if (ret)
-               goto error;
+               goto out_avail;
 
        /*
         * Some devices require MSI-X to be enabled before we can touch the
@@ -742,10 +778,8 @@ static int msix_capability_init(struct pci_dev *dev,
        msix_program_entries(dev, entries);
 
        ret = populate_msi_sysfs(dev);
-       if (ret) {
-               ret = 0;
-               goto error;
-       }
+       if (ret)
+               goto out_free;
 
        /* Set MSI-X enabled bits and unmask the function */
        pci_intx_for_msi(dev, 0);
@@ -756,7 +790,7 @@ static int msix_capability_init(struct pci_dev *dev,
 
        return 0;
 
-error:
+out_avail:
        if (ret < 0) {
                /*
                 * If we had some success, report the number of irqs
@@ -773,6 +807,7 @@ error:
                        ret = avail;
        }
 
+out_free:
        free_msi_irqs(dev);
 
        return ret;
@@ -823,6 +858,31 @@ static int pci_msi_check_device(struct pci_dev *dev, int nvec, int type)
        return 0;
 }
 
+/**
+ * pci_msi_vec_count - Return the number of MSI vectors a device can send
+ * @dev: device to report about
+ *
+ * This function returns the number of MSI vectors a device requested via
+ * Multiple Message Capable register. It returns a negative errno if the
+ * device is not capable sending MSI interrupts. Otherwise, the call succeeds
+ * and returns a power of two, up to a maximum of 2^5 (32), according to the
+ * MSI specification.
+ **/
+int pci_msi_vec_count(struct pci_dev *dev)
+{
+       int ret;
+       u16 msgctl;
+
+       if (!dev->msi_cap)
+               return -EINVAL;
+
+       pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &msgctl);
+       ret = 1 << ((msgctl & PCI_MSI_FLAGS_QMASK) >> 1);
+
+       return ret;
+}
+EXPORT_SYMBOL(pci_msi_vec_count);
+
 /**
  * pci_enable_msi_block - configure device's MSI capability structure
  * @dev: device to configure
@@ -836,16 +896,16 @@ static int pci_msi_check_device(struct pci_dev *dev, int nvec, int type)
  * 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 pci_enable_msi_block(struct pci_dev *dev, int nvec)
 {
        int status, maxvec;
-       u16 msgctl;
 
-       if (!dev->msi_cap || dev->current_state != PCI_D0)
+       if (dev->current_state != PCI_D0)
                return -EINVAL;
 
-       pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &msgctl);
-       maxvec = 1 << ((msgctl & PCI_MSI_FLAGS_QMASK) >> 1);
+       maxvec = pci_msi_vec_count(dev);
+       if (maxvec < 0)
+               return maxvec;
        if (nvec > maxvec)
                return maxvec;
 
@@ -867,31 +927,6 @@ int pci_enable_msi_block(struct pci_dev *dev, unsigned int nvec)
 }
 EXPORT_SYMBOL(pci_enable_msi_block);
 
-int pci_enable_msi_block_auto(struct pci_dev *dev, unsigned int *maxvec)
-{
-       int ret, nvec;
-       u16 msgctl;
-
-       if (!dev->msi_cap || dev->current_state != PCI_D0)
-               return -EINVAL;
-
-       pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &msgctl);
-       ret = 1 << ((msgctl & PCI_MSI_FLAGS_QMASK) >> 1);
-
-       if (maxvec)
-               *maxvec = ret;
-
-       do {
-               nvec = ret;
-               ret = pci_enable_msi_block(dev, nvec);
-       } while (ret > 0);
-
-       if (ret < 0)
-               return ret;
-       return nvec;
-}
-EXPORT_SYMBOL(pci_enable_msi_block_auto);
-
 void pci_msi_shutdown(struct pci_dev *dev)
 {
        struct msi_desc *desc;
@@ -925,25 +960,28 @@ void pci_disable_msi(struct pci_dev *dev)
 
        pci_msi_shutdown(dev);
        free_msi_irqs(dev);
-       kset_unregister(dev->msi_kset);
-       dev->msi_kset = NULL;
 }
 EXPORT_SYMBOL(pci_disable_msi);
 
 /**
- * pci_msix_table_size - return the number of device's MSI-X table entries
+ * pci_msix_vec_count - 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)
+ * This function returns the number of device's MSI-X table entries and
+ * therefore the number of MSI-X vectors device is capable of sending.
+ * It returns a negative errno if the device is not capable of sending MSI-X
+ * interrupts.
+ **/
+int pci_msix_vec_count(struct pci_dev *dev)
 {
        u16 control;
 
        if (!dev->msix_cap)
-               return 0;
+               return -EINVAL;
 
        pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &control);
        return msix_table_size(control);
 }
+EXPORT_SYMBOL(pci_msix_vec_count);
 
 /**
  * pci_enable_msix - configure device's MSI-X capability structure
@@ -972,7 +1010,9 @@ int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec)
        if (status)
                return status;
 
-       nr_entries = pci_msix_table_size(dev);
+       nr_entries = pci_msix_vec_count(dev);
+       if (nr_entries < 0)
+               return nr_entries;
        if (nvec > nr_entries)
                return nr_entries;
 
@@ -1023,8 +1063,6 @@ void pci_disable_msix(struct pci_dev *dev)
 
        pci_msix_shutdown(dev);
        free_msi_irqs(dev);
-       kset_unregister(dev->msi_kset);
-       dev->msi_kset = NULL;
 }
 EXPORT_SYMBOL(pci_disable_msix);
 
@@ -1079,3 +1117,77 @@ void pci_msi_init_pci_dev(struct pci_dev *dev)
        if (dev->msix_cap)
                msix_set_enable(dev, 0);
 }
+
+/**
+ * pci_enable_msi_range - configure device's MSI capability structure
+ * @dev: device to configure
+ * @minvec: minimal number of interrupts to configure
+ * @maxvec: maximum number of interrupts to configure
+ *
+ * This function tries to allocate a maximum possible number of interrupts in a
+ * range between @minvec and @maxvec. It returns a negative errno if an error
+ * occurs. If it succeeds, it returns the actual number of interrupts allocated
+ * 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_range(struct pci_dev *dev, int minvec, int maxvec)
+{
+       int nvec = maxvec;
+       int rc;
+
+       if (maxvec < minvec)
+               return -ERANGE;
+
+       do {
+               rc = pci_enable_msi_block(dev, nvec);
+               if (rc < 0) {
+                       return rc;
+               } else if (rc > 0) {
+                       if (rc < minvec)
+                               return -ENOSPC;
+                       nvec = rc;
+               }
+       } while (rc);
+
+       return nvec;
+}
+EXPORT_SYMBOL(pci_enable_msi_range);
+
+/**
+ * pci_enable_msix_range - configure device's MSI-X capability structure
+ * @dev: pointer to the pci_dev data structure of MSI-X device function
+ * @entries: pointer to an array of MSI-X entries
+ * @minvec: minimum number of MSI-X irqs requested
+ * @maxvec: maximum number of MSI-X irqs requested
+ *
+ * Setup the MSI-X capability structure of device function with a maximum
+ * possible number of interrupts in the range between @minvec and @maxvec
+ * upon its software driver call to request for MSI-X mode enabled on its
+ * hardware device function. It returns a negative errno if an error occurs.
+ * If it succeeds, it returns the actual number of interrupts allocated and
+ * indicates the successful configuration of MSI-X capability structure
+ * with new allocated MSI-X interrupts.
+ **/
+int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
+                              int minvec, int maxvec)
+{
+       int nvec = maxvec;
+       int rc;
+
+       if (maxvec < minvec)
+               return -ERANGE;
+
+       do {
+               rc = pci_enable_msix(dev, entries, nvec);
+               if (rc < 0) {
+                       return rc;
+               } else if (rc > 0) {
+                       if (rc < minvec)
+                               return -ENOSPC;
+                       nvec = rc;
+               }
+       } while (rc);
+
+       return nvec;
+}
+EXPORT_SYMBOL(pci_enable_msix_range);