From 3041c928024b20ce6469373946c68314fe5a1340 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 9 Jun 2021 13:40:17 -0500 Subject: [PATCH 01/18] ACPI: Check StorageD3Enable _DSD property in ACPI code Although first implemented for NVME, this check may be usable by other drivers as well. Microsoft's specification explicitly mentions that is may be usable by SATA and AHCI devices. Google also indicates that they have used this with SDHCI in a downstream kernel tree that a user can plug a storage device into. Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro Suggested-by: Keith Busch CC: Shyam-sundar S-k CC: Alexander Deucher CC: Rafael J. Wysocki CC: Prike Liang Signed-off-by: Mario Limonciello Reviewed-by: Rafael J. Wysocki Signed-off-by: Christoph Hellwig --- drivers/acpi/device_pm.c | 29 +++++++++++++++++++++++++++++ drivers/nvme/host/pci.c | 28 +--------------------------- include/linux/acpi.h | 5 +++++ 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 6de9bed7c1b28b..a001f4589ca084 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -1339,4 +1339,33 @@ int acpi_dev_pm_attach(struct device *dev, bool power_on) return 1; } EXPORT_SYMBOL_GPL(acpi_dev_pm_attach); + +/** + * acpi_storage_d3 - Check if D3 should be used in the suspend path + * @dev: Device to check + * + * Return %true if the platform firmware wants @dev to be programmed + * into D3hot or D3cold (if supported) in the suspend path, or %false + * when there is no specific preference. On some platforms, if this + * hint is ignored, @dev may remain unresponsive after suspending the + * platform as a whole. + * + * Although the property has storage in the name it actually is + * applied to the PCIe slot and plugging in a non-storage device the + * same platform restrictions will likely apply. + */ +bool acpi_storage_d3(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + u8 val; + + if (!adev) + return false; + if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", + &val)) + return false; + return val == 1; +} +EXPORT_SYMBOL_GPL(acpi_storage_d3); + #endif /* CONFIG_PM */ diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 32b7eba8a2b42a..1159f0e53703d8 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2828,32 +2828,6 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev) return 0; } -#ifdef CONFIG_ACPI -static bool nvme_acpi_storage_d3(struct pci_dev *dev) -{ - struct acpi_device *adev = ACPI_COMPANION(&dev->dev); - u8 val; - - /* - * Look for _DSD property specifying that the storage device on the port - * must use D3 to support deep platform power savings during - * suspend-to-idle. - */ - - if (!adev) - return false; - if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", - &val)) - return false; - return val == 1; -} -#else -static inline bool nvme_acpi_storage_d3(struct pci_dev *dev) -{ - return false; -} -#endif /* CONFIG_ACPI */ - static void nvme_async_probe(void *data, async_cookie_t cookie) { struct nvme_dev *dev = data; @@ -2903,7 +2877,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) quirks |= check_vendor_combination_bug(pdev); - if (!noacpi && nvme_acpi_storage_d3(pdev)) { + if (!noacpi && acpi_storage_d3(&pdev->dev)) { /* * Some systems use a bios work around to ask for D3 on * platforms that support kernel managed suspend. diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 2f7508c3c2d6a1..394a060ce83da0 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -992,6 +992,7 @@ int acpi_dev_resume(struct device *dev); int acpi_subsys_runtime_suspend(struct device *dev); int acpi_subsys_runtime_resume(struct device *dev); int acpi_dev_pm_attach(struct device *dev, bool power_on); +bool acpi_storage_d3(struct device *dev); #else static inline int acpi_subsys_runtime_suspend(struct device *dev) { return 0; } static inline int acpi_subsys_runtime_resume(struct device *dev) { return 0; } @@ -999,6 +1000,10 @@ static inline int acpi_dev_pm_attach(struct device *dev, bool power_on) { return 0; } +static inline bool acpi_storage_d3(struct device *dev) +{ + return false; +} #endif #if defined(CONFIG_ACPI) && defined(CONFIG_PM_SLEEP) From 2b52f9603e22a792ebba975ae8e7c916c7231fc1 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 9 Jun 2021 13:40:18 -0500 Subject: [PATCH 02/18] ACPI: Add quirks for AMD Renoir/Lucienne CPUs to force the D3 hint AMD systems from Renoir and Lucienne require that the NVME controller is put into D3 over a Modern Standby / suspend-to-idle cycle. This is "typically" accomplished using the `StorageD3Enable` property in the _DSD, but this property was introduced after many of these systems launched and most OEM systems don't have it in their BIOS. On AMD Renoir without these drives going into D3 over suspend-to-idle the resume will fail with the NVME controller being reset and a trace like this in the kernel logs: ``` [ 83.556118] nvme nvme0: I/O 161 QID 2 timeout, aborting [ 83.556178] nvme nvme0: I/O 162 QID 2 timeout, aborting [ 83.556187] nvme nvme0: I/O 163 QID 2 timeout, aborting [ 83.556196] nvme nvme0: I/O 164 QID 2 timeout, aborting [ 95.332114] nvme nvme0: I/O 25 QID 0 timeout, reset controller [ 95.332843] nvme nvme0: Abort status: 0x371 [ 95.332852] nvme nvme0: Abort status: 0x371 [ 95.332856] nvme nvme0: Abort status: 0x371 [ 95.332859] nvme nvme0: Abort status: 0x371 [ 95.332909] PM: dpm_run_callback(): pci_pm_resume+0x0/0xe0 returns -16 [ 95.332936] nvme 0000:03:00.0: PM: failed to resume async: error -16 ``` The Microsoft documentation for StorageD3Enable mentioned that Windows has a hardcoded allowlist for D3 support, which was used for these platforms. Introduce quirks to hardcode them for Linux as well. As this property is now "standardized", OEM systems using AMD Cezanne and newer APU's have adopted this property, and quirks like this should not be necessary. CC: Shyam-sundar S-k CC: Alexander Deucher CC: Prike Liang Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro Signed-off-by: Mario Limonciello Acked-by: Rafael J. Wysocki Tested-by: Julian Sikorski Signed-off-by: Christoph Hellwig --- drivers/acpi/device_pm.c | 3 +++ drivers/acpi/internal.h | 9 +++++++++ drivers/acpi/x86/utils.c | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index a001f4589ca084..98efcd27d0f375 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -1359,6 +1359,9 @@ bool acpi_storage_d3(struct device *dev) struct acpi_device *adev = ACPI_COMPANION(dev); u8 val; + if (force_storage_d3()) + return true; + if (!adev) return false; if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index cb8f70842249e3..96471be3f0c869 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -236,6 +236,15 @@ static inline int suspend_nvs_save(void) { return 0; } static inline void suspend_nvs_restore(void) {} #endif +#ifdef CONFIG_X86 +bool force_storage_d3(void); +#else +static inline bool force_storage_d3(void) +{ + return false; +} +#endif + /*-------------------------------------------------------------------------- Device properties -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c index bdc1ba00aee9f7..f22f23933063b7 100644 --- a/drivers/acpi/x86/utils.c +++ b/drivers/acpi/x86/utils.c @@ -135,3 +135,28 @@ bool acpi_device_always_present(struct acpi_device *adev) return ret; } + +/* + * AMD systems from Renoir and Lucienne *require* that the NVME controller + * is put into D3 over a Modern Standby / suspend-to-idle cycle. + * + * This is "typically" accomplished using the `StorageD3Enable` + * property in the _DSD that is checked via the `acpi_storage_d3` function + * but this property was introduced after many of these systems launched + * and most OEM systems don't have it in their BIOS. + * + * The Microsoft documentation for StorageD3Enable mentioned that Windows has + * a hardcoded allowlist for D3 support, which was used for these platforms. + * + * This allows quirking on Linux in a similar fashion. + */ +static const struct x86_cpu_id storage_d3_cpu_ids[] = { + X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 96, NULL), /* Renoir */ + X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 104, NULL), /* Lucienne */ + {} +}; + +bool force_storage_d3(void) +{ + return x86_match_cpu(storage_d3_cpu_ids); +} From e646f63107fdd8a7272ea6061d9c0c2558616d80 Mon Sep 17 00:00:00 2001 From: Pratik Vishwakarma Date: Thu, 17 Jun 2021 11:42:08 -0500 Subject: [PATCH 03/18] ACPI: PM: s2idle: Use correct revision id AMD spec mentions only revision 0. With this change, device constraint list is populated properly. Signed-off-by: Pratik Vishwakarma Tested-by: Julian Sikorski Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 2d7ddb8a8cb656..da27c1c45c9f83 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -96,7 +96,7 @@ static void lpi_device_get_constraints_amd(void) int i, j, k; out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, - 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, + rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, NULL, ACPI_TYPE_PACKAGE); if (!out_obj) From eff4ff57a435361f978f982b36db2f86ce614254 Mon Sep 17 00:00:00 2001 From: Pratik Vishwakarma Date: Thu, 17 Jun 2021 11:42:09 -0500 Subject: [PATCH 04/18] ACPI: PM: s2idle: Refactor common code Refactor common code to prepare for upcoming changes. * Remove unused struct. * Print error before returning. * Frees ACPI obj if _DSM type is not as expected. * Treat lps0_dsm_func_mask as an integer rather than character * Remove extra out_obj * Move rev_id Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Pratik Vishwakarma Tested-by: Julian Sikorski Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 67 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index da27c1c45c9f83..c0cba025072fb0 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -49,7 +49,7 @@ static const struct acpi_device_id lps0_device_ids[] = { static acpi_handle lps0_device_handle; static guid_t lps0_dsm_guid; -static char lps0_dsm_func_mask; +static int lps0_dsm_func_mask; /* Device constraint entry structure */ struct lpi_device_info { @@ -70,15 +70,7 @@ struct lpi_constraints { int min_dstate; }; -/* AMD */ -/* Device constraint entry structure */ -struct lpi_device_info_amd { - int revision; - int count; - union acpi_object *package; -}; - -/* Constraint package structure */ +/* AMD Constraint package structure */ struct lpi_device_constraint_amd { char *name; int enabled; @@ -99,12 +91,12 @@ static void lpi_device_get_constraints_amd(void) rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, NULL, ACPI_TYPE_PACKAGE); - if (!out_obj) - return; - acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n", out_obj ? "successful" : "failed"); + if (!out_obj) + return; + for (i = 0; i < out_obj->package.count; i++) { union acpi_object *package = &out_obj->package.elements[i]; @@ -336,11 +328,33 @@ 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) +{ + union acpi_object *obj; + 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)) { + acpi_handle_debug(handle, + "_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev); + goto out; + } + + ret = *(int *)obj->buffer.pointer; + acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret); + +out: + ACPI_FREE(obj); + return ret; +} + static int lps0_device_attach(struct acpi_device *adev, const struct acpi_device_id *not_used) { - union acpi_object *out_obj; - if (lps0_device_handle) return 0; @@ -348,28 +362,17 @@ static int lps0_device_attach(struct acpi_device *adev, return 0; if (acpi_s2idle_vendor_amd()) { - guid_parse(ACPI_LPS0_DSM_UUID_AMD, &lps0_dsm_guid); - out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 0, 0, NULL); rev_id = 0; + lps0_dsm_func_mask = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid); } else { - guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid); - out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL); rev_id = 1; + lps0_dsm_func_mask = validate_dsm(adev->handle, + ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid); } - /* Check if the _DSM is present and as expected. */ - if (!out_obj || out_obj->type != ACPI_TYPE_BUFFER) { - acpi_handle_debug(adev->handle, - "_DSM function 0 evaluation failed\n"); - return 0; - } - - lps0_dsm_func_mask = *(char *)out_obj->buffer.pointer; - - ACPI_FREE(out_obj); - - acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", - lps0_dsm_func_mask); + if (lps0_dsm_func_mask < 0) + return 0;//function eval failed lps0_device_handle = adev->handle; From 3cea4c9d0b255b7b4a46dfe03389e899deb047b1 Mon Sep 17 00:00:00 2001 From: Pratik Vishwakarma Date: Thu, 17 Jun 2021 11:42:10 -0500 Subject: [PATCH 05/18] ACPI: PM: s2idle: Add support for multiple func mask Required for follow-up patch adding new UUID needing new function mask. Signed-off-by: Pratik Vishwakarma Tested-by: Julian Sikorski Signed-off-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index c0cba025072fb0..0d19669ac7ade6 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -309,14 +309,15 @@ static void lpi_check_constraints(void) } } -static void acpi_sleep_run_lps0_dsm(unsigned int func) +static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid) { union acpi_object *out_obj; - if (!(lps0_dsm_func_mask & (1 << func))) + if (!(func_mask & (1 << func))) return; - out_obj = acpi_evaluate_dsm(lps0_device_handle, &lps0_dsm_guid, rev_id, func, NULL); + out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid, + rev_id, func, NULL); ACPI_FREE(out_obj); acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n", @@ -412,11 +413,15 @@ int acpi_s2idle_prepare_late(void) lpi_check_constraints(); if (acpi_s2idle_vendor_amd()) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF_AMD); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_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); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY); + acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, + lps0_dsm_func_mask, lps0_dsm_guid); + acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, + lps0_dsm_func_mask, lps0_dsm_guid); } return 0; @@ -428,11 +433,15 @@ void acpi_s2idle_restore_early(void) return; if (acpi_s2idle_vendor_amd()) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT_AMD); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON_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); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON); + 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); } } From 2d7e1d9564eaa5103ed9b689d69f77345d52b615 Mon Sep 17 00:00:00 2001 From: Pratik Vishwakarma Date: Thu, 17 Jun 2021 11:42:11 -0500 Subject: [PATCH 06/18] ACPI: PM: s2idle: Add support for new Microsoft UUID This adds supports for _DSM notifications to the Microsoft UUID described by Microsoft documentation for s2idle. Link: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-firmware-notifications Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Pratik Vishwakarma Tested-by: Julian Sikorski Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 0d19669ac7ade6..3f2a90648ec933 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -32,6 +32,9 @@ static const struct acpi_device_id lps0_device_ids[] = { {"", }, }; +/* Microsoft platform agnostic UUID */ +#define ACPI_LPS0_DSM_UUID_MICROSOFT "11e00d56-ce64-47ce-837b-1f898f9aa461" + #define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" #define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1 @@ -39,6 +42,8 @@ static const struct acpi_device_id lps0_device_ids[] = { #define ACPI_LPS0_SCREEN_ON 4 #define ACPI_LPS0_ENTRY 5 #define ACPI_LPS0_EXIT 6 +#define ACPI_LPS0_MS_ENTRY 7 +#define ACPI_LPS0_MS_EXIT 8 /* AMD */ #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" @@ -51,6 +56,9 @@ static acpi_handle lps0_device_handle; static guid_t lps0_dsm_guid; static int lps0_dsm_func_mask; +static guid_t lps0_dsm_guid_microsoft; +static int lps0_dsm_func_mask_microsoft; + /* Device constraint entry structure */ struct lpi_device_info { char *name; @@ -366,14 +374,18 @@ static int lps0_device_attach(struct acpi_device *adev, rev_id = 0; 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); } 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) - return 0;//function eval failed + if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0) + return 0; //function evaluation failed lps0_device_handle = adev->handle; @@ -412,7 +424,14 @@ int acpi_s2idle_prepare_late(void) if (pm_debug_messages_on) lpi_check_constraints(); - if (acpi_s2idle_vendor_amd()) { + 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_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, @@ -432,7 +451,14 @@ void acpi_s2idle_restore_early(void) if (!lps0_device_handle || sleep_no_lps0) return; - if (acpi_s2idle_vendor_amd()) { + 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, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + 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, From e2ae49eef5a7121ba348d4d1f7d2d05b32708d27 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Jun 2021 11:42:12 -0500 Subject: [PATCH 07/18] ACPI: PM: Adjust behavior for field problems on AMD systems Some AMD Systems with uPEP _HID AMD004/AMDI005 have an off by one bug in their function mask return. This means that they will call entrance but not exit for matching functions. Other AMD systems with this HID should use the Microsoft generic UUID. AMD systems with uPEP HID AMDI006 should be using the Microsoft method. Signed-off-by: Mario Limonciello Tested-by: Julian Sikorski Tested-by: Kai-Heng Feng Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 3f2a90648ec933..816bf2c34b7a97 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -371,12 +371,27 @@ static int lps0_device_attach(struct acpi_device *adev, return 0; 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; 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"))) { + 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 { rev_id = 1; lps0_dsm_func_mask = validate_dsm(adev->handle, From 259bb5e7085227ef6af505ab1e969879ae16f4b1 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 30 Jun 2021 14:46:06 -0500 Subject: [PATCH 08/18] ACPI: PM: Only mark EC GPE for wakeup on Intel systems When using s2idle on a variety of AMD notebook systems, they are experiencing spurious events that the EC or SMU are in the wrong state leading to a hard time waking up or higher than expected power consumption. These events only occur when the EC GPE is inadvertently set as a wakeup source. Originally the EC GPE was only set as a wakeup source when using the intel-vbtn or intel-hid drivers in commit 10a08fd65ec1 ("ACPI: PM: Set up EC GPE for system wakeup from drivers that need it") but during testing a reporter discovered that this was not enough for their ASUS Zenbook UX430UNR/i7-8550U to wakeup by lid event or keypress. Marking the EC GPE for wakeup universally resolved this for that reporter in commit b90ff3554aa3 ("ACPI: PM: s2idle: Always set up EC GPE for system wakeup"). However this behavior has lead to a number of problems: * On both Lenovo T14 and P14s the keyboard wakeup doesn't work, and sometimes the power button event doesn't work. * On HP 635 G7 detaching or attaching AC during suspend will cause the system not to wakeup * On Asus vivobook to prevent detaching AC causing resume problems * On Lenovo 14ARE05 to prevent detaching AC causing resume problems * On HP ENVY x360 to prevent detaching AC causing resume problems As there may be other Intel systems besides ASUS Zenbook UX430UNR/i7-8550U that don't use intel-vbtn or intel-hid, avoid these problems by only universally marking the EC GPE wakesource on non-AMD systems. Link: https://patchwork.kernel.org/project/linux-pm/cover/5997740.FPbUVk04hV@kreacher/#22825489 Link: https://gitlab.freedesktop.org/drm/amd/-/issues/1230 Link: https://gitlab.freedesktop.org/drm/amd/-/issues/1629 Signed-off-by: Mario Limonciello Acked-by: Alex Deucher Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 816bf2c34b7a97..1c507804fb1097 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -417,11 +417,15 @@ static int lps0_device_attach(struct acpi_device *adev, mem_sleep_current = PM_SUSPEND_TO_IDLE; /* - * 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. + * 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. */ - acpi_ec_mark_gpe_for_wake(); + if (!acpi_s2idle_vendor_amd()) + acpi_ec_mark_gpe_for_wake(); return 0; } From 8ffb49cb3284c989d6e342df42519d6fee5e1ece Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:17:57 +0530 Subject: [PATCH 09/18] platform/x86: amd-pmc: Fix command completion code The protocol to submit a job request to SMU is to wait for AMD_PMC_REGISTER_RESPONSE to return 1,meaning SMU is ready to take requests. PMC driver has to make sure that the response code is always AMD_PMC_RESULT_OK before making any command submissions. When we submit a message to SMU, we have to wait until it processes the request. Adding a read_poll_timeout() check as this was missing in the existing code. Also, add a mutex to protect amd_pmc_send_cmd() calls to SMU. Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") Signed-off-by: Shyam Sundar S K Acked-by: Raul E Rangel Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-2-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 38 ++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index b9da58ee9b1e3b..1b5f149932c1c3 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -68,6 +68,7 @@ struct amd_pmc_dev { u32 base_addr; u32 cpu_id; struct device *dev; + struct mutex lock; /* generic mutex lock */ #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ @@ -138,9 +139,10 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) u8 msg; u32 val; + mutex_lock(&dev->lock); /* Wait until we get a valid response */ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, - val, val > 0, PMC_MSG_DELAY_MIN_US, + val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { dev_err(dev->dev, "failed to talk to SMU\n"); @@ -156,7 +158,37 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) /* Write message ID to message ID register */ msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); - return 0; + /* Wait until we get a valid response */ + rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + val, val != 0, PMC_MSG_DELAY_MIN_US, + PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { + dev_err(dev->dev, "SMU response timed out\n"); + goto out_unlock; + } + + switch (val) { + case AMD_PMC_RESULT_OK: + break; + case AMD_PMC_RESULT_CMD_REJECT_BUSY: + dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val); + rc = -EBUSY; + goto out_unlock; + case AMD_PMC_RESULT_CMD_UNKNOWN: + dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val); + rc = -EINVAL; + goto out_unlock; + case AMD_PMC_RESULT_CMD_REJECT_PREREQ: + case AMD_PMC_RESULT_FAILED: + default: + dev_err(dev->dev, "SMU cmd failed. err: 0x%x\n", val); + rc = -EIO; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&dev->lock); + return rc; } static int __maybe_unused amd_pmc_suspend(struct device *dev) @@ -259,6 +291,7 @@ static int amd_pmc_probe(struct platform_device *pdev) amd_pmc_dump_registers(dev); + mutex_init(&dev->lock); platform_set_drvdata(pdev, dev); amd_pmc_dbgfs_register(dev); return 0; @@ -269,6 +302,7 @@ static int amd_pmc_remove(struct platform_device *pdev) struct amd_pmc_dev *dev = platform_get_drvdata(pdev); amd_pmc_dbgfs_unregister(dev); + mutex_destroy(&dev->lock); return 0; } From 7bd00d571a7d207e167a6a8464d4ed9b5b9e5730 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:17:58 +0530 Subject: [PATCH 10/18] platform/x86: amd-pmc: Fix SMU firmware reporting mechanism It was lately understood that the current mechanism available in the driver to get SMU firmware info works only on internal SMU builds and there is a separate way to get all the SMU logging counters (addressed in the next patch). Hence remove all the smu info shown via debugfs as it is no more useful. Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-3-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 1b5f149932c1c3..b1d6175a13b245 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -52,7 +52,6 @@ #define AMD_CPU_ID_PCO AMD_CPU_ID_RV #define AMD_CPU_ID_CZN AMD_CPU_ID_RN -#define AMD_SMU_FW_VERSION 0x0 #define PMC_MSG_DELAY_MIN_US 100 #define RESPONSE_REGISTER_LOOP_MAX 200 @@ -89,11 +88,6 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 #ifdef CONFIG_DEBUG_FS static int smu_fw_info_show(struct seq_file *s, void *unused) { - struct amd_pmc_dev *dev = s->private; - u32 value; - - value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION); - seq_printf(s, "SMU FW Info: %x\n", value); return 0; } DEFINE_SHOW_ATTRIBUTE(smu_fw_info); @@ -280,10 +274,6 @@ static int amd_pmc_probe(struct platform_device *pdev) pci_dev_put(rdev); base_addr = ((u64)base_addr_hi << 32 | base_addr_lo); - dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE); - if (!dev->smu_base) - return -ENOMEM; - dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET, AMD_PMC_MAPPING_SIZE); if (!dev->regbase) From d7a4be44db87566d994fe8cff90d21751e9fa63b Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:17:59 +0530 Subject: [PATCH 11/18] platform/x86: amd-pmc: call dump registers only once Currently amd_pmc_dump_registers() routine is being called at multiple places. The best to call it is after command submission to SMU. Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-4-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index b1d6175a13b245..e5107e3b191171 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -182,6 +182,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) out_unlock: mutex_unlock(&dev->lock); + amd_pmc_dump_registers(dev); return rc; } @@ -194,7 +195,6 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) if (rc) dev_err(pdev->dev, "suspend failed\n"); - amd_pmc_dump_registers(pdev); return 0; } @@ -207,7 +207,6 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) if (rc) dev_err(pdev->dev, "resume failed\n"); - amd_pmc_dump_registers(pdev); return 0; } @@ -279,8 +278,6 @@ static int amd_pmc_probe(struct platform_device *pdev) if (!dev->regbase) return -ENOMEM; - amd_pmc_dump_registers(dev); - mutex_init(&dev->lock); platform_set_drvdata(pdev, dev); amd_pmc_dbgfs_register(dev); From a0bd1d5bb702d78277e9c834b213449991998e49 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:18:00 +0530 Subject: [PATCH 12/18] platform/x86: amd-pmc: Add support for logging SMU metrics SMU provides a way to dump the s0ix debug statistics in the form of a metrics table via a of set special mailbox commands. Add support to the driver which can send these commands to SMU and expose the information received via debugfs. The information contains the s0ix entry/exit, active time of each IP block etc. As a side note, SMU subsystem logging is not supported on Picasso based SoC's. Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-5-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 147 +++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index e5107e3b191171..0ebb2732c46a6f 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -46,6 +46,14 @@ #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE #define AMD_PMC_RESULT_FAILED 0xFF +/* SMU Message Definations */ +#define SMU_MSG_GETSMUVERSION 0x02 +#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 +#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05 +#define SMU_MSG_LOG_START 0x06 +#define SMU_MSG_LOG_RESET 0x07 +#define SMU_MSG_LOG_DUMP_DATA 0x08 +#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09 /* List of supported CPU ids */ #define AMD_CPU_ID_RV 0x15D0 #define AMD_CPU_ID_RN 0x1630 @@ -55,17 +63,42 @@ #define PMC_MSG_DELAY_MIN_US 100 #define RESPONSE_REGISTER_LOOP_MAX 200 +#define SOC_SUBSYSTEM_IP_MAX 12 +#define DELAY_MIN_US 2000 +#define DELAY_MAX_US 3000 enum amd_pmc_def { MSG_TEST = 0x01, MSG_OS_HINT_PCO, MSG_OS_HINT_RN, }; +struct amd_pmc_bit_map { + const char *name; + u32 bit_mask; +}; + +static const struct amd_pmc_bit_map soc15_ip_blk[] = { + {"DISPLAY", BIT(0)}, + {"CPU", BIT(1)}, + {"GFX", BIT(2)}, + {"VDD", BIT(3)}, + {"ACP", BIT(4)}, + {"VCN", BIT(5)}, + {"ISP", BIT(6)}, + {"NBIO", BIT(7)}, + {"DF", BIT(8)}, + {"USB0", BIT(9)}, + {"USB1", BIT(10)}, + {"LAPIC", BIT(11)}, + {} +}; + struct amd_pmc_dev { void __iomem *regbase; - void __iomem *smu_base; + void __iomem *smu_virt_addr; u32 base_addr; u32 cpu_id; + u32 active_ips; struct device *dev; struct mutex lock; /* generic mutex lock */ #if IS_ENABLED(CONFIG_DEBUG_FS) @@ -74,6 +107,7 @@ struct amd_pmc_dev { }; static struct amd_pmc_dev pmc; +static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret); static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) { @@ -85,9 +119,49 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 iowrite32(val, dev->regbase + reg_offset); } +struct smu_metrics { + u32 table_version; + u32 hint_count; + u32 s0i3_cyclecount; + u32 timein_s0i2; + u64 timeentering_s0i3_lastcapture; + u64 timeentering_s0i3_totaltime; + u64 timeto_resume_to_os_lastcapture; + u64 timeto_resume_to_os_totaltime; + u64 timein_s0i3_lastcapture; + u64 timein_s0i3_totaltime; + u64 timein_swdrips_lastcapture; + u64 timein_swdrips_totaltime; + u64 timecondition_notmet_lastcapture[SOC_SUBSYSTEM_IP_MAX]; + u64 timecondition_notmet_totaltime[SOC_SUBSYSTEM_IP_MAX]; +} __packed; + #ifdef CONFIG_DEBUG_FS static int smu_fw_info_show(struct seq_file *s, void *unused) { + struct amd_pmc_dev *dev = s->private; + struct smu_metrics table; + int idx; + + if (dev->cpu_id == AMD_CPU_ID_PCO) + return -EINVAL; + + memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); + + seq_puts(s, "\n=== SMU Statistics ===\n"); + seq_printf(s, "Table Version: %d\n", table.table_version); + seq_printf(s, "Hint Count: %d\n", table.hint_count); + seq_printf(s, "S0i3 Cycle Count: %d\n", table.s0i3_cyclecount); + seq_printf(s, "Time (in us) to S0i3: %lld\n", table.timeentering_s0i3_lastcapture); + seq_printf(s, "Time (in us) in S0i3: %lld\n", table.timein_s0i3_lastcapture); + + seq_puts(s, "\n=== Active time (in us) ===\n"); + for (idx = 0 ; idx < SOC_SUBSYSTEM_IP_MAX ; idx++) { + if (soc15_ip_blk[idx].bit_mask & dev->active_ips) + seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name, + table.timecondition_notmet_lastcapture[idx]); + } + return 0; } DEFINE_SHOW_ATTRIBUTE(smu_fw_info); @@ -113,6 +187,32 @@ static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) } #endif /* CONFIG_DEBUG_FS */ +static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) +{ + u32 phys_addr_low, phys_addr_hi; + u64 smu_phys_addr; + + if (dev->cpu_id == AMD_CPU_ID_PCO) + return -EINVAL; + + /* Get Active devices list from SMU */ + amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, 1); + + /* Get dram address */ + amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, 1); + amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, 1); + smu_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); + + dev->smu_virt_addr = devm_ioremap(dev->dev, smu_phys_addr, sizeof(struct smu_metrics)); + if (!dev->smu_virt_addr) + return -ENOMEM; + + /* Start the logging */ + amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, 0); + + return 0; +} + static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) { u32 value; @@ -127,10 +227,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); } -static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) +static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret) { int rc; - u8 msg; u32 val; mutex_lock(&dev->lock); @@ -150,8 +249,8 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set); /* Write message ID to message ID register */ - msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); + /* Wait until we get a valid response */ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, val, val != 0, PMC_MSG_DELAY_MIN_US, @@ -163,6 +262,11 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) switch (val) { case AMD_PMC_RESULT_OK: + if (ret) { + /* PMFW may take longer time to return back the data */ + usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); + *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + } break; case AMD_PMC_RESULT_CMD_REJECT_BUSY: dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val); @@ -186,12 +290,29 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) return rc; } +static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) +{ + switch (dev->cpu_id) { + case AMD_CPU_ID_PCO: + return MSG_OS_HINT_PCO; + case AMD_CPU_ID_RN: + return MSG_OS_HINT_RN; + } + return -EINVAL; +} + static int __maybe_unused amd_pmc_suspend(struct device *dev) { struct amd_pmc_dev *pdev = dev_get_drvdata(dev); int rc; + u8 msg; - rc = amd_pmc_send_cmd(pdev, 1); + /* Reset and Start SMU logging - to monitor the s0i3 stats */ + amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_RESET, 0); + amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_START, 0); + + msg = amd_pmc_get_os_hint(pdev); + rc = amd_pmc_send_cmd(pdev, 1, NULL, msg, 0); if (rc) dev_err(pdev->dev, "suspend failed\n"); @@ -202,8 +323,13 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) { struct amd_pmc_dev *pdev = dev_get_drvdata(dev); int rc; + u8 msg; - rc = amd_pmc_send_cmd(pdev, 0); + /* Let SMU know that we are looking for stats */ + amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); + + msg = amd_pmc_get_os_hint(pdev); + rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); if (rc) dev_err(pdev->dev, "resume failed\n"); @@ -226,8 +352,7 @@ static int amd_pmc_probe(struct platform_device *pdev) { struct amd_pmc_dev *dev = &pmc; struct pci_dev *rdev; - u32 base_addr_lo; - u32 base_addr_hi; + u32 base_addr_lo, base_addr_hi; u64 base_addr; int err; u32 val; @@ -279,6 +404,12 @@ static int amd_pmc_probe(struct platform_device *pdev) return -ENOMEM; mutex_init(&dev->lock); + + /* Use SMU to get the s0i3 debug stats */ + err = amd_pmc_setup_smu_logging(dev); + if (err) + dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); + platform_set_drvdata(pdev, dev); amd_pmc_dbgfs_register(dev); return 0; From 114efc9b4cd2027f81e6783650196019c7ae3cf9 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:18:01 +0530 Subject: [PATCH 13/18] platform/x86: amd-pmc: Add support for logging s0ix counters Even the FCH SSC registers provides certain level of information about the s0ix entry and exit times which comes handy when the SMU fails to report the statistics via the mailbox communication. This information is captured via a new debugfs file "s0ix_stats". A non-zero entry in this counters would mean that the system entered the s0ix state. If s0ix entry time and exit time don't change during suspend to idle, the silicon has not entered the deepest state. Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-6-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 45 +++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 0ebb2732c46a6f..7f011c3f60f27b 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -46,6 +46,15 @@ #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE #define AMD_PMC_RESULT_FAILED 0xFF +/* FCH SSC Registers */ +#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30 +#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34 +#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38 +#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C +#define FCH_SSC_MAPPING_SIZE 0x800 +#define FCH_BASE_PHY_ADDR_LOW 0xFED81100 +#define FCH_BASE_PHY_ADDR_HIGH 0x00000000 + /* SMU Message Definations */ #define SMU_MSG_GETSMUVERSION 0x02 #define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 @@ -96,6 +105,7 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; + void __iomem *fch_virt_addr; u32 base_addr; u32 cpu_id; u32 active_ips; @@ -166,6 +176,29 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(smu_fw_info); +static int s0ix_stats_show(struct seq_file *s, void *unused) +{ + struct amd_pmc_dev *dev = s->private; + u64 entry_time, exit_time, residency; + + entry_time = ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_H_OFFSET); + entry_time = entry_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_L_OFFSET); + + exit_time = ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_H_OFFSET); + exit_time = exit_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_L_OFFSET); + + /* It's in 48MHz. We need to convert it */ + residency = (exit_time - entry_time) / 48; + + seq_puts(s, "=== S0ix statistics ===\n"); + seq_printf(s, "S0ix Entry Time: %lld\n", entry_time); + seq_printf(s, "S0ix Exit Time: %lld\n", exit_time); + seq_printf(s, "Residency Time: %lld\n", residency); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(s0ix_stats); + static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) { debugfs_remove_recursive(dev->dbgfs_dir); @@ -176,6 +209,8 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL); debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev, &smu_fw_info_fops); + debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev, + &s0ix_stats_fops); } #else static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) @@ -353,7 +388,7 @@ static int amd_pmc_probe(struct platform_device *pdev) struct amd_pmc_dev *dev = &pmc; struct pci_dev *rdev; u32 base_addr_lo, base_addr_hi; - u64 base_addr; + u64 base_addr, fch_phys_addr; int err; u32 val; @@ -405,6 +440,14 @@ static int amd_pmc_probe(struct platform_device *pdev) mutex_init(&dev->lock); + /* Use FCH registers to get the S0ix stats */ + base_addr_lo = FCH_BASE_PHY_ADDR_LOW; + base_addr_hi = FCH_BASE_PHY_ADDR_HIGH; + fch_phys_addr = ((u64)base_addr_hi << 32 | base_addr_lo); + dev->fch_virt_addr = devm_ioremap(dev->dev, fch_phys_addr, FCH_SSC_MAPPING_SIZE); + if (!dev->fch_virt_addr) + return -ENOMEM; + /* Use SMU to get the s0i3 debug stats */ err = amd_pmc_setup_smu_logging(dev); if (err) From 707a769530ecb14c08201e2f1cd9f20d65cbb460 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:18:02 +0530 Subject: [PATCH 14/18] platform/x86: amd-pmc: Add support for ACPI ID AMDI0006 Some newer BIOSes have added another ACPI ID for the uPEP device. SMU statistics behave identically on this device. Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-7-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 7f011c3f60f27b..c5054fa2aed98a 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -469,6 +469,7 @@ static int amd_pmc_remove(struct platform_device *pdev) static const struct acpi_device_id amd_pmc_acpi_ids[] = { {"AMDI0005", 0}, + {"AMDI0006", 0}, {"AMD0004", 0}, { } }; From f85c4733b3556d22406630ac88e31c9f083aa935 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 29 Jun 2021 14:18:03 +0530 Subject: [PATCH 15/18] platform/x86: amd-pmc: Add new acpi id for future PMC controllers The upcoming PMC controller would have a newer acpi id, add that to the supported acpid device list. Signed-off-by: Shyam Sundar S K Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210629084803.248498-8-Shyam-sundar.S-k@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index c5054fa2aed98a..d2f9a62e116607 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -68,6 +68,7 @@ #define AMD_CPU_ID_RN 0x1630 #define AMD_CPU_ID_PCO AMD_CPU_ID_RV #define AMD_CPU_ID_CZN AMD_CPU_ID_RN +#define AMD_CPU_ID_YC 0x14B5 #define PMC_MSG_DELAY_MIN_US 100 #define RESPONSE_REGISTER_LOOP_MAX 200 @@ -331,6 +332,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) case AMD_CPU_ID_PCO: return MSG_OS_HINT_PCO; case AMD_CPU_ID_RN: + case AMD_CPU_ID_YC: return MSG_OS_HINT_RN; } return -EINVAL; @@ -376,6 +378,7 @@ static const struct dev_pm_ops amd_pmc_pm_ops = { }; static const struct pci_device_id pmc_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, @@ -470,6 +473,7 @@ static int amd_pmc_remove(struct platform_device *pdev) static const struct acpi_device_id amd_pmc_acpi_ids[] = { {"AMDI0005", 0}, {"AMDI0006", 0}, + {"AMDI0007", 0}, {"AMD0004", 0}, { } }; From 04c243b4f772224c45da77a1fbe8a4020f79cac1 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 7 Jul 2021 09:16:47 -0500 Subject: [PATCH 16/18] platform/x86: amd-pmc: Use return code on suspend Right now the driver will still return success even if the OS_HINT command failed to send to the SMU. In the rare event of a failure, the suspend should really be aborted here so that relevant logs can may be captured. Signed-off-by: Mario Limonciello Acked-by: Shyam Sundar S K Link: https://lore.kernel.org/r/20210707141647.8871-1-mario.limonciello@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index d2f9a62e116607..680f94c7e075b8 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -353,7 +353,7 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) if (rc) dev_err(pdev->dev, "suspend failed\n"); - return 0; + return rc; } static int __maybe_unused amd_pmc_resume(struct device *dev) From eecbf13952d4df2f494776aabd2fc04a628976ed Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 11 Aug 2021 14:56:54 -0600 Subject: [PATCH 17/18] ACPI: PM: s2idle: Invert Microsoft UUID entry and exit It was reported by a user with a Dell m15 R5 (5800H) that the keyboard backlight was turning on when entering suspend and turning off when exiting (the opposite of how it should be). The user bisected it back to commit 5dbf50997578 ("ACPI: PM: s2idle: Add support for new Microsoft UUID"). Previous to that commit the LEDs didn't turn off at all. Confirming in the spec, these were reversed when introduced. Fix them to match the spec. BugLink: https://gitlab.freedesktop.org/drm/amd/-/issues/1230#note_1021836 Fixes: 5dbf50997578 ("ACPI: PM: s2idle: Add support for new Microsoft UUID") Signed-off-by: Mario Limonciello --- drivers/acpi/x86/s2idle.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index 1c507804fb1097..b16c245bbe5b88 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -446,7 +446,7 @@ int acpi_s2idle_prepare_late(void) 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, + 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); @@ -473,7 +473,7 @@ void acpi_s2idle_restore_early(void) 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, + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); From 5b5c939ae43ce64cb298eb30eb94c7591e7eaf04 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 1 Sep 2021 09:21:11 -0500 Subject: [PATCH 18/18] ACPI: PM: s2idle: Run both AMD and Microsoft methods if both are supported It was reported that on "HP ENVY x360" that power LED does not come back, certain keys like brightness controls do not work, and the fan never spins up, even under load on 5.14 final. In analysis of the SSDT it's clear that the Microsoft UUID doesn't provide functional support, but rather the AMD UUID should be supporting this system. Because this is a gap in the expected logic, we checked back with internal team. The conclusion was that on Windows AMD uPEP *does* run even when Microsoft UUID present, but most OEM systems have adopted value of "0x3" for supported functions and hence nothing runs. Henceforth add support for running both Microsoft and AMD methods. This approach will also allow the same logic on Intel systems if desired at a future time as well by pulling the evaluation of `lps0_dsm_func_mask_microsoft` out of the `if` block for `acpi_s2idle_vendor_amd`. Link: https://gitlab.freedesktop.org/drm/amd/uploads/9fbcd7ec3a385cc6949c9bacf45dc41b/acpi-f.20.bin BugLink: https://gitlab.freedesktop.org/drm/amd/-/issues/1691 Reported-by: Maxwell Beck Signed-off-by: Mario Limonciello [ rjw: Edits of the new comments ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 67 +++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index b16c245bbe5b88..a72e166f35ce80 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -443,25 +443,30 @@ int acpi_s2idle_prepare_late(void) 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_ENTRY, - lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + + /* LPS0 entry */ + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_ENTRY_AMD : + ACPI_LPS0_ENTRY, + lps0_dsm_func_mask, lps0_dsm_guid); + if (lps0_dsm_func_mask_microsoft > 0) { 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); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY, - lps0_dsm_func_mask, lps0_dsm_guid); + /* modern standby entry */ + acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); } - return 0; } @@ -470,24 +475,30 @@ void acpi_s2idle_restore_early(void) if (!lps0_device_handle || sleep_no_lps0) return; - if (lps0_dsm_func_mask_microsoft > 0) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT, - lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + /* Modern standby exit */ + if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - 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 { + + /* 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, lps0_dsm_guid); + 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, lps0_dsm_guid); - } + lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + 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 = {