From 3ac9b733723e4a09ad9125ceb51898d904cd5169 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:01 -0500 Subject: [PATCH 1/7] ACPI: Adjust #ifdef for *_lps0_dev use The `#ifdef` for acpi_register_lps0_dev() currently is guarded against `CONFIG_X86`, but actually the functions contained in the block are specifically sleep related functions. Adjust the guard to also check for `CONFIG_SUSPEND`. Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 641dc48439873..6b4966e5d7b10 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1100,7 +1100,7 @@ void acpi_os_set_prepare_extended_sleep(int (*func)(u8 sleep_state, acpi_status acpi_os_prepare_extended_sleep(u8 sleep_state, u32 val_a, u32 val_b); -#ifdef CONFIG_X86 +#if defined(CONFIG_SUSPEND) && defined(CONFIG_X86) struct acpi_s2idle_dev_ops { struct list_head list_node; void (*prepare)(void); @@ -1109,7 +1109,7 @@ struct acpi_s2idle_dev_ops { }; int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg); void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg); -#endif /* CONFIG_X86 */ +#endif /* CONFIG_SUSPEND && CONFIG_X86 */ #ifndef CONFIG_IA64 void arch_reserve_mem_area(acpi_physical_address addr, size_t size); #else From 3c6b1212d20bbbffcad5709ab0f2d5ed9b5859a8 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:02 -0500 Subject: [PATCH 2/7] ACPI: x86: s2idle: Post-increment variables when getting constraints When code uses a pre-increment it makes the reader question "why". In the constraint fetching code there is no reason for the variables to be pre-incremented so adjust to post-increment. No intended functional changes. Reviewed-by: Kuppuswamy Sathyanarayanan Suggested-by: Bjorn Helgaas Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index ce62e61a9605e..7711dde68947f 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -123,13 +123,13 @@ 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) { + 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]; @@ -214,7 +214,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]); @@ -246,7 +246,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; From 883cf0d4cf288313b71146ddebdf5d647b76c78b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:03 -0500 Subject: [PATCH 3/7] ACPI: x86: s2idle: Catch multiple ACPI_TYPE_PACKAGE objects If a badly constructed firmware includes multiple `ACPI_TYPE_PACKAGE` objects while evaluating the AMD LPS0 _DSM, there will be a memory leak. Explicitly guard against this. Suggested-by: Bjorn Helgaas Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 7711dde68947f..508decbac2986 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -113,6 +113,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); From 9cc8cd086f05d9a01026c65c98da88561e9c619e Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:04 -0500 Subject: [PATCH 4/7] ACPI: x86: s2idle: Fix a logic error parsing AMD constraints table The constraints table should be resetting the `list` object after running through all of `info_obj` iterations. This adjusts whitespace as well as less code will now be included with each loop. This fixes a functional problem is fixed where a badly formed package in the inner loop may have incorrect data. Fixes: 146f1ed852a8 ("ACPI: PM: s2idle: Add AMD support to handle _DSM") Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 508decbac2986..60835953ebfc4 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -135,12 +135,11 @@ static void lpi_device_get_constraints_amd(void) struct lpi_constraints *list; acpi_status status; + list = &lpi_constraints_table[lpi_constraints_table_size]; + 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->min_dstate = -1; - switch (k) { case 0: dev_info.enabled = obj->integer.value; @@ -155,27 +154,21 @@ 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; + if (!dev_info.enabled || !dev_info.name || + !dev_info.min_dstate) + continue; - status = acpi_get_handle(NULL, dev_info.name, - &list->handle); - if (ACPI_FAILURE(status)) - continue; + status = acpi_get_handle(NULL, dev_info.name, &list->handle); + if (ACPI_FAILURE(status)) + continue; - acpi_handle_debug(lps0_device_handle, - "Name:%s\n", dev_info.name); + acpi_handle_debug(lps0_device_handle, + "Name:%s\n", dev_info.name); - 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++; } } From a879058d01e2f6e324cc98ff2ffbe4f574c100a6 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:05 -0500 Subject: [PATCH 5/7] ACPI: x86: s2idle: Add more debugging for AMD constraints parsing While parsing the constraints show all the entries for the table to aid with debugging other problems later. Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 60835953ebfc4..87563337a4786 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -156,6 +156,13 @@ static void lpi_device_get_constraints_amd(void) } } + 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); + if (!dev_info.enabled || !dev_info.name || !dev_info.min_dstate) continue; @@ -164,9 +171,6 @@ static void lpi_device_get_constraints_amd(void) if (ACPI_FAILURE(status)) continue; - acpi_handle_debug(lps0_device_handle, - "Name:%s\n", dev_info.name); - list->min_dstate = dev_info.min_dstate; lpi_constraints_table_size++; From 41233988112f0f5fb015172a8db68a3c052aedda Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Aug 2023 14:40:06 -0500 Subject: [PATCH 6/7] ACPI: x86: s2idle: Add for_each_lpi_constraint() helper We have one existing and one coming user of this macro. Introduce a helper. Signed-off-by: Andy Shevchenko Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 87563337a4786..1aa3cd5677bd8 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -94,6 +94,11 @@ 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; @@ -296,30 +301,29 @@ static void lpi_device_get_constraints(void) static void lpi_check_constraints(void) { - int i; + struct lpi_constraints *entry; - for (i = 0; i < lpi_constraints_table_size; ++i) { - acpi_handle handle = lpi_constraints_table[i].handle; - struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + for_each_lpi_constraint(entry) { + struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle); 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)); } } From 1c2a66d47de36608551d73562d01bb4afcf1d61a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Aug 2023 14:40:07 -0500 Subject: [PATCH 7/7] ACPI: x86: s2idle: Add a function to get LPS0 constraint for a device Other parts of the kernel may use LPS0 constraints information to make decisions on what power state to put a device into. Signed-off-by: Mario Limonciello [ rjw: Rewrite kerneldoc, rearrange if () statement, edit subject and changelog ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 24 ++++++++++++++++++++++++ include/linux/acpi.h | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 1aa3cd5677bd8..08f7c67082063 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -299,6 +299,30 @@ static void lpi_device_get_constraints(void) ACPI_FREE(out_obj); } +/** + * acpi_get_lps0_constraint - Get the LPS0 constraint for a device. + * @adev: Device to get the constraint for. + * + * The LPS0 constraint is the shallowest (minimum) power state in which the + * device can be so as to allow the platform as a whole to achieve additional + * energy conservation by utilizing a system-wide low-power state. + * + * Returns: + * - ACPI power state value of the constraint for @adev on success. + * - Otherwise, ACPI_STATE_UNKNOWN. + */ +int acpi_get_lps0_constraint(struct acpi_device *adev) +{ + struct lpi_constraints *entry; + + for_each_lpi_constraint(entry) { + if (adev->handle == entry->handle) + return entry->min_dstate; + } + + return ACPI_STATE_UNKNOWN; +} + static void lpi_check_constraints(void) { struct lpi_constraints *entry; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 6b4966e5d7b10..9e2a15dd33e40 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1109,6 +1109,12 @@ struct acpi_s2idle_dev_ops { }; int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg); void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg); +int acpi_get_lps0_constraint(struct acpi_device *adev); +#else /* CONFIG_SUSPEND && CONFIG_X86 */ +static inline int acpi_get_lps0_constraint(struct device *dev) +{ + return ACPI_STATE_UNKNOWN; +} #endif /* CONFIG_SUSPEND && CONFIG_X86 */ #ifndef CONFIG_IA64 void arch_reserve_mem_area(acpi_physical_address addr, size_t size);