PCI: MSI: Remove unsafe and unnecessary hardware access
authorBen Hutchings <bhutchings@solarflare.com>
Thu, 17 Jun 2010 19:16:36 +0000 (20:16 +0100)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Fri, 30 Jul 2010 16:29:34 +0000 (09:29 -0700)
During suspend on an SMP system, {read,write}_msi_msg_desc() may be
called to mask and unmask interrupts on a device that is already in a
reduced power state.  At this point memory-mapped registers including
MSI-X tables are not accessible, and config space may not be fully
functional either.

While a device is in a reduced power state its interrupts are
effectively masked and its MSI(-X) state will be restored when it is
brought back to D0.  Therefore these functions can simply read and
write msi_desc::msg for devices not in D0.

Further, read_msi_msg_desc() should only ever be used to update a
previously written message, so it can always read msi_desc::msg
and never needs to touch the hardware.

Tested-by: "Michael Chan" <mchan@broadcom.com>
Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
drivers/pci/msi.c

index 6a0f2f07f95543ebf2a10f7412ce1927fe2ff9f0..4c14f31f2b4d76d869f307c095778caa2199a644 100644 (file)
@@ -196,30 +196,15 @@ void unmask_msi_irq(unsigned int irq)
 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;
+       /* We do not touch the hardware (which may not even be
+        * accessible at the moment) but return the last message
+        * written.  Assert that this is valid, assuming that
+        * valid messages are not all-zeroes. */
+       BUG_ON(!(entry->msg.address_hi | entry->msg.address_lo |
+                entry->msg.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, 0), &data);
-               }
-               msg->data = data;
-       }
+       *msg = entry->msg;
 }
 
 void read_msi_msg(unsigned int irq, struct msi_msg *msg)
@@ -232,7 +217,10 @@ void read_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_desc_msi(desc);
-       if (entry->msi_attrib.is_msix) {
+
+       if (entry->dev->current_state != PCI_D0) {
+               /* Don't touch the hardware now */
+       } else if (entry->msi_attrib.is_msix) {
                void __iomem *base;
                base = entry->mask_base +
                        entry->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE;