From 392ee1e6dd901db6c4504617476f6442ed91f72d Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 8 Mar 2007 13:04:57 -0700 Subject: [PATCH] msi: Safer state caching. There are two ways pci_save_state and pci_restore_state are used. As helper functions during suspend/resume, and as helper functions around a hardware reset event. When used as helper functions around a hardware reset event there is no reason to believe the calls will be paired, nor is there a good reason to believe that if we restore the msi state from before the reset that it will match the current msi state. Since arch code may change the msi message without going through the driver, drivers currently do not have enough information to even know when to call pci_save_state to ensure they will have msi state in sync with the other kernel irq reception data structures. It turns out the solution is straight forward, cache the state in the existing msi data structures (not the magic pci saved things) and have the msi code update the cached state each time we write to the hardware. This means we never need to read the hardware to figure out what the hardware state should be. By modifying the caching in this manner we get to remove our save_state routines and only need to provide restore_state routines. The only fields that were at all tricky to regenerate were the msi and msi-x control registers and the way we regenerate them currently is a bit dependent upon assumptions on how we use the allow msi registers to be configured and used making the code a little bit brittle. If we ever change what cases we allow or how we configure the msi bits we can address the fragility then. Signed-off-by: Eric W. Biederman Signed-off-by: Greg Kroah-Hartman Acked-by: Auke Kok Signed-off-by: Linus Torvalds --- drivers/pci/msi.c | 150 +++++++++--------------------------------------------- drivers/pci/pci.c | 2 - drivers/pci/pci.h | 2 - 3 files changed, 25 insertions(+), 129 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index 01869b1782e4..ad33e0159514 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -100,6 +100,7 @@ static void msi_set_mask_bit(unsigned int irq, int flag) BUG(); break; } + entry->msi_attrib.masked = !!flag; } void read_msi_msg(unsigned int irq, struct msi_msg *msg) @@ -179,6 +180,7 @@ void write_msi_msg(unsigned int irq, struct msi_msg *msg) default: BUG(); } + entry->msg = *msg; } void mask_msi_irq(unsigned int irq) @@ -225,164 +227,60 @@ static struct msi_desc* alloc_msi_entry(void) } #ifdef CONFIG_PM -static int __pci_save_msi_state(struct pci_dev *dev) -{ - int pos, i = 0; - u16 control; - struct pci_cap_saved_state *save_state; - u32 *cap; - - if (!dev->msi_enabled) - return 0; - - pos = pci_find_capability(dev, PCI_CAP_ID_MSI); - if (pos <= 0) - 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"); - 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; -} - static void __pci_restore_msi_state(struct pci_dev *dev) { - int i = 0, pos; + int pos; u16 control; - struct pci_cap_saved_state *save_state; - u32 *cap; + struct msi_desc *entry; if (!dev->msi_enabled) return; - 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]; + entry = get_irq_msi(dev->irq); + pos = entry->msi_attrib.pos; pci_intx(dev, 0); /* disable intx */ - control = cap[i++] >> 16; msi_set_enable(dev, 0); - 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++]); + 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; pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control); - pci_remove_saved_cap(save_state); - kfree(save_state); -} - -static int __pci_save_msix_state(struct pci_dev *dev) -{ - int pos; - int irq, head, tail = 0; - u16 control; - struct pci_cap_saved_state *save_state; - - if (!dev->msix_enabled) - return 0; - - pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); - if (pos <= 0) - return 0; - - /* save the capability */ - pci_read_config_word(dev, msi_control_reg(pos), &control); - 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; - } - *((u16 *)&save_state->data[0]) = control; - - /* save the table */ - irq = head = dev->first_msi_irq; - while (head != tail) { - struct msi_desc *entry; - - entry = get_irq_msi(irq); - read_msi_msg(irq, &entry->msg_save); - - tail = entry->link.tail; - irq = tail; - } - - save_state->cap_nr = PCI_CAP_ID_MSIX; - pci_add_saved_cap(dev, save_state); - return 0; -} - -int pci_save_msi_state(struct pci_dev *dev) -{ - int rc; - - rc = __pci_save_msi_state(dev); - if (rc) - return rc; - - rc = __pci_save_msix_state(dev); - - return rc; } static void __pci_restore_msix_state(struct pci_dev *dev) { - u16 save; int pos; int irq, head, tail = 0; struct msi_desc *entry; - struct pci_cap_saved_state *save_state; + u16 control; if (!dev->msix_enabled) return; - 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; - /* route the table */ pci_intx(dev, 0); /* disable intx */ msix_set_enable(dev, 0); irq = head = dev->first_msi_irq; + entry = get_irq_msi(irq); + pos = entry->msi_attrib.pos; while (head != tail) { entry = get_irq_msi(irq); - write_msi_msg(irq, &entry->msg_save); + write_msi_msg(irq, &entry->msg); + msi_set_mask_bit(irq, entry->msi_attrib.masked); tail = entry->link.tail; irq = tail; } - pci_write_config_word(dev, msi_control_reg(pos), save); + 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); } void pci_restore_msi_state(struct pci_dev *dev) @@ -420,6 +318,7 @@ static int msi_capability_init(struct pci_dev *dev) 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)) { @@ -507,6 +406,7 @@ static int msix_capability_init(struct pci_dev *dev, 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; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a32db0628157..6048c0c637a0 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -638,8 +638,6 @@ pci_save_state(struct pci_dev *dev) /* XXX: 100% dword access ok here? */ for (i = 0; i < 16; i++) pci_read_config_dword(dev, i * 4,&dev->saved_config_space[i]); - if ((i = pci_save_msi_state(dev)) != 0) - return i; if ((i = pci_save_pcie_state(dev)) != 0) return i; if ((i = pci_save_pcix_state(dev)) != 0) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ae7a975995a5..62ea04c8af64 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -52,10 +52,8 @@ static inline void pci_no_msi(void) { } #endif #if defined(CONFIG_PCI_MSI) && defined(CONFIG_PM) -int pci_save_msi_state(struct pci_dev *dev); void pci_restore_msi_state(struct pci_dev *dev); #else -static inline int pci_save_msi_state(struct pci_dev *dev) { return 0; } static inline void pci_restore_msi_state(struct pci_dev *dev) {} #endif -- cgit v1.2.2 From 9f35575dfc172f0a93fb464761883c8f49599b7a Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 8 Mar 2007 13:06:13 -0700 Subject: [PATCH] pci: Repair pci_save/restore_state so we can restore one save many times. Because we do not reserve space for the pci-x and pci-e state in struct pci dev we need to dynamically allocate it. However because we need to support restore being called multiple times after a single save it is never safe to free the buffers we have allocated to hold the state. So this patch modifies the save routines to first check to see if we have already allocated a state buffer before allocating a new one. Then the restore routines are modified to not free the state after restoring it. Simple and it fixes some subtle error path handling bugs, that are hard to test for. Signed-off-by: Eric W. Biederman Signed-off-by: Greg Kroah-Hartman Acked-by: Auke Kok Signed-off-by: Linus Torvalds --- drivers/pci/pci.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 6048c0c637a0..d3eab057b2d3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -551,7 +551,9 @@ static int pci_save_pcie_state(struct pci_dev *dev) if (pos <= 0) return 0; - save_state = kzalloc(sizeof(*save_state) + sizeof(u16) * 4, GFP_KERNEL); + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + if (!save_state) + save_state = kzalloc(sizeof(*save_state) + sizeof(u16) * 4, GFP_KERNEL); if (!save_state) { dev_err(&dev->dev, "Out of memory in pci_save_pcie_state\n"); return -ENOMEM; @@ -582,8 +584,6 @@ static void pci_restore_pcie_state(struct pci_dev *dev) pci_write_config_word(dev, pos + PCI_EXP_LNKCTL, cap[i++]); pci_write_config_word(dev, pos + PCI_EXP_SLTCTL, cap[i++]); pci_write_config_word(dev, pos + PCI_EXP_RTCTL, cap[i++]); - pci_remove_saved_cap(save_state); - kfree(save_state); } @@ -597,7 +597,9 @@ static int pci_save_pcix_state(struct pci_dev *dev) if (pos <= 0) return 0; - save_state = kzalloc(sizeof(*save_state) + sizeof(u16), GFP_KERNEL); + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + if (!save_state) + save_state = kzalloc(sizeof(*save_state) + sizeof(u16), GFP_KERNEL); if (!save_state) { dev_err(&dev->dev, "Out of memory in pci_save_pcie_state\n"); return -ENOMEM; @@ -622,8 +624,6 @@ static void pci_restore_pcix_state(struct pci_dev *dev) cap = (u16 *)&save_state->data[0]; pci_write_config_word(dev, pos + PCI_X_CMD, cap[i++]); - pci_remove_saved_cap(save_state); - kfree(save_state); } -- cgit v1.2.2 From 9208ee8286ea2c0136a4bc58638b0295bad791c8 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 24 Mar 2007 16:56:44 +0100 Subject: PCI: Stop unhiding the SMBus on Toshiba laptops It was found that the Toshiba laptops with hidden Intel SMBus have SMM code handling the thermal management which accesses the SMBus. Thus it is not safe to unhide it and let Linux access it. We have to leave the SMBus hidden. SMM is a pain, really. This fixes bugs #6315 and #6395, for good this time. Signed-off-by: Jean Delvare Signed-off-by: Greg Kroah-Hartman --- drivers/pci/quirks.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 7f94fc098cd3..65d6f23ead41 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -963,6 +963,13 @@ DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237, k8t_sound_ho * bridge. Unfortunately, this device has no subvendor/subdevice ID. So it * becomes necessary to do this tweak in two steps -- I've chosen the Host * bridge as trigger. + * + * Note that we used to unhide the SMBus that way on Toshiba laptops + * (Satellite A40 and Tecra M2) but then found that the thermal management + * was done by SMM code, which could cause unsynchronized concurrent + * accesses to the SMBus registers, with potentially bad effects. Thus you + * should be very careful when adding new entries: if SMM is accessing the + * Intel SMBus, this is a very good reason to leave it hidden. */ static int asus_hides_smbus; @@ -1040,17 +1047,6 @@ static void __init asus_hides_smbus_hostbridge(struct pci_dev *dev) case 0x099c: /* HP Compaq nx6110 */ asus_hides_smbus = 1; } - } else if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_TOSHIBA)) { - if (dev->device == PCI_DEVICE_ID_INTEL_82855GM_HB) - switch(dev->subsystem_device) { - case 0x0001: /* Toshiba Satellite A40 */ - asus_hides_smbus = 1; - } - else if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) - switch(dev->subsystem_device) { - case 0x0001: /* Toshiba Tecra M2 */ - asus_hides_smbus = 1; - } } else if (unlikely(dev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG)) { if (dev->device == PCI_DEVICE_ID_INTEL_82855PM_HB) switch(dev->subsystem_device) { -- cgit v1.2.2 From bf5b4ba3d9f37d41d940a92822b534561abf4f4c Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Fri, 23 Mar 2007 12:15:05 -0400 Subject: PCI: Fix warning message in PCIE port driver PCIE error output should conform to vendor_id:device_id. Signed-off-by: Prarit Bhargava Signed-off-by: Greg Kroah-Hartman --- drivers/pci/pcie/portdrv_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 0be5a0b30725..df383645e366 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -93,7 +93,7 @@ static int __devinit pcie_portdrv_probe (struct pci_dev *dev, if (!dev->irq && dev->pin) { printk(KERN_WARNING "%s->Dev[%04x:%04x] has invalid IRQ. Check vendor BIOS\n", - __FUNCTION__, dev->device, dev->vendor); + __FUNCTION__, dev->vendor, dev->device); } if (pcie_port_device_register(dev)) { pci_disable_device(dev); -- cgit v1.2.2 From 348e3fd19487534d9d4dd70c3ad0b751afd35792 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 3 Apr 2007 01:41:49 -0600 Subject: [PATCH] msi: synchronously mask and unmask msi-x irqs. This is a simplified and actually more comprehensive form of a bug fix from Mitch Williams . When we mask or unmask a msi-x irqs the writes may be posted because we are writing to memory mapped region. This means the mask and unmask don't happen immediately but at some unspecified time in the future. Which is out of sync with how the mask/unmask logic work for ioapic irqs. The practical result is that we get very subtle and hard to track down irq migration bugs. This patch performs a read flush after writes to the MSI-X table for mask and unmask operations. Since the SMP affinity is set while the interrupt is masked, and since it's unmasked immediately after, no additional flushes are required in the various affinity setting routines. The testing by Mitch Williams on his especially problematic system should still be valid as I have only simplified the code, not changed the functionality. We currently have 7 drivers: cciss, mthca, cxgb3, forceth, s2io, pcie/portdrv_core, and qla2xxx in 2.6.21 that are affected by this problem when the hardware they driver is plugged into the right slot. Given the difficulty of reproducing this bug and tracing it down to anything that even remotely resembles a cause, even if people are being affected we aren't likely to see many meaningful bug reports, and the people who see this bug aren't likely to be able to reproduce this bug in a timely fashion. So it is best to get this problem fixed as soon as we can so people don't have problems. Then if people do have a kernel message stating "No irq for vector" we will know it is yet another novel cause that needs a complete new investigation. Cc: Greg KH Cc: Andrew Morton Signed-off-by: "Eric W. Biederman" Acked-by: Mitch Williams Acked-by: "Siddha, Suresh B" Signed-off-by: Linus Torvalds --- drivers/pci/msi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pci') diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index ad33e0159514..435c1958a7b7 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -94,6 +94,7 @@ static void msi_set_mask_bit(unsigned int irq, int flag) 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: -- cgit v1.2.2 From 01abc2aa0f447bce2f6beb06dd0607ba0f01c5bb Mon Sep 17 00:00:00 2001 From: Bartlomiej Zolnierkiewicz Date: Mon, 23 Apr 2007 23:19:36 +0200 Subject: Revert "adjust legacy IDE resource setting (v2)" This reverts commit ed8ccee0918ad063a4741c0656fda783e02df627. It causes hang on boot for some users and we don't yet know why: http://bugzilla.kernel.org/show_bug.cgi?id=7562 http://lkml.org/lkml/2007/4/20/404 http://lkml.org/lkml/2007/3/25/113 Just reverse it for 2.6.21-final, having broken X server is somehow better than unbootable system. Signed-off-by: Bartlomiej Zolnierkiewicz --- drivers/pci/probe.c | 45 +++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index a4a96826d9e0..2fe1d690eb13 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -682,34 +682,7 @@ static void pci_read_irq(struct pci_dev *dev) dev->irq = irq; } -static void change_legacy_io_resource(struct pci_dev * dev, unsigned index, - unsigned start, unsigned end) -{ - unsigned base = start & PCI_BASE_ADDRESS_IO_MASK; - unsigned len = (end | ~PCI_BASE_ADDRESS_IO_MASK) - base + 1; - - /* - * Some X versions get confused when the BARs reported through - * /sys or /proc differ from those seen in config space, thus - * try to update the config space values, too. - */ - if (!(pci_resource_flags(dev, index) & IORESOURCE_IO)) - printk(KERN_WARNING "%s: cannot adjust BAR%u (not I/O)\n", - pci_name(dev), index); - else if (pci_resource_len(dev, index) != len) - printk(KERN_WARNING "%s: cannot adjust BAR%u (size %04X)\n", - pci_name(dev), index, (unsigned)pci_resource_len(dev, index)); - else { - printk(KERN_INFO "%s: trying to change BAR%u from %04X to %04X\n", - pci_name(dev), index, - (unsigned)pci_resource_start(dev, index), base); - pci_write_config_dword(dev, PCI_BASE_ADDRESS_0 + index * 4, base); - } - pci_resource_start(dev, index) = start; - pci_resource_end(dev, index) = end; - pci_resource_flags(dev, index) = - IORESOURCE_IO | IORESOURCE_PCI_FIXED | PCI_BASE_ADDRESS_SPACE_IO; -} +#define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED) /** * pci_setup_device - fill in class and map information of a device @@ -762,12 +735,20 @@ static int pci_setup_device(struct pci_dev * dev) u8 progif; pci_read_config_byte(dev, PCI_CLASS_PROG, &progif); if ((progif & 1) == 0) { - change_legacy_io_resource(dev, 0, 0x1F0, 0x1F7); - change_legacy_io_resource(dev, 1, 0x3F6, 0x3F6); + dev->resource[0].start = 0x1F0; + dev->resource[0].end = 0x1F7; + dev->resource[0].flags = LEGACY_IO_RESOURCE; + dev->resource[1].start = 0x3F6; + dev->resource[1].end = 0x3F6; + dev->resource[1].flags = LEGACY_IO_RESOURCE; } if ((progif & 4) == 0) { - change_legacy_io_resource(dev, 2, 0x170, 0x177); - change_legacy_io_resource(dev, 3, 0x376, 0x376); + dev->resource[2].start = 0x170; + dev->resource[2].end = 0x177; + dev->resource[2].flags = LEGACY_IO_RESOURCE; + dev->resource[3].start = 0x376; + dev->resource[3].end = 0x376; + dev->resource[3].flags = LEGACY_IO_RESOURCE; } } break; -- cgit v1.2.2