PATCH: power down acpi and pci devices in suspend/resume
Nate Lawson
nate at root.org
Wed Nov 24 09:49:24 PST 2004
Kevin Oberman wrote:
> I have tried the new set of ACPI power patches and they are better. Now
> the system almost works after resume. Only the cbb fails:
> cbb0: bad Vcc request. ctrl=0xffffff88, status=0xffffffff
> cbb_power: 0V
> tdkphy0: detached
Apologies, I just found what was causing this. My patch to perform
suspending before powering down devices didn't get merged with this tree
where I was implementing powerstates. I fixed this and unified pci/acpi
power on suspend behavior under the tunable/sysctl "debug.suspend_power".
Please test the attached patch. If it works well, I'll commit it as
shown to get testing in -current. If it causes trouble, the default for
debug.suspend_power can be set to 0.
-Nate
-------------- next part --------------
Index: sys/dev/acpica/acpi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi.c,v
retrieving revision 1.193
diff -u -r1.193 acpi.c
--- sys/dev/acpica/acpi.c 13 Oct 2004 07:29:29 -0000 1.193
+++ sys/dev/acpica/acpi.c 24 Nov 2004 17:41:34 -0000
@@ -59,6 +59,10 @@
#include <dev/acpica/acpiio.h>
#include <contrib/dev/acpica/acnamesp.h>
+#include "pci_if.h"
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pci_private.h>
+
MALLOC_DEFINE(M_ACPIDEV, "acpidev", "ACPI devices");
/* Hooks for the ACPI CA debugging infrastructure */
@@ -87,10 +91,13 @@
static void acpi_identify(driver_t *driver, device_t parent);
static int acpi_probe(device_t dev);
static int acpi_attach(device_t dev);
+static int acpi_suspend(device_t dev);
+static int acpi_resume(device_t dev);
static int acpi_shutdown(device_t dev);
static device_t acpi_add_child(device_t bus, int order, const char *name,
int unit);
static int acpi_print_child(device_t bus, device_t child);
+static void acpi_probe_nomatch(device_t bus, device_t child);
static int acpi_read_ivar(device_t dev, device_t child, int index,
uintptr_t *result);
static int acpi_write_ivar(device_t dev, device_t child, int index,
@@ -110,10 +117,14 @@
static ACPI_STATUS acpi_device_eval_obj(device_t bus, device_t dev,
ACPI_STRING pathname, ACPI_OBJECT_LIST *parameters,
ACPI_BUFFER *ret);
+static int acpi_device_pwr_for_sleep(device_t bus, device_t dev,
+ int *dstate);
static ACPI_STATUS acpi_device_scan_cb(ACPI_HANDLE h, UINT32 level,
void *context, void **retval);
static ACPI_STATUS acpi_device_scan_children(device_t bus, device_t dev,
int max_depth, acpi_scan_cb_t user_fn, void *arg);
+static int acpi_set_powerstate_method(device_t bus, device_t child,
+ int state);
static int acpi_isa_pnp_probe(device_t bus, device_t child,
struct isa_pnp_id *ids);
static void acpi_probe_children(device_t bus);
@@ -145,12 +156,13 @@
DEVMETHOD(device_attach, acpi_attach),
DEVMETHOD(device_shutdown, acpi_shutdown),
DEVMETHOD(device_detach, bus_generic_detach),
- DEVMETHOD(device_suspend, bus_generic_suspend),
- DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_suspend, acpi_suspend),
+ DEVMETHOD(device_resume, acpi_resume),
/* Bus interface */
DEVMETHOD(bus_add_child, acpi_add_child),
DEVMETHOD(bus_print_child, acpi_print_child),
+ DEVMETHOD(bus_probe_nomatch, acpi_probe_nomatch),
DEVMETHOD(bus_read_ivar, acpi_read_ivar),
DEVMETHOD(bus_write_ivar, acpi_write_ivar),
DEVMETHOD(bus_get_resource_list, acpi_get_rlist),
@@ -169,8 +181,12 @@
/* ACPI bus */
DEVMETHOD(acpi_id_probe, acpi_device_id_probe),
DEVMETHOD(acpi_evaluate_object, acpi_device_eval_obj),
+ DEVMETHOD(acpi_pwr_for_sleep, acpi_device_pwr_for_sleep),
DEVMETHOD(acpi_scan_children, acpi_device_scan_children),
+ /* PCI emulation */
+ DEVMETHOD(pci_set_powerstate, acpi_set_powerstate_method),
+
/* ISA emulation */
DEVMETHOD(isa_pnp_probe, acpi_isa_pnp_probe),
@@ -212,6 +228,9 @@
static int acpi_serialize_methods;
TUNABLE_INT("hw.acpi.serialize_methods", &acpi_serialize_methods);
+/* Power devices off and on in suspend and resume. XXX Remove once tested. */
+extern int do_suspend_power;
+
/*
* ACPI can only be loaded as a module by the loader; activating it after
* system bootstrap time is not useful, and can be fatal to the system.
@@ -570,6 +589,72 @@
}
static int
+acpi_suspend(device_t dev)
+{
+ struct acpi_softc *sc;
+ device_t child, *devlist;
+ int error, i, numdevs, pstate;
+
+ /* First give child devices a chance to suspend. */
+ error = bus_generic_suspend(dev);
+ if (error)
+ return (error);
+
+ /*
+ * Now, set them into the appropriate power state, usually D3. If the
+ * device has an _SxD method for the next sleep state, use that power
+ * state instead.
+ */
+ sc = device_get_softc(dev);
+ device_get_children(dev, &devlist, &numdevs);
+ for (i = 0; i < numdevs; i++) {
+ /* If the device is not attached, we've powered it down elsewhere. */
+ child = devlist[i];
+ if (!device_is_attached(child))
+ continue;
+
+ /*
+ * Default to D3 for all sleep states. The _SxD method is optional
+ * so set the powerstate even if it's absent.
+ */
+ pstate = PCI_POWERSTATE_D3;
+ error = acpi_device_pwr_for_sleep(device_get_parent(child),
+ child, &pstate);
+ if ((error == 0 || error == ESRCH) && do_suspend_power)
+ pci_set_powerstate(child, pstate);
+ }
+ free(devlist, M_TEMP);
+ error = 0;
+
+ return (error);
+}
+
+static int
+acpi_resume(device_t dev)
+{
+ ACPI_HANDLE handle;
+ int i, numdevs;
+ device_t child, *devlist;
+
+ /*
+ * Put all devices in D0 before resuming them. Call _S0D on each one
+ * since some systems expect this.
+ */
+ device_get_children(dev, &devlist, &numdevs);
+ for (i = 0; i < numdevs; i++) {
+ child = devlist[i];
+ handle = acpi_get_handle(child);
+ if (handle)
+ AcpiEvaluateObject(handle, "_S0D", NULL, NULL);
+ if (device_is_attached(child) && do_suspend_power)
+ pci_set_powerstate(child, PCI_POWERSTATE_D0);
+ }
+ free(devlist, M_TEMP);
+
+ return (bus_generic_resume(dev));
+}
+
+static int
acpi_shutdown(device_t dev)
{
@@ -624,6 +709,23 @@
return (retval);
}
+static void
+acpi_probe_nomatch(device_t bus, device_t child)
+{
+
+ /*
+ * If this device is an ACPI child but no one claimed it, attempt
+ * to power it off. We'll power it back up when a driver is added.
+ *
+ * XXX Disabled for now since many necessary devices (like fdc and
+ * ATA) don't claim the devices we created for them but still expect
+ * them to be powered up.
+ */
+#if 0
+ pci_set_powerstate(child, PCI_POWERSTATE_D3);
+#endif
+}
+
/* Location hint for devctl(8) */
static int
acpi_child_location_str_method(device_t cbdev, device_t child, char *buf,
@@ -1064,6 +1166,57 @@
return (AcpiEvaluateObject(h, pathname, parameters, ret));
}
+static int
+acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate)
+{
+ struct acpi_softc *sc;
+ ACPI_HANDLE handle;
+ ACPI_STATUS status;
+ char sxd[8];
+ int error;
+
+ sc = device_get_softc(bus);
+ handle = acpi_get_handle(dev);
+
+ /*
+ * XXX If we find these devices, don't try to power them down.
+ * The serial and IRDA ports on my T23 hang the system when
+ * set to D3 and it appears that such legacy devices may
+ * need special handling in their drivers.
+ */
+ if (handle == NULL ||
+ acpi_MatchHid(handle, "PNP0500") ||
+ acpi_MatchHid(handle, "PNP0501") ||
+ acpi_MatchHid(handle, "PNP0502") ||
+ acpi_MatchHid(handle, "PNP0510") ||
+ acpi_MatchHid(handle, "PNP0511"))
+ return (ENXIO);
+
+ /*
+ * Override next state with the value from _SxD, if present. If no
+ * dstate argument was provided, don't fetch the return value.
+ */
+ snprintf(sxd, sizeof(sxd), "_S%dD", sc->acpi_sstate);
+ if (dstate)
+ status = acpi_GetInteger(handle, sxd, dstate);
+ else
+ status = AcpiEvaluateObject(handle, sxd, NULL, NULL);
+
+ switch (status) {
+ case AE_OK:
+ error = 0;
+ break;
+ case AE_NOT_FOUND:
+ error = ESRCH;
+ break;
+ default:
+ error = ENXIO;
+ break;
+ }
+
+ return (error);
+}
+
/* Callback arg for our implementation of walking the namespace. */
struct acpi_device_scan_ctx {
acpi_scan_cb_t user_fn;
@@ -1138,6 +1291,34 @@
acpi_device_scan_cb, &ctx, NULL));
}
+/*
+ * Even though ACPI devices are not PCI, we use the PCI approach for setting
+ * device power states since it's close enough to ACPI.
+ */
+static int
+acpi_set_powerstate_method(device_t bus, device_t child, int state)
+{
+ ACPI_HANDLE h;
+ ACPI_STATUS status;
+ int error;
+
+ error = 0;
+ h = acpi_get_handle(child);
+ if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3)
+ return (EINVAL);
+ if (h == NULL)
+ return (0);
+
+ /* Ignore errors if the power methods aren't present. */
+ status = acpi_pwr_switch_consumer(h, state);
+ if (ACPI_FAILURE(status) && status != AE_NOT_FOUND
+ && status != AE_BAD_PARAMETER)
+ device_printf(bus, "failed to set ACPI power state D%d on %s: %s\n",
+ state, acpi_name(h), AcpiFormatException(status));
+
+ return (error);
+}
+
static int
acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids)
{
Index: sys/dev/acpica/acpi_if.m
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_if.m,v
retrieving revision 1.2
diff -u -r1.2 acpi_if.m
--- sys/dev/acpica/acpi_if.m 15 Jul 2004 16:29:08 -0000 1.2
+++ sys/dev/acpica/acpi_if.m 20 Nov 2004 03:12:35 -0000
@@ -109,6 +109,26 @@
};
#
+# Get the highest power state (D0-D3) that is usable for a device when
+# suspending/resuming. If a bus calls this when suspending a device, it
+# must also call it when resuming.
+#
+# device_t bus: parent bus for the device
+#
+# device_t dev: check this device's appropriate power state
+#
+# int *dstate: if successful, contains the highest valid sleep state
+#
+# Returns: 0 on success, ESRCH if device has no special state, or
+# some other error value.
+#
+METHOD int pwr_for_sleep {
+ device_t bus;
+ device_t dev;
+ int *dstate;
+};
+
+#
# Rescan a subtree and optionally reattach devices to handles. Users
# specify a callback that is called for each ACPI_HANDLE of type Device
# that is a child of "dev".
Index: sys/dev/acpica/acpi_pci.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pci.c,v
retrieving revision 1.24
diff -u -r1.24 acpi_pci.c
--- sys/dev/acpica/acpi_pci.c 22 Sep 2004 15:46:16 -0000 1.24
+++ sys/dev/acpica/acpi_pci.c 22 Nov 2004 17:54:25 -0000
@@ -59,6 +59,12 @@
ACPI_SERIAL_DECL(pci_powerstate, "ACPI PCI power methods");
+/* Be sure that ACPI and PCI power states are equivalent. */
+CTASSERT(ACPI_STATE_D0 == PCI_POWERSTATE_D0);
+CTASSERT(ACPI_STATE_D1 == PCI_POWERSTATE_D1);
+CTASSERT(ACPI_STATE_D2 == PCI_POWERSTATE_D2);
+CTASSERT(ACPI_STATE_D3 == PCI_POWERSTATE_D3);
+
static int acpi_pci_attach(device_t dev);
static int acpi_pci_child_location_str_method(device_t cbdev,
device_t child, char *buf, size_t buflen);
@@ -183,25 +189,11 @@
{
ACPI_HANDLE h;
ACPI_STATUS status;
- int acpi_state, old_state, error;
+ int old_state, error;
error = 0;
- switch (state) {
- case PCI_POWERSTATE_D0:
- acpi_state = ACPI_STATE_D0;
- break;
- case PCI_POWERSTATE_D1:
- acpi_state = ACPI_STATE_D1;
- break;
- case PCI_POWERSTATE_D2:
- acpi_state = ACPI_STATE_D2;
- break;
- case PCI_POWERSTATE_D3:
- acpi_state = ACPI_STATE_D3;
- break;
- default:
+ if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3)
return (EINVAL);
- }
/*
* We set the state using PCI Power Management outside of setting
@@ -220,11 +212,11 @@
goto out;
}
h = acpi_get_handle(child);
- status = acpi_pwr_switch_consumer(h, acpi_state);
+ status = acpi_pwr_switch_consumer(h, state);
if (ACPI_FAILURE(status) && status != AE_NOT_FOUND)
device_printf(dev,
"Failed to set ACPI power state D%d on %s: %s\n",
- acpi_state, acpi_name(h), AcpiFormatException(status));
+ state, acpi_name(h), AcpiFormatException(status));
if (old_state > state)
error = pci_set_powerstate_method(dev, child, state);
Index: sys/dev/pci/pci.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/pci/pci.c,v
retrieving revision 1.268
diff -u -r1.268 pci.c
--- sys/dev/pci/pci.c 10 Nov 2004 00:41:39 -0000 1.268
+++ sys/dev/pci/pci.c 24 Nov 2004 17:40:58 -0000
@@ -60,6 +60,10 @@
#include "pcib_if.h"
#include "pci_if.h"
+#include <contrib/dev/acpica/acpi.h>
+#include <dev/acpica/acpivar.h>
+#include "acpi_if.h"
+
static uint32_t pci_mapbase(unsigned mapreg);
static int pci_maptype(unsigned mapreg);
static int pci_mapsize(unsigned testval);
@@ -183,6 +187,11 @@
"Power down devices into D3 state when no driver attaches to them.\n\
Otherwise, leave the device in D0 state when no driver attaches.");
+int do_suspend_power = 1;
+TUNABLE_INT("debug.suspend_power", &do_suspend_power);
+SYSCTL_INT(_debug, OID_AUTO, suspend_power, CTLFLAG_RW,
+ &do_suspend_power, 1, "Power down devices into D3 state in suspend.");
+
/* Find a device_t by bus/slot/function */
device_t
@@ -1016,42 +1025,71 @@
int
pci_suspend(device_t dev)
{
- int numdevs;
- device_t *devlist;
- device_t child;
+ int dstate, error, i, numdevs;
+ device_t acpi_dev, child, *devlist;
struct pci_devinfo *dinfo;
- int i;
/*
- * Save the pci configuration space for each child. We don't need
- * to do this, unless the BIOS suspend code powers down the bus and
- * the devices on the bus.
+ * Save the PCI configuration space for each child and set the
+ * device in the appropriate power state for this sleep state.
*/
+ acpi_dev = NULL;
+ if (do_suspend_power)
+ acpi_dev = devclass_get_device(devclass_find("acpi"), 0);
device_get_children(dev, &devlist, &numdevs);
for (i = 0; i < numdevs; i++) {
child = devlist[i];
dinfo = (struct pci_devinfo *) device_get_ivars(child);
pci_cfg_save(child, dinfo, 0);
}
+
+ /* Suspend devices before potentially powering them down. */
+ error = bus_generic_suspend(dev);
+ if (error)
+ return (error);
+
+ /*
+ * Always set the device to D3. If ACPI suggests a different
+ * power state, use it instead. If ACPI is not present, the
+ * firmware is responsible for managing device power.
+ */
+ for (i = 0; acpi_dev && i < numdevs; i++) {
+ child = devlist[i];
+ dstate = PCI_POWERSTATE_D3;
+ ACPI_PWR_FOR_SLEEP(acpi_dev, child, &dstate);
+ pci_set_powerstate(child, dstate);
+ }
free(devlist, M_TEMP);
- return (bus_generic_suspend(dev));
+ return (0);
}
int
pci_resume(device_t dev)
{
- int numdevs;
- device_t *devlist;
- device_t child;
+ int i, numdevs;
+ device_t acpi_dev, child, *devlist;
struct pci_devinfo *dinfo;
- int i;
/*
- * Restore the pci configuration space for each child.
+ * Set each child to D0 and restore its PCI configuration space.
*/
+ acpi_dev = NULL;
+ if (do_suspend_power)
+ acpi_dev = devclass_get_device(devclass_find("acpi"), 0);
device_get_children(dev, &devlist, &numdevs);
for (i = 0; i < numdevs; i++) {
+ /*
+ * Notify ACPI we're going to D0 but ignore the result.
+ * If ACPI is not present, the firmware is responsible for
+ * managing device power.
+ */
child = devlist[i];
+ if (acpi_dev) {
+ ACPI_PWR_FOR_SLEEP(acpi_dev, child, NULL);
+ pci_set_powerstate(child, PCI_POWERSTATE_D0);
+ }
+
+ /* Now the device is powered up, restore its config space. */
dinfo = (struct pci_devinfo *) device_get_ivars(child);
pci_cfg_restore(child, dinfo);
}
More information about the freebsd-acpi
mailing list