As pea@ noticed, Apple machines with an NVIDIA MCP79 chipset will
suffer an interrupt storm when you send them WOL packet to their
nfe(4) interface. The acpi0 interrupt handler fires continously in
this case. Some digging revealed that the device wake GPE for the
nfe(4) interface was casuing this. The machine's AML sends a
notification, suggesting that the device driver for the device needs
to take some action. But ours doesn't.
Some more digging revealed that the PCIe PME status bit gets set.
Clearing that bit stops the interrupts and makes the machine happy
again.
So here is a diff that installs a notification handler for all PCI
devices that clears the PME status bit if the device has one. This
seems to be what Windows does. At least I found some Microsoft
documentation that says that the PCI driver is responsible for
clearing the PME status bit if a PME event is received.
ok?
Index: acpi.c
===================================================================
RCS file: /cvs/src/sys/dev/acpi/acpi.c,v
retrieving revision 1.267
diff -u -p -r1.267 acpi.c
--- acpi.c 20 Jul 2014 18:05:21 -0000 1.267
+++ acpi.c 6 Sep 2014 18:54:38 -0000
@@ -74,6 +74,7 @@ int acpi_hasprocfvs;
void acpi_pci_match(struct device *, struct pci_attach_args *);
pcireg_t acpi_pci_min_powerstate(pci_chipset_tag_t, pcitag_t);
void acpi_pci_set_powerstate(pci_chipset_tag_t, pcitag_t, int, int);
+int acpi_pci_notify(struct aml_node *, int, void *);
int acpi_match(struct device *, void *, void *);
void acpi_attach(struct device *, struct device *, void *);
@@ -567,6 +568,8 @@ acpi_pci_match(struct device *dev, struc
state = pci_get_powerstate(pa->pa_pc, pa->pa_tag);
acpi_pci_set_powerstate(pa->pa_pc, pa->pa_tag, state, 1);
acpi_pci_set_powerstate(pa->pa_pc, pa->pa_tag, state, 0);
+
+ aml_register_notify(pdev->node, NULL, acpi_pci_notify, pdev, 0);
}
}
@@ -660,6 +663,29 @@ acpi_pci_set_powerstate(pci_chipset_tag_
}
#endif /* NACPIPWRRES > 0 */
+}
+
+int
+acpi_pci_notify(struct aml_node *node, int ntype, void *arg)
+{
+ struct acpi_pci *pdev = arg;
+ pci_chipset_tag_t pc = NULL;
+ pcitag_t tag;
+ pcireg_t reg;
+ int offset;
+
+ /* We're only interested in Device Wake notifications. */
+ if (ntype != 2)
+ return (0);
+
+ tag = pci_make_tag(pc, pdev->bus, pdev->dev, pdev->fun);
+ if (pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, 0)) {
+ /* Clear the PME Status bit if it is set. */
+ reg = pci_conf_read(pc, tag, offset + PCI_PMCSR);
+ pci_conf_write(pc, tag, offset + PCI_PMCSR, reg);
+ }
+
+ return (0);
}
void