Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions drivers/acpi/device_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1339,4 +1339,36 @@ 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 (force_storage_d3())
return true;

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 */
9 changes: 9 additions & 0 deletions drivers/acpi/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------------------------------------------------------- */
Expand Down
176 changes: 122 additions & 54 deletions drivers/acpi/x86/s2idle.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@ 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
#define ACPI_LPS0_SCREEN_OFF 3
#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"
Expand All @@ -49,7 +54,10 @@ 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;

static guid_t lps0_dsm_guid_microsoft;
static int lps0_dsm_func_mask_microsoft;

/* Device constraint entry structure */
struct lpi_device_info {
Expand All @@ -70,15 +78,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;
Expand All @@ -96,15 +96,15 @@ 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)
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];

Expand Down Expand Up @@ -317,14 +317,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",
Expand All @@ -336,40 +337,70 @@ 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;

if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
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);
/* 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 {
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);
lps0_dsm_func_mask_microsoft = -EINVAL;
}

/* 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 && lps0_dsm_func_mask_microsoft < 0)
return 0; //function evaluation failed

lps0_device_handle = adev->handle;

Expand All @@ -386,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;
}
Expand All @@ -408,14 +443,30 @@ int acpi_s2idle_prepare_late(void)
if (pm_debug_messages_on)
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);
} else {
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF);
acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY);
/* 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);

/* 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);
/* modern standby entry */
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
}

return 0;
}

Expand All @@ -424,13 +475,30 @@ void acpi_s2idle_restore_early(void)
if (!lps0_device_handle || sleep_no_lps0)
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);
} else {
acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT);
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON);
}
/* 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);

/* 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);

/* 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);
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 = {
Expand Down
25 changes: 25 additions & 0 deletions drivers/acpi/x86/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
28 changes: 1 addition & 27 deletions drivers/nvme/host/pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Loading