1
0

Merge branches 'acpi-property', 'acpi-resource', 'acpi-pm' and 'acpi-tables'

Merge updates of the ACPI device properties management code, ACPI
resources management code, ACPI power management, and ACPI data tables
parsing code for 6.18-rc1:

 - Fix ACPI buffer properties extraction for data-only subnodes
   represented as _DSD-equivalent packages (Rafael Wysocki)

 - Fix handling of ACPI data-only subnodes represented as _DSD-equivalent
   packages in the case when they are embedded in larger _DSD-equivalent
   packages and clean up acpi_nondev_subnode_extract() (Rafael Wysocki)

 - Skip ACPI IRQ override on ASUS Vivobook Pro N6506CU (Sam van Kampen)

 - Add power resource init function and use it for introducing an HP
   EliteBook 855 G7 WWAN modem power resource quirk (Maciej Szmigiero)

 - Add support for DBG2 RISC-V SBI port subtype and Precise Baud Rate
   field to the ACPI SPCR table parser (Chen Pei)

* acpi-property:
  ACPI: property: Adjust failure handling in acpi_nondev_subnode_extract()
  ACPI: property: Do not pass NULL handles to acpi_attach_data()
  ACPI: property: Add code comments explaining what is going on
  ACPI: property: Disregard references in data-only subnode lists
  ACPI: property: Fix buffer properties extraction for subnodes

* acpi-resource:
  ACPI: resource: Skip IRQ override on ASUS Vivobook Pro N6506CU

* acpi-pm:
  ACPI: PM: Add HP EliteBook 855 G7 WWAN modem power resource quirk
  ACPI: PM: Add power resource init function

* acpi-tables:
  ACPI: SPCR: Support Precise Baud Rate field
  ACPI: SPCR: Add support for DBG2 RISC-V SBI port subtype
This commit is contained in:
Rafael J. Wysocki
2025-09-29 15:14:29 +02:00
6 changed files with 206 additions and 62 deletions

View File

@@ -140,6 +140,7 @@ int __acpi_device_uevent_modalias(const struct acpi_device *adev,
/* --------------------------------------------------------------------------
Power Resource
-------------------------------------------------------------------------- */
void acpi_power_resources_init(void);
void acpi_power_resources_list_free(struct list_head *list);
int acpi_extract_power_resources(union acpi_object *package, unsigned int start,
struct list_head *list);

View File

@@ -23,6 +23,7 @@
#define pr_fmt(fmt) "ACPI: PM: " fmt
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -63,6 +64,9 @@ struct acpi_power_resource_entry {
struct acpi_power_resource *resource;
};
static bool hp_eb_gp12pxp_quirk;
static bool unused_power_resources_quirk;
static LIST_HEAD(acpi_power_resource_list);
static DEFINE_MUTEX(power_resource_list_lock);
@@ -992,6 +996,38 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle)
}
#ifdef CONFIG_ACPI_SLEEP
static bool resource_is_gp12pxp(acpi_handle handle)
{
const char *path;
bool ret;
path = acpi_handle_path(handle);
ret = path && strcmp(path, "\\_SB_.PCI0.GP12.PXP_") == 0;
kfree(path);
return ret;
}
static void acpi_resume_on_eb_gp12pxp(struct acpi_power_resource *resource)
{
acpi_handle_notice(resource->device.handle,
"HP EB quirk - turning OFF then ON\n");
__acpi_power_off(resource);
__acpi_power_on(resource);
/*
* Use the same delay as DSDT uses in modem _RST method.
*
* Otherwise we get "Unable to change power state from unknown to D0,
* device inaccessible" error for the modem PCI device after thaw.
*
* This power resource is normally being enabled only during thaw (once)
* so this wait is not a performance issue.
*/
msleep(200);
}
void acpi_resume_power_resources(void)
{
struct acpi_power_resource *resource;
@@ -1013,8 +1049,14 @@ void acpi_resume_power_resources(void)
if (state == ACPI_POWER_RESOURCE_STATE_OFF
&& resource->ref_count) {
acpi_handle_debug(resource->device.handle, "Turning ON\n");
__acpi_power_on(resource);
if (hp_eb_gp12pxp_quirk &&
resource_is_gp12pxp(resource->device.handle)) {
acpi_resume_on_eb_gp12pxp(resource);
} else {
acpi_handle_debug(resource->device.handle,
"Turning ON\n");
__acpi_power_on(resource);
}
}
mutex_unlock(&resource->resource_lock);
@@ -1024,6 +1066,41 @@ void acpi_resume_power_resources(void)
}
#endif
static const struct dmi_system_id dmi_hp_elitebook_gp12pxp_quirk[] = {
/*
* This laptop (and possibly similar models too) has power resource called
* "GP12.PXP_" for its WWAN modem.
*
* For this power resource to turn ON power for the modem it needs certain
* internal flag called "ONEN" to be set.
* This flag only gets set from this power resource "_OFF" method, while the
* actual modem power gets turned off during suspend by "GP12.PTS" method
* called from the global "_PTS" (Prepare To Sleep) method.
* On the other hand, this power resource "_OFF" method implementation just
* sets the aforementioned flag without actually doing anything else (it
* doesn't contain any code to actually turn off power).
*
* The above means that when upon hibernation finish we try to set this
* power resource back ON since its "_STA" method returns 0 (while the resource
* is still considered in use) its "_ON" method won't do anything since
* that "ONEN" flag is not set.
* Overall, this means the modem is dead until laptop is rebooted since its
* power has been cut by "_PTS" and its PCI configuration was lost and not able
* to be restored.
*
* The easiest way to workaround the issue is to call this power resource
* "_OFF" method before calling the "_ON" method to make sure the "ONEN"
* flag gets properly set.
*/
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook 855 G7 Notebook PC"),
},
},
{}
};
static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = {
{
/*
@@ -1046,7 +1123,7 @@ void acpi_turn_off_unused_power_resources(void)
{
struct acpi_power_resource *resource;
if (dmi_check_system(dmi_leave_unused_power_resources_on))
if (unused_power_resources_quirk)
return;
mutex_lock(&power_resource_list_lock);
@@ -1065,3 +1142,10 @@ void acpi_turn_off_unused_power_resources(void)
mutex_unlock(&power_resource_list_lock);
}
void __init acpi_power_resources_init(void)
{
hp_eb_gp12pxp_quirk = dmi_check_system(dmi_hp_elitebook_gp12pxp_quirk);
unused_power_resources_quirk =
dmi_check_system(dmi_leave_unused_power_resources_on);
}

View File

@@ -83,6 +83,7 @@ static bool acpi_nondev_subnode_extract(union acpi_object *desc,
struct fwnode_handle *parent)
{
struct acpi_data_node *dn;
acpi_handle scope = NULL;
bool result;
if (acpi_graph_ignore_port(handle))
@@ -98,48 +99,70 @@ static bool acpi_nondev_subnode_extract(union acpi_object *desc,
INIT_LIST_HEAD(&dn->data.properties);
INIT_LIST_HEAD(&dn->data.subnodes);
result = acpi_extract_properties(handle, desc, &dn->data);
/*
* The scope for the completion of relative pathname segments and
* subnode object lookup is the one of the namespace node (device)
* containing the object that has returned the package. That is, it's
* the scope of that object's parent device.
*/
if (handle)
acpi_get_parent(handle, &scope);
if (handle) {
acpi_handle scope;
acpi_status status;
/*
* The scope for the subnode object lookup is the one of the
* namespace node (device) containing the object that has
* returned the package. That is, it's the scope of that
* object's parent.
*/
status = acpi_get_parent(handle, &scope);
if (ACPI_SUCCESS(status)
&& acpi_enumerate_nondev_subnodes(scope, desc, &dn->data,
&dn->fwnode))
result = true;
} else if (acpi_enumerate_nondev_subnodes(NULL, desc, &dn->data,
&dn->fwnode)) {
/*
* Extract properties from the _DSD-equivalent package pointed to by
* desc and use scope (if not NULL) for the completion of relative
* pathname segments.
*
* The extracted properties will be held in the new data node dn.
*/
result = acpi_extract_properties(scope, desc, &dn->data);
/*
* Look for subnodes in the _DSD-equivalent package pointed to by desc
* and create child nodes of dn if there are any.
*/
if (acpi_enumerate_nondev_subnodes(scope, desc, &dn->data, &dn->fwnode))
result = true;
if (!result) {
kfree(dn);
acpi_handle_debug(handle, "Invalid properties/subnodes data, skipping\n");
return false;
}
if (result) {
dn->handle = handle;
dn->data.pointer = desc;
list_add_tail(&dn->sibling, list);
return true;
}
/*
* This will be NULL if the desc package is embedded in an outer
* _DSD-equivalent package and its scope cannot be determined.
*/
dn->handle = handle;
dn->data.pointer = desc;
list_add_tail(&dn->sibling, list);
kfree(dn);
acpi_handle_debug(handle, "Invalid properties/subnodes data, skipping\n");
return false;
return true;
}
static bool acpi_nondev_subnode_data_ok(acpi_handle handle,
const union acpi_object *link,
struct list_head *list,
struct fwnode_handle *parent)
static bool acpi_nondev_subnode_ok(acpi_handle scope,
const union acpi_object *link,
struct list_head *list,
struct fwnode_handle *parent)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
acpi_handle handle;
acpi_status status;
/*
* If the scope is unknown, the _DSD-equivalent package being parsed
* was embedded in an outer _DSD-equivalent package as a result of
* direct evaluation of an object pointed to by a reference. In that
* case, using a pathname as the target object pointer is invalid.
*/
if (!scope)
return false;
status = acpi_get_handle(scope, link->package.elements[1].string.pointer,
&handle);
if (ACPI_FAILURE(status))
return false;
status = acpi_evaluate_object_typed(handle, NULL, NULL, &buf,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status))
@@ -153,25 +176,6 @@ static bool acpi_nondev_subnode_data_ok(acpi_handle handle,
return false;
}
static bool acpi_nondev_subnode_ok(acpi_handle scope,
const union acpi_object *link,
struct list_head *list,
struct fwnode_handle *parent)
{
acpi_handle handle;
acpi_status status;
if (!scope)
return false;
status = acpi_get_handle(scope, link->package.elements[1].string.pointer,
&handle);
if (ACPI_FAILURE(status))
return false;
return acpi_nondev_subnode_data_ok(handle, link, list, parent);
}
static bool acpi_add_nondev_subnodes(acpi_handle scope,
union acpi_object *links,
struct list_head *list,
@@ -180,9 +184,12 @@ static bool acpi_add_nondev_subnodes(acpi_handle scope,
bool ret = false;
int i;
/*
* Every element in the links package is expected to represent a link
* to a non-device node in a tree containing device-specific data.
*/
for (i = 0; i < links->package.count; i++) {
union acpi_object *link, *desc;
acpi_handle handle;
bool result;
link = &links->package.elements[i];
@@ -190,26 +197,53 @@ static bool acpi_add_nondev_subnodes(acpi_handle scope,
if (link->package.count != 2)
continue;
/* The first one must be a string. */
/* The first one (the key) must be a string. */
if (link->package.elements[0].type != ACPI_TYPE_STRING)
continue;
/* The second one may be a string, a reference or a package. */
/* The second one (the target) may be a string or a package. */
switch (link->package.elements[1].type) {
case ACPI_TYPE_STRING:
/*
* The string is expected to be a full pathname or a
* pathname segment relative to the given scope. That
* pathname is expected to point to an object returning
* a package that contains _DSD-equivalent information.
*/
result = acpi_nondev_subnode_ok(scope, link, list,
parent);
break;
case ACPI_TYPE_LOCAL_REFERENCE:
handle = link->package.elements[1].reference.handle;
result = acpi_nondev_subnode_data_ok(handle, link, list,
parent);
break;
case ACPI_TYPE_PACKAGE:
/*
* This happens when a reference is used in AML to
* point to the target. Since the target is expected
* to be a named object, a reference to it will cause it
* to be avaluated in place and its return package will
* be embedded in the links package at the location of
* the reference.
*
* The target package is expected to contain _DSD-
* equivalent information, but the scope in which it
* is located in the original AML is unknown. Thus
* it cannot contain pathname segments represented as
* strings because there is no way to build full
* pathnames out of them.
*/
acpi_handle_debug(scope, "subnode %s: Unknown scope\n",
link->package.elements[0].string.pointer);
desc = &link->package.elements[1];
result = acpi_nondev_subnode_extract(desc, NULL, link,
list, parent);
break;
case ACPI_TYPE_LOCAL_REFERENCE:
/*
* It is not expected to see any local references in
* the links package because referencing a named object
* should cause it to be evaluated in place.
*/
acpi_handle_info(scope, "subnode %s: Unexpected reference\n",
link->package.elements[0].string.pointer);
fallthrough;
default:
result = false;
break;
@@ -369,6 +403,9 @@ static void acpi_untie_nondev_subnodes(struct acpi_device_data *data)
struct acpi_data_node *dn;
list_for_each_entry(dn, &data->subnodes, sibling) {
if (!dn->handle)
continue;
acpi_detach_data(dn->handle, acpi_nondev_subnode_tag);
acpi_untie_nondev_subnodes(&dn->data);
@@ -383,6 +420,9 @@ static bool acpi_tie_nondev_subnodes(struct acpi_device_data *data)
acpi_status status;
bool ret;
if (!dn->handle)
continue;
status = acpi_attach_data(dn->handle, acpi_nondev_subnode_tag, dn);
if (ACPI_FAILURE(status) && status != AE_ALREADY_EXISTS) {
acpi_handle_err(dn->handle, "Can't tag data node\n");

View File

@@ -510,6 +510,13 @@ static const struct dmi_system_id irq1_level_low_skip_override[] = {
DMI_MATCH(DMI_BOARD_NAME, "N6506M"),
},
},
{
/* Asus Vivobook Pro N6506CU* */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_BOARD_NAME, "N6506CU"),
},
},
{
/* LG Electronics 17U70P */
.matches = {

View File

@@ -2704,6 +2704,7 @@ void __init acpi_scan_init(void)
acpi_memory_hotplug_init();
acpi_watchdog_init();
acpi_pnp_init();
acpi_power_resources_init();
acpi_int340x_thermal_init();
acpi_init_lpit();

View File

@@ -141,12 +141,23 @@ int __init acpi_parse_spcr(bool enable_earlycon, bool enable_console)
case ACPI_DBG2_16550_NVIDIA:
uart = "uart";
break;
case ACPI_DBG2_RISCV_SBI_CON:
uart = "sbi";
break;
default:
err = -ENOENT;
goto done;
}
switch (table->baud_rate) {
/*
* SPCR 1.09 defines Precise Baud Rate Filed contains a specific
* non-zero baud rate which overrides the value of the Configured
* Baud Rate field. If this field is zero or not present, Configured
* Baud Rate is used.
*/
if (table->precise_baudrate)
baud_rate = table->precise_baudrate;
else switch (table->baud_rate) {
case 0:
/*
* SPCR 1.04 defines 0 as a preconfigured state of UART.