Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 10 Dec 2004 10:47:16 -0800
From:      Nate Lawson <nate@root.org>
To:        Josef Karthauser <joe@FreeBSD.org>
Cc:        freebsd-acpi@FreeBSD.org
Subject:   Re: S3 on a Sony VGN-A290
Message-ID:  <41B9EF34.9020500@root.org>
In-Reply-To: <20041210184350.GJ1615@genius.tao.org.uk>
References:  <20041210133615.GA1482@genius.tao.org.uk> <41B9ED5A.80503@root.org> <20041210184350.GJ1615@genius.tao.org.uk>

next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format.
--------------050102010902040504040104
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Josef Karthauser wrote:
> On Fri, Dec 10, 2004 at 10:39:22AM -0800, Nate Lawson wrote:
> 
>>Josef Karthauser wrote:
>>
>>>Grump.  I leave my new Sony in S3 and come back to it in the morning to
>>>find that it's run out of battery :/.  I thought that S3 was a low
>>>energy state.  Anyone else got a similar machine?  Is it a problem with
>>>the machine or our ACPI?  (I'm running RELENG_5 on it).
>>
>>Try a -current kernel.  It has more code to power down devices while in 
>>suspend but this part is too experimental to MFC for a while.
>>
> 
> 
> Can I run a current kernel on a RELENG_5 userland?

For testing, should work ok.  The recent mount changes may have hosed 
that path though.  Instead, just use the attached patch against 5.x

-- 
Nate

--------------050102010902040504040104
Content-Type: text/plain;
 name="acpi_pwr5.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="acpi_pwr5.diff"

Index: sys/dev/acpica/acpi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi.c,v
retrieving revision 1.186.2.6
diff -u -r1.186.2.6 acpi.c
--- sys/dev/acpica/acpi.c	7 Nov 2004 20:24:05 -0000	1.186.2.6
+++ sys/dev/acpica/acpi.c	30 Nov 2004 20:32:31 -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,14 @@
 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 void	acpi_driver_added(device_t dev, driver_t *driver);
 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 +118,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 +157,14 @@
     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_driver_added,		acpi_driver_added),
     DEVMETHOD(bus_read_ivar,		acpi_read_ivar),
     DEVMETHOD(bus_write_ivar,		acpi_write_ivar),
     DEVMETHOD(bus_get_resource_list,	acpi_get_rlist),
@@ -160,7 +174,6 @@
     DEVMETHOD(bus_release_resource,	acpi_release_resource),
     DEVMETHOD(bus_child_pnpinfo_str,	acpi_child_pnpinfo_str_method),
     DEVMETHOD(bus_child_location_str,	acpi_child_location_str_method),
-    DEVMETHOD(bus_driver_added,		bus_generic_driver_added),
     DEVMETHOD(bus_activate_resource,	bus_generic_activate_resource),
     DEVMETHOD(bus_deactivate_resource,	bus_generic_deactivate_resource),
     DEVMETHOD(bus_setup_intr,		bus_generic_setup_intr),
@@ -169,8 +182,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 +229,12 @@
 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. */
+static int acpi_do_powerstate = 1;
+TUNABLE_INT("debug.acpi.do_powerstate", &acpi_do_powerstate);
+SYSCTL_INT(_debug_acpi, OID_AUTO, do_powerstate, CTLFLAG_RW,
+    &acpi_do_powerstate, 1, "Turn off devices when suspending.");
+
 /*
  * 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 +593,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) && acpi_do_powerstate)
+	    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) && acpi_do_powerstate)
+	    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 +713,45 @@
     return (retval);
 }
 
+/*
+ * 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.
+ */
+static void
+acpi_probe_nomatch(device_t bus, device_t child)
+{
+
+    /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */
+}
+
+/*
+ * If a new driver has a chance to probe a child, first power it up.
+ *
+ * XXX Disabled for now (see acpi_probe_nomatch for details).
+ */
+static void
+acpi_driver_added(device_t dev, driver_t *driver)
+{
+    device_t child, *devlist;
+    int i, numdevs;
+
+    DEVICE_IDENTIFY(driver, dev);
+    device_get_children(dev, &devlist, &numdevs);
+    for (i = 0; i < numdevs; i++) {
+	child = devlist[i];
+	if (device_get_state(child) == DS_NOTPRESENT) {
+	    /* pci_set_powerstate(child, PCI_POWERSTATE_D0); */
+	    if (device_probe_and_attach(child) != 0)
+		; /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */
+	}
+    }
+    free(devlist, M_TEMP);
+}
+
 /* Location hint for devctl(8) */
 static int
 acpi_child_location_str_method(device_t cbdev, device_t child, char *buf,
@@ -1064,6 +1192,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 +1317,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	30 Nov 2004 20:32:31 -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/pci/pci.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/pci/pci.c,v
retrieving revision 1.264
diff -u -r1.264 pci.c
--- sys/dev/pci/pci.c	2 Jul 2004 13:42:36 -0000	1.264
+++ sys/dev/pci/pci.c	30 Nov 2004 20:33:15 -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);
@@ -169,15 +173,15 @@
 SYSCTL_NODE(_hw, OID_AUTO, pci, CTLFLAG_RD, 0, "PCI bus tuning parameters");
 
 static int pci_enable_io_modes = 1;
-TUNABLE_INT("hw.pci.enable_io_modes", (int *)&pci_enable_io_modes);
+TUNABLE_INT("hw.pci.enable_io_modes", &pci_enable_io_modes);
 SYSCTL_INT(_hw_pci, OID_AUTO, enable_io_modes, CTLFLAG_RW,
     &pci_enable_io_modes, 1,
     "Enable I/O and memory bits in the config register.  Some BIOSes do not\n\
 enable these bits correctly.  We'd like to do this all the time, but there\n\
 are some peripherals that this causes problems with.");
 
-static int pci_do_powerstate = 0;
-TUNABLE_INT("hw.pci.do_powerstate", (int *)&pci_do_powerstate);
+static int pci_do_powerstate = 1;
+TUNABLE_INT("hw.pci.do_powerstate", &pci_do_powerstate);
 SYSCTL_INT(_hw_pci, OID_AUTO, do_powerstate, CTLFLAG_RW,
     &pci_do_powerstate, 0,
     "Power down devices into D3 state when no driver attaches to them.\n\
@@ -1015,43 +1019,78 @@
 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 (pci_do_powerstate)
+		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.  Skip
+	 * children who aren't attached since they are powered down
+	 * separately.  Only manage type 0 devices for now.
+	 */
+	for (i = 0; acpi_dev && i < numdevs; i++) {
+		child = devlist[i];
+		dinfo = (struct pci_devinfo *) device_get_ivars(child);
+		if (device_is_attached(child) && dinfo->cfg.hdrtype == 0) {
+			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 (pci_do_powerstate)
+		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.  Only manage type 0 devices for now.
+		 */
 		child = devlist[i];
 		dinfo = (struct pci_devinfo *) device_get_ivars(child);
+		if (acpi_dev && device_is_attached(child) &&
+		    dinfo->cfg.hdrtype == 0) {
+			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. */
 		pci_cfg_restore(child, dinfo);
 	}
 	free(devlist, M_TEMP);

--------------050102010902040504040104--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?41B9EF34.9020500>