diff options
Diffstat (limited to 'drivers/acpi/x86/s2idle.c')
| -rw-r--r-- | drivers/acpi/x86/s2idle.c | 383 |
1 files changed, 275 insertions, 108 deletions
diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 1c507804fb10..6d4d06236f61 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -17,6 +17,7 @@ #include <linux/acpi.h> #include <linux/device.h> +#include <linux/dmi.h> #include <linux/suspend.h> #include "../sleep.h" @@ -58,6 +59,7 @@ static int lps0_dsm_func_mask; static guid_t lps0_dsm_guid_microsoft; static int lps0_dsm_func_mask_microsoft; +static int lps0_dsm_state; /* Device constraint entry structure */ struct lpi_device_info { @@ -86,10 +88,17 @@ struct lpi_device_constraint_amd { int min_dstate; }; +static LIST_HEAD(lps0_s2idle_devops_head); + static struct lpi_constraints *lpi_constraints_table; static int lpi_constraints_table_size; static int rev_id; +#define for_each_lpi_constraint(entry) \ + for (int i = 0; \ + entry = &lpi_constraints_table[i], i < lpi_constraints_table_size; \ + i++) + static void lpi_device_get_constraints_amd(void) { union acpi_object *out_obj; @@ -109,6 +118,12 @@ static void lpi_device_get_constraints_amd(void) union acpi_object *package = &out_obj->package.elements[i]; if (package->type == ACPI_TYPE_PACKAGE) { + if (lpi_constraints_table) { + acpi_handle_err(lps0_device_handle, + "Duplicate constraints list\n"); + goto free_acpi_buffer; + } + lpi_constraints_table = kcalloc(package->package.count, sizeof(*lpi_constraints_table), GFP_KERNEL); @@ -119,17 +134,16 @@ static void lpi_device_get_constraints_amd(void) acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n"); - for (j = 0; j < package->package.count; ++j) { + for (j = 0; j < package->package.count; j++) { union acpi_object *info_obj = &package->package.elements[j]; struct lpi_device_constraint_amd dev_info = {}; struct lpi_constraints *list; acpi_status status; - for (k = 0; k < info_obj->package.count; ++k) { - union acpi_object *obj = &info_obj->package.elements[k]; + list = &lpi_constraints_table[lpi_constraints_table_size]; - list = &lpi_constraints_table[lpi_constraints_table_size]; - list->min_dstate = -1; + for (k = 0; k < info_obj->package.count; k++) { + union acpi_object *obj = &info_obj->package.elements[k]; switch (k) { case 0: @@ -145,27 +159,25 @@ static void lpi_device_get_constraints_amd(void) dev_info.min_dstate = obj->integer.value; break; } + } - if (!dev_info.enabled || !dev_info.name || - !dev_info.min_dstate) - continue; + acpi_handle_debug(lps0_device_handle, + "Name:%s, Enabled: %d, States: %d, MinDstate: %d\n", + dev_info.name, + dev_info.enabled, + dev_info.function_states, + dev_info.min_dstate); - status = acpi_get_handle(NULL, dev_info.name, - &list->handle); - if (ACPI_FAILURE(status)) - continue; + if (!dev_info.enabled || !dev_info.name || + !dev_info.min_dstate) + continue; - acpi_handle_debug(lps0_device_handle, - "Name:%s\n", dev_info.name); + status = acpi_get_handle(NULL, dev_info.name, &list->handle); + if (ACPI_FAILURE(status)) + continue; - list->min_dstate = dev_info.min_dstate; + list->min_dstate = dev_info.min_dstate; - if (list->min_dstate < 0) { - acpi_handle_debug(lps0_device_handle, - "Incomplete constraint defined\n"); - continue; - } - } lpi_constraints_table_size++; } } @@ -210,7 +222,7 @@ static void lpi_device_get_constraints(void) if (!package) continue; - for (j = 0; j < package->package.count; ++j) { + for (j = 0; j < package->package.count; j++) { union acpi_object *element = &(package->package.elements[j]); @@ -242,7 +254,7 @@ static void lpi_device_get_constraints(void) constraint->min_dstate = -1; - for (j = 0; j < package_count; ++j) { + for (j = 0; j < package_count; j++) { union acpi_object *info_obj = &info.package[j]; union acpi_object *cnstr_pkg; union acpi_object *obj; @@ -289,34 +301,74 @@ free_acpi_buffer: static void lpi_check_constraints(void) { - int i; + struct lpi_constraints *entry; + + if (IS_ERR_OR_NULL(lpi_constraints_table)) + return; - for (i = 0; i < lpi_constraints_table_size; ++i) { - acpi_handle handle = lpi_constraints_table[i].handle; - struct acpi_device *adev; + for_each_lpi_constraint(entry) { + struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle); - if (!handle || acpi_bus_get_device(handle, &adev)) + if (!adev) continue; - acpi_handle_debug(handle, + acpi_handle_debug(entry->handle, "LPI: required min power state:%s current power state:%s\n", - acpi_power_state_string(lpi_constraints_table[i].min_dstate), + acpi_power_state_string(entry->min_dstate), acpi_power_state_string(adev->power.state)); if (!adev->flags.power_manageable) { - acpi_handle_info(handle, "LPI: Device not power manageable\n"); - lpi_constraints_table[i].handle = NULL; + acpi_handle_info(entry->handle, "LPI: Device not power manageable\n"); + entry->handle = NULL; continue; } - if (adev->power.state < lpi_constraints_table[i].min_dstate) - acpi_handle_info(handle, + if (adev->power.state < entry->min_dstate) + acpi_handle_info(entry->handle, "LPI: Constraint not met; min power state:%s current power state:%s\n", - acpi_power_state_string(lpi_constraints_table[i].min_dstate), + acpi_power_state_string(entry->min_dstate), acpi_power_state_string(adev->power.state)); } } +static bool acpi_s2idle_vendor_amd(void) +{ + return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; +} + +static const char *acpi_sleep_dsm_state_to_str(unsigned int state) +{ + if (lps0_dsm_func_mask_microsoft || !acpi_s2idle_vendor_amd()) { + switch (state) { + case ACPI_LPS0_SCREEN_OFF: + return "screen off"; + case ACPI_LPS0_SCREEN_ON: + return "screen on"; + case ACPI_LPS0_ENTRY: + return "lps0 entry"; + case ACPI_LPS0_EXIT: + return "lps0 exit"; + case ACPI_LPS0_MS_ENTRY: + return "lps0 ms entry"; + case ACPI_LPS0_MS_EXIT: + return "lps0 ms exit"; + } + } else { + switch (state) { + case ACPI_LPS0_SCREEN_ON_AMD: + return "screen on"; + case ACPI_LPS0_SCREEN_OFF_AMD: + return "screen off"; + case ACPI_LPS0_ENTRY_AMD: + return "lps0 entry"; + case ACPI_LPS0_EXIT_AMD: + return "lps0 exit"; + } + } + + return "unknown"; +} + static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid) { union acpi_object *out_obj; @@ -328,14 +380,15 @@ static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, g rev_id, func, NULL); ACPI_FREE(out_obj); - acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n", - func, out_obj ? "successful" : "failed"); + lps0_dsm_state = func; + if (pm_debug_messages_on) { + acpi_handle_info(lps0_device_handle, + "%s transitioned to state %s\n", + out_obj ? "Successfully" : "Failed to", + acpi_sleep_dsm_state_to_str(lps0_dsm_state)); + } } -static bool acpi_s2idle_vendor_amd(void) -{ - return boot_cpu_data.x86_vendor == X86_VENDOR_AMD; -} static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid) { @@ -343,11 +396,10 @@ static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *d int ret = -EINVAL; guid_parse(uuid, dsm_guid); - obj = acpi_evaluate_dsm(handle, dsm_guid, rev, 0, NULL); /* Check if the _DSM is present and as expected. */ - if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length == 0 || - obj->buffer.length > sizeof(u32)) { + obj = acpi_evaluate_dsm_typed(handle, dsm_guid, rev, 0, NULL, ACPI_TYPE_BUFFER); + if (!obj || obj->buffer.length == 0 || obj->buffer.length > sizeof(u32)) { acpi_handle_debug(handle, "_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev); goto out; @@ -361,42 +413,73 @@ out: return ret; } +struct amd_lps0_hid_device_data { + const bool check_off_by_one; +}; + +static const struct amd_lps0_hid_device_data amd_picasso = { + .check_off_by_one = true, +}; + +static const struct amd_lps0_hid_device_data amd_cezanne = { + .check_off_by_one = false, +}; + +static const struct acpi_device_id amd_hid_ids[] = { + {"AMD0004", (kernel_ulong_t)&amd_picasso, }, + {"AMD0005", (kernel_ulong_t)&amd_picasso, }, + {"AMDI0005", (kernel_ulong_t)&amd_picasso, }, + {"AMDI0006", (kernel_ulong_t)&amd_cezanne, }, + {} +}; + static int lps0_device_attach(struct acpi_device *adev, const struct acpi_device_id *not_used) { if (lps0_device_handle) return 0; - if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) - return 0; - + lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID_MICROSOFT, 0, + &lps0_dsm_guid_microsoft); if (acpi_s2idle_vendor_amd()) { - /* AMD0004, AMDI0005: - * - Should use rev_id 0x0 - * - function mask > 0x3: Should use AMD method, but has off by one bug - * - function mask = 0x3: Should use Microsoft method - * AMDI0006: - * - should use rev_id 0x0 - * - function mask = 0x3: Should use Microsoft method - */ - const char *hid = acpi_device_hid(adev); - rev_id = 0; + static const struct acpi_device_id *dev_id; + const struct amd_lps0_hid_device_data *data; + + for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++) + if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL)) + break; + if (dev_id->id[0]) + data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data; + else + data = &amd_cezanne; lps0_dsm_func_mask = validate_dsm(adev->handle, ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid); - lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle, - ACPI_LPS0_DSM_UUID_MICROSOFT, rev_id, - &lps0_dsm_guid_microsoft); - if (lps0_dsm_func_mask > 0x3 && (!strcmp(hid, "AMD0004") || - !strcmp(hid, "AMDI0005"))) { + if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) { lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1; acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n", ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask); + } else if (lps0_dsm_func_mask_microsoft > 0 && rev_id) { + lps0_dsm_func_mask_microsoft = -EINVAL; + acpi_handle_debug(adev->handle, "_DSM Using AMD method\n"); } } else { rev_id = 1; lps0_dsm_func_mask = validate_dsm(adev->handle, ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid); - lps0_dsm_func_mask_microsoft = -EINVAL; + if (lps0_dsm_func_mask > 0 && lps0_dsm_func_mask_microsoft > 0) { + unsigned int func_mask; + + /* + * Log a message if the _DSM function sets for two + * different UUIDs overlap. + */ + func_mask = lps0_dsm_func_mask & lps0_dsm_func_mask_microsoft; + if (func_mask) + acpi_handle_info(adev->handle, + "Duplicate LPS0 _DSM functions (mask: 0x%x)\n", + func_mask); + } } if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0) @@ -404,28 +487,23 @@ static int lps0_device_attach(struct acpi_device *adev, lps0_device_handle = adev->handle; - if (acpi_s2idle_vendor_amd()) - lpi_device_get_constraints_amd(); - else - lpi_device_get_constraints(); - /* - * Use suspend-to-idle by default if the default suspend mode was not - * set from the command line. + * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in + * the FADT and the default suspend mode was not set from the command + * line. */ - if (mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) + if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) && + mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) { mem_sleep_current = PM_SUSPEND_TO_IDLE; + pr_info("Low-power S0 idle used by default for system suspend\n"); + } /* - * Some Intel based LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U don't - * use intel-hid or intel-vbtn but require the EC GPE to be enabled while - * suspended for certain wakeup devices to work, so mark it as wakeup-capable. - * - * Only enable on !AMD as enabling this universally causes problems for a number - * of AMD based systems. + * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the + * EC GPE to be enabled while suspended for certain wakeup devices to + * work, so mark it as wakeup-capable. */ - if (!acpi_s2idle_vendor_amd()) - acpi_ec_mark_gpe_for_wake(); + acpi_ec_mark_gpe_for_wake(); return 0; } @@ -435,75 +513,164 @@ static struct acpi_scan_handler lps0_handler = { .attach = lps0_device_attach, }; -int acpi_s2idle_prepare_late(void) +static int acpi_s2idle_begin_lps0(void) +{ + if (pm_debug_messages_on && !lpi_constraints_table) { + if (acpi_s2idle_vendor_amd()) + lpi_device_get_constraints_amd(); + else + lpi_device_get_constraints(); + + /* + * Try to retrieve the constraints only once because failures + * to do so usually are sticky. + */ + if (!lpi_constraints_table) + lpi_constraints_table = ERR_PTR(-ENODATA); + } + + return acpi_s2idle_begin(); +} + +static int acpi_s2idle_prepare_late_lps0(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return 0; if (pm_debug_messages_on) lpi_check_constraints(); - if (lps0_dsm_func_mask_microsoft > 0) { + /* Screen off */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_OFF_AMD : + ACPI_LPS0_SCREEN_OFF, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, + + /* LPS0 entry */ + if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, + lps0_dsm_func_mask, lps0_dsm_guid); + + if (lps0_dsm_func_mask_microsoft > 0) { + /* Modern Standby entry */ + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - } else if (acpi_s2idle_vendor_amd()) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF_AMD, - lps0_dsm_func_mask, lps0_dsm_guid); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD, - lps0_dsm_func_mask, lps0_dsm_guid); - } else { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, - lps0_dsm_func_mask, lps0_dsm_guid); + } + + if (lps0_dsm_func_mask > 0 && !acpi_s2idle_vendor_amd()) acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, - lps0_dsm_func_mask, lps0_dsm_guid); + lps0_dsm_func_mask, lps0_dsm_guid); + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->prepare) + handler->prepare(); } return 0; } -void acpi_s2idle_restore_early(void) +static void acpi_s2idle_check_lps0(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return; + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->check) + handler->check(); + } +} + +static void acpi_s2idle_restore_early_lps0(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return; + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) + if (handler->restore) + handler->restore(); + + /* LPS0 exit */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_EXIT_AMD : + ACPI_LPS0_EXIT, + lps0_dsm_func_mask, lps0_dsm_guid); + if (lps0_dsm_func_mask_microsoft > 0) { acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, + /* Modern Standby exit */ + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + } + + /* Screen on */ + if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - } else if (acpi_s2idle_vendor_amd()) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT_AMD, - lps0_dsm_func_mask, lps0_dsm_guid); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON_AMD, - lps0_dsm_func_mask, lps0_dsm_guid); - } else { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, - lps0_dsm_func_mask, lps0_dsm_guid); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, - lps0_dsm_func_mask, lps0_dsm_guid); - } + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_ON_AMD : + ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask, lps0_dsm_guid); } static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { - .begin = acpi_s2idle_begin, + .begin = acpi_s2idle_begin_lps0, .prepare = acpi_s2idle_prepare, - .prepare_late = acpi_s2idle_prepare_late, + .prepare_late = acpi_s2idle_prepare_late_lps0, + .check = acpi_s2idle_check_lps0, .wake = acpi_s2idle_wake, - .restore_early = acpi_s2idle_restore_early, + .restore_early = acpi_s2idle_restore_early_lps0, .restore = acpi_s2idle_restore, .end = acpi_s2idle_end, }; -void acpi_s2idle_setup(void) +void __init acpi_s2idle_setup(void) { acpi_scan_add_handler(&lps0_handler); s2idle_set_ops(&acpi_s2idle_ops_lps0); } +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + unsigned int sleep_flags; + + if (!lps0_device_handle || sleep_no_lps0) + return -ENODEV; + + sleep_flags = lock_system_sleep(); + list_add(&arg->list_node, &lps0_s2idle_devops_head); + unlock_system_sleep(sleep_flags); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); + +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + unsigned int sleep_flags; + + if (!lps0_device_handle || sleep_no_lps0) + return; + + sleep_flags = lock_system_sleep(); + list_del(&arg->list_node); + unlock_system_sleep(sleep_flags); +} +EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); + #endif /* CONFIG_SUSPEND */ |
