diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h --- a/sys/compat/linuxkpi/common/include/linux/pci.h +++ b/sys/compat/linuxkpi/common/include/linux/pci.h @@ -212,11 +212,11 @@ typedef int pci_power_t; -#define PCI_D0 PCI_POWERSTATE_D0 -#define PCI_D1 PCI_POWERSTATE_D1 -#define PCI_D2 PCI_POWERSTATE_D2 -#define PCI_D3hot PCI_POWERSTATE_D3 -#define PCI_D3cold 4 +#define PCI_D0 PCI_POWERSTATE_D0 +#define PCI_D1 PCI_POWERSTATE_D1 +#define PCI_D2 PCI_POWERSTATE_D2 +#define PCI_D3hot PCI_POWERSTATE_D3_HOT +#define PCI_D3cold PCI_POWERSTATE_D3_COLD #define PCI_POWER_ERROR PCI_POWERSTATE_UNKNOWN diff --git a/sys/contrib/dev/acpica/include/actypes.h b/sys/contrib/dev/acpica/include/actypes.h --- a/sys/contrib/dev/acpica/include/actypes.h +++ b/sys/contrib/dev/acpica/include/actypes.h @@ -741,9 +741,11 @@ #define ACPI_STATE_D0 (UINT8) 0 #define ACPI_STATE_D1 (UINT8) 1 #define ACPI_STATE_D2 (UINT8) 2 -#define ACPI_STATE_D3 (UINT8) 3 -#define ACPI_D_STATES_MAX ACPI_STATE_D3 -#define ACPI_D_STATE_COUNT 4 +#define ACPI_STATE_D3_HOT (UINT8) 3 +#define ACPI_STATE_D3_COLD (UINT8) 4 +#define ACPI_STATE_D3 ACPI_STATE_D3_COLD +#define ACPI_D_STATES_MAX ACPI_STATE_D3_COLD +#define ACPI_D_STATE_COUNT 5 #define ACPI_STATE_C0 (UINT8) 0 #define ACPI_STATE_C1 (UINT8) 1 diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -2080,12 +2080,13 @@ status = acpi_pwr_switch_consumer(h, state); if (ACPI_SUCCESS(status)) { if (bootverbose) - device_printf(child, "set ACPI power state D%d on %s\n", - state, acpi_name(h)); + device_printf(child, "set ACPI power state %s on %s\n", + acpi_d_state_to_str(state), acpi_name(h)); } else if (status != AE_NOT_FOUND) device_printf(child, - "failed to set ACPI power state D%d on %s: %s\n", state, - acpi_name(h), AcpiFormatException(status)); + "failed to set ACPI power state %s on %s: %s\n", + acpi_d_state_to_str(state), acpi_name(h), + AcpiFormatException(status)); return (0); } diff --git a/sys/dev/acpica/acpi_pci.c b/sys/dev/acpica/acpi_pci.c --- a/sys/dev/acpica/acpi_pci.c +++ b/sys/dev/acpica/acpi_pci.c @@ -53,9 +53,6 @@ #include -#include "pcib_if.h" -#include "pci_if.h" - /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("PCI") @@ -240,12 +237,13 @@ status = acpi_pwr_switch_consumer(h, state); if (ACPI_SUCCESS(status)) { if (bootverbose) - device_printf(dev, "set ACPI power state D%d on %s\n", - state, acpi_name(h)); + device_printf(dev, "set ACPI power state %s on %s\n", + acpi_d_state_to_str(state), acpi_name(h)); } else if (status != AE_NOT_FOUND) device_printf(dev, - "failed to set ACPI power state D%d on %s: %s\n", - state, acpi_name(h), AcpiFormatException(status)); + "failed to set ACPI power state %s on %s: %s\n", + acpi_d_state_to_str(state), acpi_name(h), + AcpiFormatException(status)); if (old_state > state && pci_do_power_resume) error = pci_set_powerstate_method(dev, child, state); diff --git a/sys/dev/acpica/acpi_powerres.c b/sys/dev/acpica/acpi_powerres.c --- a/sys/dev/acpica/acpi_powerres.c +++ b/sys/dev/acpica/acpi_powerres.c @@ -76,6 +76,13 @@ /* Device which is powered */ ACPI_HANDLE ac_consumer; int ac_state; + + struct { + bool prx_has; + size_t prx_count; + ACPI_HANDLE *prx_deps; + } ac_prx[ACPI_D_STATE_COUNT]; + TAILQ_ENTRY(acpi_powerconsumer) ac_link; TAILQ_HEAD(,acpi_powerreference) ac_references; }; @@ -221,6 +228,76 @@ } #endif /* notyet */ +/* + * Evaluate the _PRx (power resources each D-state depends on). This also + * populates the acpi_powerresources queue with the power resources discovered + * during this step. + * + * ACPI 7.3.8 - 7.3.11 guarantee that _PRx will return the same data each + * time they are evaluated. + */ +static ACPI_STATUS +acpi_pwr_get_power_resources(ACPI_HANDLE consumer, struct acpi_powerconsumer *pc) +{ + ACPI_INTEGER status; + ACPI_STRING reslist_name; + ACPI_HANDLE reslist_handle; + ACPI_STRING reslist_names[] = {"_PR0", "_PR1", "_PR2", "_PR3"}; + ACPI_BUFFER reslist; + ACPI_OBJECT *reslist_object; + ACPI_OBJECT *dep; + ACPI_HANDLE *res; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(powerres); + + if (consumer == NULL) + return_ACPI_STATUS (AE_NOT_FOUND); + + for (int state = ACPI_STATE_D0; state <= ACPI_STATE_D3_HOT; state++) { + pc->ac_prx[state].prx_has = false; + pc->ac_prx[state].prx_count = 0; + pc->ac_prx[state].prx_deps = NULL; + + reslist_name = reslist_names[state - ACPI_STATE_D0]; + if (ACPI_FAILURE(AcpiGetHandle(consumer, reslist_name, &reslist_handle))) + continue; + + reslist.Pointer = NULL; + reslist.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObjectTyped(reslist_handle, NULL, NULL, &reslist, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status) || reslist.Pointer == NULL) + continue; + + reslist_object = (ACPI_OBJECT *)reslist.Pointer; + pc->ac_prx[state].prx_has = true; + pc->ac_prx[state].prx_count = reslist_object->Package.Count; + + if (reslist_object->Package.Count == 0) + continue; + + pc->ac_prx[state].prx_deps = malloc(sizeof(*pc->ac_prx[state].prx_deps), + M_ACPIPWR, M_NOWAIT); + if (pc->ac_prx[state].prx_deps == NULL) { + AcpiOsFree(reslist_object); + return_ACPI_STATUS (AE_NO_MEMORY); + } + + for (size_t i = 0; i < reslist_object->Package.Count; i++) { + dep = &reslist_object->Package.Elements[i]; + res = dep->Reference.Handle; + pc->ac_prx[state].prx_deps[i] = res; + + /* It's fine to attempt to register the same resource twice. */ + acpi_pwr_register_resource(res); + } + AcpiOsFree(reslist_object); + } + + return_ACPI_STATUS (AE_OK); +} + /* * Register a power consumer. * @@ -245,8 +322,15 @@ TAILQ_INIT(&pc->ac_references); pc->ac_consumer = consumer; - /* XXX we should try to find its current state */ - pc->ac_state = ACPI_STATE_UNKNOWN; + /* + * Get all its power resource dependencies, if it has _PRx. We do this now + * as an opportunity to populate the acpi_powerresources queue. + */ + acpi_pwr_get_power_resources(consumer, pc); + + /* Find its initial state. */ + if (ACPI_FAILURE(acpi_pwr_get_state(consumer, &pc->ac_state))) + pc->ac_state = ACPI_STATE_UNKNOWN; ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "registered power consumer %s\n", acpi_name(consumer))); @@ -279,6 +363,8 @@ /* Pull the consumer off the list and free it */ TAILQ_REMOVE(&acpi_powerconsumers, pc, ac_link); + for (size_t i = 0; i < sizeof(pc->ac_prx) / sizeof(*pc->ac_prx); i++) + free(pc->ac_prx[i].prx_deps, M_ACPIPWR); free(pc, M_ACPIPWR); ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "deregistered power consumer %s\n", @@ -289,7 +375,129 @@ #endif /* notyet */ /* - * Set a power consumer to a particular power state. + * The _PSC control method isn't required if it's possible to infer the D-state + * from the _PRx control methods. (See 7.3.6.) + * We can infer that a given D-state has been achieved when all the dependencies + * are in the ON state. + */ +static ACPI_STATUS +acpi_pwr_infer_state(ACPI_HANDLE consumer, struct acpi_powerconsumer *pc) +{ + ACPI_HANDLE *res; + uint32_t on; + bool all_on = false; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_SERIAL_ASSERT(powerres); + + if (consumer == NULL) + return_ACPI_STATUS (AE_NOT_FOUND); + + /* It is important we go from the hottest to the coldest state. */ + for ( + pc->ac_state = ACPI_STATE_D0; + pc->ac_state <= ACPI_STATE_D3_HOT && !all_on; + pc->ac_state++ + ) { + MPASS(pc->ac_state <= sizeof(pc->ac_prx) / sizeof(*pc->ac_prx)); + + if (!pc->ac_prx[pc->ac_state].prx_has) + continue; + + all_on = true; + + for (size_t i = 0; i < pc->ac_prx[pc->ac_state].prx_count; i++) { + res = pc->ac_prx[pc->ac_state].prx_deps[i]; + /* If failure, better to assume D-state is hotter than colder. */ + if (ACPI_FAILURE(acpi_GetInteger(res, "_STA", &on))) + continue; + if (on == 0) { + all_on = false; + break; + } + } + } + + MPASS(pc->ac_state != ACPI_STATE_D0); + + /* + * If none of the power resources required for the shallower D-states are + * on, then we can assume it is unpowered (i.e. D3cold). A device is not + * required to support D3cold however; in that case, _PR3 is not explicitly + * provided. Those devices should default to D3hot instead. + */ + if (!all_on) + pc->ac_state = pc->ac_prx[ACPI_STATE_D3_HOT].prx_has ? + ACPI_STATE_D3_COLD : ACPI_STATE_D3_HOT; + else + pc->ac_state--; + + return_ACPI_STATUS (AE_OK); +} + +/* + * Get a power consumer's D-state. + */ +ACPI_STATUS +acpi_pwr_get_state(ACPI_HANDLE consumer, int *state) +{ + + struct acpi_powerconsumer *pc; + ACPI_HANDLE method_handle; + ACPI_STATUS status; + ACPI_BUFFER result; + ACPI_OBJECT *object = NULL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (consumer == NULL) + return_ACPI_STATUS (AE_NOT_FOUND); + + ACPI_SERIAL_BEGIN(powerres); + + if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) { + if (ACPI_FAILURE(status = acpi_pwr_register_consumer(consumer))) + goto out; + if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) + panic("acpi added power consumer but can't find it"); + } + + status = AcpiGetHandle(consumer, "_PSC", &method_handle); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "no _PSC object - %s\n", + AcpiFormatException(status))); + status = acpi_pwr_infer_state(consumer, pc); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "couldn't infer D-state - %s\n", + AcpiFormatException(status))); + pc->ac_state = ACPI_STATE_UNKNOWN; + } + goto out; + } + + result.Pointer = NULL; + result.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObjectTyped(method_handle, NULL, NULL, &result, ACPI_TYPE_INTEGER); + if (ACPI_FAILURE(status) || result.Pointer == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to get state with _PSC - %s\n", + AcpiFormatException(status))); + pc->ac_state = ACPI_STATE_UNKNOWN; + goto out; + } + + object = (ACPI_OBJECT *)result.Pointer; + pc->ac_state = ACPI_STATE_D0 + object->Integer.Value; + +out: + ACPI_SERIAL_END(powerres); + if (object != NULL) + AcpiOsFree(object); + *state = pc->ac_state; + return_ACPI_STATUS (AE_OK); +} + +/* + * Set a power consumer to a particular D-state. */ ACPI_STATUS acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state) @@ -299,7 +507,7 @@ ACPI_BUFFER reslist_buffer; ACPI_OBJECT *reslist_object; ACPI_STATUS status; - char *method_name, *reslist_name; + char *method_name, *reslist_name = NULL; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); @@ -318,9 +526,26 @@ panic("acpi added power consumer but can't find it"); } - /* Check for valid transitions. We can only go to D0 from D3. */ + /* Stop here if we're already at the target D-state. */ + if (pc->ac_state == state) { + status = AE_OK; + goto out; + } + + /* + * Check for valid transitions. From D3hot or D3cold, we can only go to D0. + * The exception to this is going from D3hot to D3cold or the other way + * around. This is because they both use _PS3, so the only difference when + * doing these transitions is whether or not the power resources for _PR3 + * are on for devices which support D3cold, and turning these power + * resources on/off is always perfectly fine (ACPI 7.3.11). + */ status = AE_BAD_PARAMETER; - if (pc->ac_state == ACPI_STATE_D3 && state != ACPI_STATE_D0) + if (pc->ac_state == ACPI_STATE_D3_HOT && state != ACPI_STATE_D0 && + state != ACPI_STATE_D3_COLD) + goto out; + if (pc->ac_state == ACPI_STATE_D3_COLD && state != ACPI_STATE_D0 && + state != ACPI_STATE_D3_HOT) goto out; /* Find transition mechanism(s) */ @@ -337,15 +562,20 @@ method_name = "_PS2"; reslist_name = "_PR2"; break; - case ACPI_STATE_D3: + case ACPI_STATE_D3_HOT: method_name = "_PS3"; reslist_name = "_PR3"; break; + case ACPI_STATE_D3_COLD: + method_name = "_PS3"; + reslist_name = NULL; + break; default: goto out; } - ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s D%d -> D%d\n", - acpi_name(consumer), pc->ac_state, state)); + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s %s -> %s\n", + acpi_name(consumer), acpi_d_state_to_str(pc->ac_state), + acpi_d_state_to_str(state))); /* * Verify that this state is supported, ie. one of method or @@ -367,9 +597,12 @@ status = AE_OK; goto out; } - if (state != ACPI_STATE_D3) { + if (state == ACPI_STATE_D3_COLD) + state = ACPI_STATE_D3_HOT; + if (state != ACPI_STATE_D3_HOT) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, - "attempt to set unsupported state D%d\n", state)); + "attempt to set unsupported state %s\n", + acpi_d_state_to_str(state))); goto out; } @@ -380,21 +613,23 @@ if (ACPI_FAILURE(AcpiGetHandle(consumer, "_PR0", &pr0_handle))) { status = AE_NOT_FOUND; ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, - "device missing _PR0 (desired state was D%d)\n", state)); + "device missing _PR0 (desired state was %s)\n", + acpi_d_state_to_str(state))); goto out; } reslist_buffer.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(pr0_handle, NULL, NULL, &reslist_buffer); if (ACPI_FAILURE(status)) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, - "can't evaluate _PR0 for device %s, state D%d\n", - acpi_name(consumer), state)); + "can't evaluate _PR0 for device %s, state %s\n", + acpi_name(consumer), acpi_d_state_to_str(state))); goto out; } reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer; if (!ACPI_PKG_VALID(reslist_object, 1)) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, - "invalid package object for state D%d\n", state)); + "invalid package object for state %s\n", + acpi_d_state_to_str(state))); status = AE_TYPE; goto out; } @@ -450,8 +685,8 @@ */ if (ACPI_FAILURE(status = acpi_pwr_switch_power())) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, - "failed to switch resources from %s to D%d\n", - acpi_name(consumer), state)); + "failed to switch resources from %s to %s\n", + acpi_name(consumer), acpi_d_state_to_str(state))); /* XXX is this appropriate? Should we return to previous state? */ goto out; @@ -473,8 +708,20 @@ } } + /* Make sure the transition succeeded. */ + int new_state; + if (ACPI_FAILURE(acpi_pwr_get_state(consumer, &new_state))) + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to get new D-state\n")); + else if (new_state != state) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, + "new power state %s is not the one requested %s\n", + acpi_d_state_to_str(new_state), + acpi_d_state_to_str(state))); + pc->ac_state = new_state; + } else + pc->ac_state = state; + /* Transition was successful */ - pc->ac_state = state; status = AE_OK; out: diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h --- a/sys/dev/acpica/acpivar.h +++ b/sys/dev/acpica/acpivar.h @@ -461,6 +461,7 @@ /* Device power control. */ ACPI_STATUS acpi_pwr_wake_enable(ACPI_HANDLE consumer, int enable); +ACPI_STATUS acpi_pwr_get_state(ACPI_HANDLE consumer, int *state); ACPI_STATUS acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state); acpi_pwr_for_sleep_t acpi_device_pwr_for_sleep; int acpi_set_powerstate(device_t child, int state); @@ -488,6 +489,15 @@ return (0); } +static __inline char const * +acpi_d_state_to_str(int state) +{ + char const *strs[ACPI_D_STATE_COUNT] = {"D0", "D1", "D2", "D3", "D3cold"}; + + MPASS(state >= ACPI_STATE_D0 && state <= ACPI_D_STATES_MAX); + return strs[state]; +} + char *acpi_name(ACPI_HANDLE handle); int acpi_avoid(ACPI_HANDLE handle); int acpi_disabled(char *subsys); diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c --- a/sys/dev/pci/pci.c +++ b/sys/dev/pci/pci.c @@ -81,6 +81,9 @@ #include +#include +#include + #include "pcib_if.h" #include "pci_if.h" @@ -2846,8 +2849,8 @@ } if (bootverbose) - pci_printf(cfg, "Transition from D%d to D%d\n", oldstate, - state); + pci_printf(cfg, "Transition from %s to %s\n", + acpi_d_state_to_str(oldstate), acpi_d_state_to_str(state)); PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status, 2); if (delay) diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h --- a/sys/dev/pci/pcivar.h +++ b/sys/dev/pci/pcivar.h @@ -501,22 +501,25 @@ /* * PCI power states are as defined by ACPI: * - * D0 State in which device is on and running. It is receiving full - * power from the system and delivering full functionality to the user. - * D1 Class-specific low-power state in which device context may or may not - * be lost. Buses in D1 cannot do anything to the bus that would force - * devices on that bus to lose context. - * D2 Class-specific low-power state in which device context may or may - * not be lost. Attains greater power savings than D1. Buses in D2 - * can cause devices on that bus to lose some context. Devices in D2 - * must be prepared for the bus to be in D2 or higher. - * D3 State in which the device is off and not running. Device context is - * lost. Power can be removed from the device. + * D0 State in which device is on and running. It is receiving full + * power from the system and delivering full functionality to the user. + * D1 Class-specific low-power state in which device context may or may not + * be lost. Buses in D1 cannot do anything to the bus that would force + * devices on that bus to lose context. + * D2 Class-specific low-power state in which device context may or may + * not be lost. Attains greater power savings than D1. Buses in D2 + * can cause devices on that bus to lose some context. Devices in D2 + * must be prepared for the bus to be in D2 or higher. + * D3hot State in which the device is off and not running. Device context is + * lost. Power can be removed from the device. + * D3cold Same as D3hot, but power has been removed from the device. */ #define PCI_POWERSTATE_D0 0 #define PCI_POWERSTATE_D1 1 #define PCI_POWERSTATE_D2 2 -#define PCI_POWERSTATE_D3 3 +#define PCI_POWERSTATE_D3_HOT 3 +#define PCI_POWERSTATE_D3_COLD 4 +#define PCI_POWERSTATE_D3 PCI_POWERSTATE_D3_COLD #define PCI_POWERSTATE_UNKNOWN -1 static __inline int