Date: Wed, 24 Nov 2004 09:49:18 -0800 From: Nate Lawson <nate@root.org> To: Kevin Oberman <oberman@es.net> Cc: acpi@FreeBSD.org Subject: Re: PATCH: power down acpi and pci devices in suspend/resume Message-ID: <41A4C99E.8050507@root.org> In-Reply-To: <20041124173304.3D7A15D07@ptavv.es.net> References: <20041124173304.3D7A15D07@ptavv.es.net>
next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format. --------------010606030407040208010108 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit 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 --------------010606030407040208010108 Content-Type: text/plain; name="acpi_pwr.diff" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="acpi_pwr.diff" 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); } --------------010606030407040208010108--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?41A4C99E.8050507>