diff options
Diffstat (limited to 'drivers/acpi')
28 files changed, 1077 insertions, 297 deletions
diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 33418dd6768a..6d870d97ada6 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -90,19 +90,18 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) args[0].buffer.pointer = (u8 *)rt; args[0].buffer.length = sizeof(*rt); - pm_runtime_get_sync(dev); + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; status = acpi_evaluate_integer(handle, "_SRT", &arg_list, &retval); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status) || retval) return -EIO; return 0; } -static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +static int acpi_tad_evaluate_grt(struct device *dev, struct acpi_tad_rt *rt) { acpi_handle handle = ACPI_HANDLE(dev); struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER }; @@ -111,12 +110,7 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) acpi_status status; int ret = -EIO; - pm_runtime_get_sync(dev); - status = acpi_evaluate_object(handle, "_GRT", NULL, &output); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status)) goto out_free; @@ -139,6 +133,21 @@ out_free: return ret; } +static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +{ + int ret; + + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + ret = acpi_tad_evaluate_grt(dev, rt); + if (ret) + return ret; + + return 0; +} + static char *acpi_tad_rt_next_field(char *s, int *val) { char *p; @@ -266,12 +275,11 @@ static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, args[0].integer.value = timer_id; args[1].integer.value = value; - pm_runtime_get_sync(dev); + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status) || retval) return -EIO; @@ -314,12 +322,11 @@ static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, args[0].integer.value = timer_id; - pm_runtime_get_sync(dev); + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status)) return -EIO; @@ -370,12 +377,11 @@ static int acpi_tad_clear_status(struct device *dev, u32 timer_id) args[0].integer.value = timer_id; - pm_runtime_get_sync(dev); + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; status = acpi_evaluate_integer(handle, "_CWS", &arg_list, &retval); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status) || retval) return -EIO; @@ -411,12 +417,11 @@ static ssize_t acpi_tad_status_read(struct device *dev, char *buf, u32 timer_id) args[0].integer.value = timer_id; - pm_runtime_get_sync(dev); + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; status = acpi_evaluate_integer(handle, "_GWS", &arg_list, &retval); - - pm_runtime_put_sync(dev); - if (ACPI_FAILURE(status)) return -EIO; @@ -563,8 +568,6 @@ static void acpi_tad_remove(struct platform_device *pdev) device_init_wakeup(dev, false); - pm_runtime_get_sync(dev); - if (dd->capabilities & ACPI_TAD_RT) sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); @@ -573,14 +576,16 @@ static void acpi_tad_remove(struct platform_device *pdev) sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); - if (dd->capabilities & ACPI_TAD_DC_WAKE) { - acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); + scoped_guard(pm_runtime_noresume, dev) { + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); + } } - pm_runtime_put_sync(dev); + pm_runtime_suspend(dev); pm_runtime_disable(dev); acpi_remove_cmos_rtc_space_handler(handle); } diff --git a/drivers/acpi/acpica/nswalk.c b/drivers/acpi/acpica/nswalk.c index a2ac06a26e92..5670ff5a43cd 100644 --- a/drivers/acpi/acpica/nswalk.c +++ b/drivers/acpi/acpica/nswalk.c @@ -169,9 +169,12 @@ acpi_ns_walk_namespace(acpi_object_type type, if (start_node == ACPI_ROOT_OBJECT) { start_node = acpi_gbl_root_node; - if (!start_node) { - return_ACPI_STATUS(AE_NO_NAMESPACE); - } + } + + /* Avoid walking the namespace if the StartNode is NULL */ + + if (!start_node) { + return_ACPI_STATUS(AE_NO_NAMESPACE); } /* Null child means "get first node" */ diff --git a/drivers/acpi/arm64/Kconfig b/drivers/acpi/arm64/Kconfig index b3ed6212244c..f2fd79f22e7d 100644 --- a/drivers/acpi/arm64/Kconfig +++ b/drivers/acpi/arm64/Kconfig @@ -21,3 +21,6 @@ config ACPI_AGDI config ACPI_APMT bool + +config ACPI_MPAM + bool diff --git a/drivers/acpi/arm64/Makefile b/drivers/acpi/arm64/Makefile index 05ecde9eaabe..9390b57cb564 100644 --- a/drivers/acpi/arm64/Makefile +++ b/drivers/acpi/arm64/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_ACPI_APMT) += apmt.o obj-$(CONFIG_ACPI_FFH) += ffh.o obj-$(CONFIG_ACPI_GTDT) += gtdt.o obj-$(CONFIG_ACPI_IORT) += iort.o +obj-$(CONFIG_ACPI_MPAM) += mpam.o obj-$(CONFIG_ACPI_PROCESSOR_IDLE) += cpuidle.o obj-$(CONFIG_ARM_AMBA) += amba.o obj-y += dma.o init.o diff --git a/drivers/acpi/arm64/gtdt.c b/drivers/acpi/arm64/gtdt.c index 8cc8af8fd408..ffc867bac2d6 100644 --- a/drivers/acpi/arm64/gtdt.c +++ b/drivers/acpi/arm64/gtdt.c @@ -303,40 +303,6 @@ error: return -EINVAL; } -/** - * acpi_arch_timer_mem_init() - Get the info of all GT blocks in GTDT table. - * @timer_mem: The pointer to the array of struct arch_timer_mem for returning - * the result of parsing. The element number of this array should - * be platform_timer_count(the total number of platform timers). - * @timer_count: It points to a integer variable which is used for storing the - * number of GT blocks we have parsed. - * - * Return: 0 if success, -EINVAL/-ENODEV if error. - */ -int __init acpi_arch_timer_mem_init(struct arch_timer_mem *timer_mem, - int *timer_count) -{ - int ret; - void *platform_timer; - - *timer_count = 0; - for_each_platform_timer(platform_timer) { - if (is_timer_block(platform_timer)) { - ret = gtdt_parse_timer_block(platform_timer, timer_mem); - if (ret) - return ret; - timer_mem++; - (*timer_count)++; - } - } - - if (*timer_count) - pr_info("found %d memory-mapped timer block(s).\n", - *timer_count); - - return 0; -} - /* * Initialize a SBSA generic Watchdog platform device info from GTDT */ diff --git a/drivers/acpi/arm64/mpam.c b/drivers/acpi/arm64/mpam.c new file mode 100644 index 000000000000..84963a20c3e7 --- /dev/null +++ b/drivers/acpi/arm64/mpam.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Arm Ltd. + +/* Parse the MPAM ACPI table feeding the discovered nodes into the driver */ + +#define pr_fmt(fmt) "ACPI MPAM: " fmt + +#include <linux/acpi.h> +#include <linux/arm_mpam.h> +#include <linux/bits.h> +#include <linux/cpu.h> +#include <linux/cpumask.h> +#include <linux/platform_device.h> + +#include <acpi/processor.h> + +/* + * Flags for acpi_table_mpam_msc.*_interrupt_flags. + * See 2.1.1 Interrupt Flags, Table 5, of DEN0065B_MPAM_ACPI_3.0-bet. + */ +#define ACPI_MPAM_MSC_IRQ_MODE BIT(0) +#define ACPI_MPAM_MSC_IRQ_TYPE_MASK GENMASK(2, 1) +#define ACPI_MPAM_MSC_IRQ_TYPE_WIRED 0 +#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_MASK BIT(3) +#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR 0 +#define ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR_CONTAINER 1 +#define ACPI_MPAM_MSC_IRQ_AFFINITY_VALID BIT(4) + +/* + * Encodings for the MSC node body interface type field. + * See 2.1 MPAM MSC node, Table 4 of DEN0065B_MPAM_ACPI_3.0-bet. + */ +#define ACPI_MPAM_MSC_IFACE_MMIO 0x00 +#define ACPI_MPAM_MSC_IFACE_PCC 0x0a + +static bool _is_ppi_partition(u32 flags) +{ + u32 aff_type, is_ppi; + bool ret; + + is_ppi = FIELD_GET(ACPI_MPAM_MSC_IRQ_AFFINITY_VALID, flags); + if (!is_ppi) + return false; + + aff_type = FIELD_GET(ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_MASK, flags); + ret = (aff_type == ACPI_MPAM_MSC_IRQ_AFFINITY_TYPE_PROCESSOR_CONTAINER); + if (ret) + pr_err_once("Partitioned interrupts not supported\n"); + + return ret; +} + +static int acpi_mpam_register_irq(struct platform_device *pdev, + u32 intid, u32 flags) +{ + int irq; + u32 int_type; + int trigger; + + if (!intid) + return -EINVAL; + + if (_is_ppi_partition(flags)) + return -EINVAL; + + trigger = FIELD_GET(ACPI_MPAM_MSC_IRQ_MODE, flags); + int_type = FIELD_GET(ACPI_MPAM_MSC_IRQ_TYPE_MASK, flags); + if (int_type != ACPI_MPAM_MSC_IRQ_TYPE_WIRED) + return -EINVAL; + + irq = acpi_register_gsi(&pdev->dev, intid, trigger, ACPI_ACTIVE_HIGH); + if (irq < 0) + pr_err_once("Failed to register interrupt 0x%x with ACPI\n", intid); + + return irq; +} + +static void acpi_mpam_parse_irqs(struct platform_device *pdev, + struct acpi_mpam_msc_node *tbl_msc, + struct resource *res, int *res_idx) +{ + u32 flags, intid; + int irq; + + intid = tbl_msc->overflow_interrupt; + flags = tbl_msc->overflow_interrupt_flags; + irq = acpi_mpam_register_irq(pdev, intid, flags); + if (irq > 0) + res[(*res_idx)++] = DEFINE_RES_IRQ_NAMED(irq, "overflow"); + + intid = tbl_msc->error_interrupt; + flags = tbl_msc->error_interrupt_flags; + irq = acpi_mpam_register_irq(pdev, intid, flags); + if (irq > 0) + res[(*res_idx)++] = DEFINE_RES_IRQ_NAMED(irq, "error"); +} + +static int acpi_mpam_parse_resource(struct mpam_msc *msc, + struct acpi_mpam_resource_node *res) +{ + int level, nid; + u32 cache_id; + + switch (res->locator_type) { + case ACPI_MPAM_LOCATION_TYPE_PROCESSOR_CACHE: + cache_id = res->locator.cache_locator.cache_reference; + level = find_acpi_cache_level_from_id(cache_id); + if (level <= 0) { + pr_err_once("Bad level (%d) for cache with id %u\n", level, cache_id); + return -EINVAL; + } + return mpam_ris_create(msc, res->ris_index, MPAM_CLASS_CACHE, + level, cache_id); + case ACPI_MPAM_LOCATION_TYPE_MEMORY: + nid = pxm_to_node(res->locator.memory_locator.proximity_domain); + if (nid == NUMA_NO_NODE) { + pr_debug("Bad proximity domain %lld, using node 0 instead\n", + res->locator.memory_locator.proximity_domain); + nid = 0; + } + return mpam_ris_create(msc, res->ris_index, MPAM_CLASS_MEMORY, + MPAM_CLASS_ID_DEFAULT, nid); + default: + /* These get discovered later and are treated as unknown */ + return 0; + } +} + +int acpi_mpam_parse_resources(struct mpam_msc *msc, + struct acpi_mpam_msc_node *tbl_msc) +{ + int i, err; + char *ptr, *table_end; + struct acpi_mpam_resource_node *resource; + + table_end = (char *)tbl_msc + tbl_msc->length; + ptr = (char *)(tbl_msc + 1); + for (i = 0; i < tbl_msc->num_resource_nodes; i++) { + u64 max_deps, remaining_table; + + if (ptr + sizeof(*resource) > table_end) + return -EINVAL; + + resource = (struct acpi_mpam_resource_node *)ptr; + + remaining_table = table_end - ptr; + max_deps = remaining_table / sizeof(struct acpi_mpam_func_deps); + if (resource->num_functional_deps > max_deps) { + pr_debug("MSC has impossible number of functional dependencies\n"); + return -EINVAL; + } + + err = acpi_mpam_parse_resource(msc, resource); + if (err) + return err; + + ptr += sizeof(*resource); + ptr += resource->num_functional_deps * sizeof(struct acpi_mpam_func_deps); + } + + return 0; +} + +/* + * Creates the device power management link and returns true if the + * acpi id is valid and usable for cpu affinity. This is the case + * when the linked device is a processor or a processor container. + */ +static bool __init parse_msc_pm_link(struct acpi_mpam_msc_node *tbl_msc, + struct platform_device *pdev, + u32 *acpi_id) +{ + char hid[sizeof(tbl_msc->hardware_id_linked_device) + 1] = { 0 }; + bool acpi_id_valid = false; + struct acpi_device *buddy; + char uid[11]; + int len; + + memcpy(hid, &tbl_msc->hardware_id_linked_device, + sizeof(tbl_msc->hardware_id_linked_device)); + + if (!strcmp(hid, ACPI_PROCESSOR_CONTAINER_HID)) { + *acpi_id = tbl_msc->instance_id_linked_device; + acpi_id_valid = true; + } + + len = snprintf(uid, sizeof(uid), "%u", + tbl_msc->instance_id_linked_device); + if (len >= sizeof(uid)) { + pr_debug("Failed to convert uid of device for power management."); + return acpi_id_valid; + } + + buddy = acpi_dev_get_first_match_dev(hid, uid, -1); + if (buddy) { + device_link_add(&pdev->dev, &buddy->dev, DL_FLAG_STATELESS); + acpi_dev_put(buddy); + } + + return acpi_id_valid; +} + +static int decode_interface_type(struct acpi_mpam_msc_node *tbl_msc, + enum mpam_msc_iface *iface) +{ + switch (tbl_msc->interface_type) { + case ACPI_MPAM_MSC_IFACE_MMIO: + *iface = MPAM_IFACE_MMIO; + return 0; + case ACPI_MPAM_MSC_IFACE_PCC: + *iface = MPAM_IFACE_PCC; + return 0; + default: + return -EINVAL; + } +} + +static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_node *tbl_msc) +{ + struct platform_device *pdev __free(platform_device_put) = + platform_device_alloc("mpam_msc", tbl_msc->identifier); + int next_res = 0, next_prop = 0, err; + /* pcc, nrdy, affinity and a sentinel */ + struct property_entry props[4] = { 0 }; + /* mmio, 2xirq, no sentinel. */ + struct resource res[3] = { 0 }; + struct acpi_device *companion; + enum mpam_msc_iface iface; + char uid[16]; + u32 acpi_id; + + if (!pdev) + return ERR_PTR(-ENOMEM); + + /* Some power management is described in the namespace: */ + err = snprintf(uid, sizeof(uid), "%u", tbl_msc->identifier); + if (err > 0 && err < sizeof(uid)) { + companion = acpi_dev_get_first_match_dev("ARMHAA5C", uid, -1); + if (companion) { + ACPI_COMPANION_SET(&pdev->dev, companion); + acpi_dev_put(companion); + } else { + pr_debug("MSC.%u: missing namespace entry\n", tbl_msc->identifier); + } + } + + if (decode_interface_type(tbl_msc, &iface)) { + pr_debug("MSC.%u: unknown interface type\n", tbl_msc->identifier); + return ERR_PTR(-EINVAL); + } + + if (iface == MPAM_IFACE_MMIO) { + res[next_res++] = DEFINE_RES_MEM_NAMED(tbl_msc->base_address, + tbl_msc->mmio_size, + "MPAM:MSC"); + } else if (iface == MPAM_IFACE_PCC) { + props[next_prop++] = PROPERTY_ENTRY_U32("pcc-channel", + tbl_msc->base_address); + } + + acpi_mpam_parse_irqs(pdev, tbl_msc, res, &next_res); + + WARN_ON_ONCE(next_res > ARRAY_SIZE(res)); + err = platform_device_add_resources(pdev, res, next_res); + if (err) + return ERR_PTR(err); + + props[next_prop++] = PROPERTY_ENTRY_U32("arm,not-ready-us", + tbl_msc->max_nrdy_usec); + + /* + * The MSC's CPU affinity is described via its linked power + * management device, but only if it points at a Processor or + * Processor Container. + */ + if (parse_msc_pm_link(tbl_msc, pdev, &acpi_id)) + props[next_prop++] = PROPERTY_ENTRY_U32("cpu_affinity", acpi_id); + + WARN_ON_ONCE(next_prop > ARRAY_SIZE(props) - 1); + err = device_create_managed_software_node(&pdev->dev, props, NULL); + if (err) + return ERR_PTR(err); + + /* + * Stash the table entry for acpi_mpam_parse_resources() to discover + * what this MSC controls. + */ + err = platform_device_add_data(pdev, tbl_msc, tbl_msc->length); + if (err) + return ERR_PTR(err); + + err = platform_device_add(pdev); + if (err) + return ERR_PTR(err); + + return_ptr(pdev); +} + +static int __init acpi_mpam_parse(void) +{ + char *table_end, *table_offset; + struct acpi_mpam_msc_node *tbl_msc; + struct platform_device *pdev; + + if (acpi_disabled || !system_supports_mpam()) + return 0; + + struct acpi_table_header *table __free(acpi_put_table) = + acpi_get_table_pointer(ACPI_SIG_MPAM, 0); + + if (IS_ERR(table)) + return 0; + + if (table->revision < 1) { + pr_debug("MPAM ACPI table revision %d not supported\n", table->revision); + return 0; + } + + table_offset = (char *)(table + 1); + table_end = (char *)table + table->length; + + while (table_offset < table_end) { + tbl_msc = (struct acpi_mpam_msc_node *)table_offset; + if (table_offset + sizeof(*tbl_msc) > table_end || + table_offset + tbl_msc->length > table_end) { + pr_err("MSC entry overlaps end of ACPI table\n"); + return -EINVAL; + } + table_offset += tbl_msc->length; + + /* + * If any of the reserved fields are set, make no attempt to + * parse the MSC structure. This MSC will still be counted by + * acpi_mpam_count_msc(), meaning the MPAM driver can't probe + * against all MSC, and will never be enabled. There is no way + * to enable it safely, because we cannot determine safe + * system-wide partid and pmg ranges in this situation. + */ + if (tbl_msc->reserved || tbl_msc->reserved1 || tbl_msc->reserved2) { + pr_err_once("Unrecognised MSC, MPAM not usable\n"); + pr_debug("MSC.%u: reserved field set\n", tbl_msc->identifier); + continue; + } + + if (!tbl_msc->mmio_size) { + pr_debug("MSC.%u: marked as disabled\n", tbl_msc->identifier); + continue; + } + + pdev = acpi_mpam_parse_msc(tbl_msc); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + } + + return 0; +} + +/** + * acpi_mpam_count_msc() - Count the number of MSC described by firmware. + * + * Returns the number of MSCs, or zero for an error. + * + * This can be called before or in parallel with acpi_mpam_parse(). + */ +int acpi_mpam_count_msc(void) +{ + char *table_end, *table_offset; + struct acpi_mpam_msc_node *tbl_msc; + int count = 0; + + if (acpi_disabled || !system_supports_mpam()) + return 0; + + struct acpi_table_header *table __free(acpi_put_table) = + acpi_get_table_pointer(ACPI_SIG_MPAM, 0); + + if (IS_ERR(table)) + return 0; + + if (table->revision < 1) + return 0; + + table_offset = (char *)(table + 1); + table_end = (char *)table + table->length; + + while (table_offset < table_end) { + tbl_msc = (struct acpi_mpam_msc_node *)table_offset; + + if (table_offset + sizeof(*tbl_msc) > table_end) + return -EINVAL; + if (tbl_msc->length < sizeof(*tbl_msc)) + return -EINVAL; + if (tbl_msc->length > table_end - table_offset) + return -EINVAL; + table_offset += tbl_msc->length; + + if (!tbl_msc->mmio_size) + continue; + + count++; + } + + return count; +} + +/* + * Call after ACPI devices have been created, which happens behind acpi_scan_init() + * called from subsys_initcall(). PCC requires the mailbox driver, which is + * initialised from postcore_initcall(). + */ +subsys_initcall_sync(acpi_mpam_parse); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 67b76492c839..34181fa52e93 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -91,7 +91,6 @@ enum { }; struct acpi_battery { - struct mutex lock; struct mutex update_lock; struct power_supply *bat; struct power_supply_desc bat_desc; @@ -535,11 +534,9 @@ static int acpi_battery_get_info(struct acpi_battery *battery) struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_status status = AE_ERROR; - mutex_lock(&battery->lock); status = acpi_evaluate_object(battery->device->handle, use_bix ? "_BIX":"_BIF", NULL, &buffer); - mutex_unlock(&battery->lock); if (ACPI_FAILURE(status)) { acpi_handle_info(battery->device->handle, @@ -576,11 +573,8 @@ static int acpi_battery_get_state(struct acpi_battery *battery) msecs_to_jiffies(cache_time))) return 0; - mutex_lock(&battery->lock); status = acpi_evaluate_object(battery->device->handle, "_BST", NULL, &buffer); - mutex_unlock(&battery->lock); - if (ACPI_FAILURE(status)) { acpi_handle_info(battery->device->handle, "_BST evaluation failed: %s", @@ -628,11 +622,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery) !test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags)) return -ENODEV; - mutex_lock(&battery->lock); status = acpi_execute_simple_method(battery->device->handle, "_BTP", battery->alarm); - mutex_unlock(&battery->lock); - if (ACPI_FAILURE(status)) return -ENODEV; @@ -1235,9 +1226,6 @@ static int acpi_battery_add(struct acpi_device *device) strscpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_BATTERY_CLASS); device->driver_data = battery; - result = devm_mutex_init(&device->dev, &battery->lock); - if (result) - return result; result = devm_mutex_init(&device->dev, &battery->update_lock); if (result) diff --git a/drivers/acpi/dptf/Makefile b/drivers/acpi/dptf/Makefile index 297340682f66..e912a3be1d28 100644 --- a/drivers/acpi/dptf/Makefile +++ b/drivers/acpi/dptf/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_ACPI) += int340x_thermal.o obj-$(CONFIG_DPTF_POWER) += dptf_power.o obj-$(CONFIG_DPTF_PCH_FIVR) += dptf_pch_fivr.o diff --git a/drivers/acpi/dptf/dptf_pch_fivr.c b/drivers/acpi/dptf/dptf_pch_fivr.c index 952216c67d58..8d7e555929d3 100644 --- a/drivers/acpi/dptf/dptf_pch_fivr.c +++ b/drivers/acpi/dptf/dptf_pch_fivr.c @@ -41,7 +41,7 @@ static int pch_fivr_read(acpi_handle handle, char *method, struct pch_fivr_resp ret = 0; release_buffer: - kfree(buffer.pointer); + ACPI_FREE(buffer.pointer); return ret; } diff --git a/drivers/acpi/dptf/dptf_power.c b/drivers/acpi/dptf/dptf_power.c index 776914f31b9e..55ccbb8ddbe3 100644 --- a/drivers/acpi/dptf/dptf_power.c +++ b/drivers/acpi/dptf/dptf_power.c @@ -240,6 +240,8 @@ static const struct acpi_device_id int3407_device_ids[] = { {"INTC10D9", 0}, {"INTC1100", 0}, {"INTC1101", 0}, + {"INTC10F7", 0}, + {"INTC10F8", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, int3407_device_ids); diff --git a/drivers/acpi/dptf/int340x_thermal.c b/drivers/acpi/dptf/int340x_thermal.c deleted file mode 100644 index a222df059a16..000000000000 --- a/drivers/acpi/dptf/int340x_thermal.c +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * ACPI support for int340x thermal drivers - * - * Copyright (C) 2014, Intel Corporation - * Authors: Zhang Rui <rui.zhang@intel.com> - */ - -#include <linux/acpi.h> -#include <linux/module.h> - -#include "../internal.h" - -#define INT3401_DEVICE 0X01 -static const struct acpi_device_id int340x_thermal_device_ids[] = { - {"INT3400"}, - {"INT3401", INT3401_DEVICE}, - {"INT3402"}, - {"INT3403"}, - {"INT3404"}, - {"INT3406"}, - {"INT3407"}, - {"INT3408"}, - {"INT3409"}, - {"INT340A"}, - {"INT340B"}, - {"INT3532"}, - {"INTC1040"}, - {"INTC1041"}, - {"INTC1042"}, - {"INTC1043"}, - {"INTC1044"}, - {"INTC1045"}, - {"INTC1046"}, - {"INTC1047"}, - {"INTC1048"}, - {"INTC1049"}, - {"INTC1050"}, - {"INTC1060"}, - {"INTC1061"}, - {"INTC1062"}, - {"INTC1063"}, - {"INTC1064"}, - {"INTC1065"}, - {"INTC1066"}, - {"INTC1068"}, - {"INTC1069"}, - {"INTC106A"}, - {"INTC106B"}, - {"INTC106C"}, - {"INTC106D"}, - {"INTC10A0"}, - {"INTC10A1"}, - {"INTC10A2"}, - {"INTC10A3"}, - {"INTC10A4"}, - {"INTC10A5"}, - {"INTC10D4"}, - {"INTC10D5"}, - {"INTC10D6"}, - {"INTC10D7"}, - {"INTC10D8"}, - {"INTC10D9"}, - {"INTC10FC"}, - {"INTC10FD"}, - {"INTC10FE"}, - {"INTC10FF"}, - {"INTC1100"}, - {"INTC1101"}, - {"INTC1102"}, - {""}, -}; - -static int int340x_thermal_handler_attach(struct acpi_device *adev, - const struct acpi_device_id *id) -{ - if (IS_ENABLED(CONFIG_INT340X_THERMAL)) - acpi_create_platform_device(adev, NULL); - /* Intel SoC DTS thermal driver needs INT3401 to set IRQ descriptor */ - else if (IS_ENABLED(CONFIG_INTEL_SOC_DTS_THERMAL) && - id->driver_data == INT3401_DEVICE) - acpi_create_platform_device(adev, NULL); - return 1; -} - -static struct acpi_scan_handler int340x_thermal_handler = { - .ids = int340x_thermal_device_ids, - .attach = int340x_thermal_handler_attach, -}; - -void __init acpi_int340x_thermal_init(void) -{ - acpi_scan_add_handler(&int340x_thermal_handler); -} diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 7855bbf752b1..59b3d50ff01e 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -2294,7 +2294,8 @@ static int acpi_ec_init_workqueues(void) ec_wq = alloc_ordered_workqueue("kec", 0); if (!ec_query_wq) - ec_query_wq = alloc_workqueue("kec_query", 0, ec_max_queries); + ec_query_wq = alloc_workqueue("kec_query", WQ_PERCPU, + ec_max_queries); if (!ec_wq || !ec_query_wq) { acpi_ec_destroy_workqueues(); diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index bedbab0e8e4e..97ce3212edf3 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -11,6 +11,7 @@ #define _ACPI_FAN_H_ #include <linux/kconfig.h> +#include <linux/limits.h> #define ACPI_FAN_DEVICE_IDS \ {"INT3404", }, /* Fan */ \ @@ -21,6 +22,7 @@ {"INTC10A2", }, /* Fan for Raptor Lake generation */ \ {"INTC10D6", }, /* Fan for Panther Lake generation */ \ {"INTC10FE", }, /* Fan for Wildcat Lake generation */ \ + {"INTC10F5", }, /* Fan for Nova Lake generation */ \ {"PNP0C0B", } /* Generic ACPI fan */ #define ACPI_FPS_NAME_LEN 20 @@ -55,19 +57,58 @@ struct acpi_fan { struct acpi_fan_fif fif; struct acpi_fan_fps *fps; int fps_count; + /* A value of 0 means that trippoint-related functions are not supported */ + u32 fan_trip_granularity; +#if IS_REACHABLE(CONFIG_HWMON) + struct device *hdev; +#endif struct thermal_cooling_device *cdev; struct device_attribute fst_speed; struct device_attribute fine_grain_control; }; +/** + * acpi_fan_speed_valid - Check if fan speed value is valid + * @speeed: Speed value returned by the ACPI firmware + * + * Check if the fan speed value returned by the ACPI firmware is valid. This function is + * necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the + * ACPI fan does not support speed reporting. Additionally, some buggy ACPI firmware + * implementations return a value larger than the 32-bit integer value defined by + * the ACPI specification when using placeholder values. Such invalid values are also + * detected by this function. + * + * Returns: True if the fan speed value is valid, false otherwise. + */ +static inline bool acpi_fan_speed_valid(u64 speed) +{ + return speed < U32_MAX; +} + +/** + * acpi_fan_power_valid - Check if fan power value is valid + * @power: Power value returned by the ACPI firmware + * + * Check if the fan power value returned by the ACPI firmware is valid. + * See acpi_fan_speed_valid() for details. + * + * Returns: True if the fan power value is valid, false otherwise. + */ +static inline bool acpi_fan_power_valid(u64 power) +{ + return power < U32_MAX; +} + int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst); int acpi_fan_create_attributes(struct acpi_device *device); void acpi_fan_delete_attributes(struct acpi_device *device); #if IS_REACHABLE(CONFIG_HWMON) int devm_acpi_fan_create_hwmon(struct device *dev); +void acpi_fan_notify_hwmon(struct device *dev); #else static inline int devm_acpi_fan_create_hwmon(struct device *dev) { return 0; }; +static inline void acpi_fan_notify_hwmon(struct device *dev) { }; #endif #endif diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 46e7fe7a506d..fb08b8549ed7 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -7,11 +7,16 @@ * Copyright (C) 2022 Intel Corporation. All rights reserved. */ +#include <linux/bits.h> #include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/math.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/uaccess.h> +#include <linux/uuid.h> #include <linux/thermal.h> #include <linux/acpi.h> #include <linux/platform_device.h> @@ -19,6 +24,26 @@ #include "fan.h" +#define ACPI_FAN_NOTIFY_STATE_CHANGED 0x80 + +/* + * Defined inside the "Fan Noise Signal" section at + * https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide. + */ +static const guid_t acpi_fan_microsoft_guid = GUID_INIT(0xA7611840, 0x99FE, 0x41AE, 0xA4, 0x88, + 0x35, 0xC7, 0x59, 0x26, 0xC8, 0xEB); +#define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1 +#define ACPI_FAN_DSM_SET_TRIP_POINTS 2 +#define ACPI_FAN_DSM_GET_OPERATING_RANGES 3 + +/* + * Ensures that fans with a very low trip point granularity + * do not send too many notifications. + */ +static uint min_trip_distance = 100; +module_param(min_trip_distance, uint, 0); +MODULE_PARM_DESC(min_trip_distance, "Minimum distance between fan speed trip points in RPM"); + static const struct acpi_device_id fan_device_ids[] = { ACPI_FAN_DEVICE_IDS, {"", 0}, @@ -308,6 +333,182 @@ err: return status; } +static int acpi_fan_dsm_init(struct device *dev) +{ + union acpi_object dummy = { + .package = { + .type = ACPI_TYPE_PACKAGE, + .count = 0, + .elements = NULL, + }, + }; + struct acpi_fan *fan = dev_get_drvdata(dev); + union acpi_object *obj; + int ret = 0; + + if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, + BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) | + BIT(ACPI_FAN_DSM_SET_TRIP_POINTS))) + return 0; + + dev_info(dev, "Using Microsoft fan extensions\n"); + + obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0, + ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy, + ACPI_TYPE_INTEGER); + if (!obj) + return -EIO; + + if (obj->integer.value > U32_MAX) + ret = -EOVERFLOW; + else + fan->fan_trip_granularity = obj->integer.value; + + kfree(obj); + + return ret; +} + +static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower) +{ + union acpi_object args[2] = { + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = lower, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = upper, + }, + }, + }; + struct acpi_fan *fan = dev_get_drvdata(dev); + union acpi_object in = { + .package = { + .type = ACPI_TYPE_PACKAGE, + .count = ARRAY_SIZE(args), + .elements = args, + }, + }; + union acpi_object *obj; + + obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, + ACPI_FAN_DSM_SET_TRIP_POINTS, &in); + kfree(obj); + + return 0; +} + +static int acpi_fan_dsm_start(struct device *dev) +{ + struct acpi_fan *fan = dev_get_drvdata(dev); + int ret; + + if (!fan->fan_trip_granularity) + return 0; + + /* + * Some firmware implementations only update the values returned by the + * _FST control method when a notification is received. This usually + * works with Microsoft Windows as setting up trip points will keep + * triggering said notifications, but will cause issues when using _FST + * without the Microsoft-specific trip point extension. + * + * Because of this, an initial notification needs to be triggered to + * start the cycle of trip points updates. This is achieved by setting + * the trip points sequencially to two separate ranges. As by the + * Microsoft specification the firmware should trigger a notification + * immediately if the fan speed is outside the trip point range. This + * _should_ result in at least one notification as both ranges do not + * overlap, meaning that the current fan speed needs to be outside at + * least one range. + */ + ret = acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity, 0); + if (ret < 0) + return ret; + + return acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity * 3, + fan->fan_trip_granularity * 2); +} + +static int acpi_fan_dsm_update_trips_points(struct device *dev, struct acpi_fan_fst *fst) +{ + struct acpi_fan *fan = dev_get_drvdata(dev); + u64 upper, lower; + + if (!fan->fan_trip_granularity) + return 0; + + if (!acpi_fan_speed_valid(fst->speed)) + return -EINVAL; + + upper = roundup_u64(fst->speed + min_trip_distance, fan->fan_trip_granularity); + if (fst->speed <= min_trip_distance) { + lower = 0; + } else { + /* + * Valid fan speed values cannot be larger than 32 bit, so + * we can safely assume that no overflow will happen here. + */ + lower = rounddown((u32)fst->speed - min_trip_distance, fan->fan_trip_granularity); + } + + return acpi_fan_dsm_set_trip_points(dev, upper, lower); +} + +static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context) +{ + struct device *dev = context; + struct acpi_fan_fst fst; + int ret; + + switch (event) { + case ACPI_FAN_NOTIFY_STATE_CHANGED: + /* + * The ACPI specification says that we must evaluate _FST when we + * receive an ACPI event indicating that the fan state has changed. + */ + ret = acpi_fan_get_fst(handle, &fst); + if (ret < 0) { + dev_err(dev, "Error retrieving current fan status: %d\n", ret); + } else { + ret = acpi_fan_dsm_update_trips_points(dev, &fst); + if (ret < 0) + dev_err(dev, "Failed to update trip points: %d\n", ret); + } + + acpi_fan_notify_hwmon(dev); + acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0); + break; + default: + dev_dbg(dev, "Unsupported ACPI notification 0x%x\n", event); + break; + } +} + +static void acpi_fan_notify_remove(void *data) +{ + struct acpi_fan *fan = data; + + acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler); +} + +static int devm_acpi_fan_notify_init(struct device *dev) +{ + struct acpi_fan *fan = dev_get_drvdata(dev); + acpi_status status; + + status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, + acpi_fan_notify_handler, dev); + if (ACPI_FAILURE(status)) + return -EIO; + + return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan); +} + static int acpi_fan_probe(struct platform_device *pdev) { int result = 0; @@ -347,10 +548,24 @@ static int acpi_fan_probe(struct platform_device *pdev) } if (fan->has_fst) { + result = acpi_fan_dsm_init(&pdev->dev); + if (result) + return result; + result = devm_acpi_fan_create_hwmon(&pdev->dev); if (result) return result; + result = devm_acpi_fan_notify_init(&pdev->dev); + if (result) + return result; + + result = acpi_fan_dsm_start(&pdev->dev); + if (result) { + dev_err(&pdev->dev, "Failed to start Microsoft fan extensions\n"); + return result; + } + result = acpi_fan_create_attributes(device); if (result) return result; @@ -436,8 +651,14 @@ static int acpi_fan_suspend(struct device *dev) static int acpi_fan_resume(struct device *dev) { - int result; struct acpi_fan *fan = dev_get_drvdata(dev); + int result; + + if (fan->has_fst) { + result = acpi_fan_dsm_start(dev); + if (result) + dev_err(dev, "Failed to start Microsoft fan extensions: %d\n", result); + } if (fan->acpi4) return 0; diff --git a/drivers/acpi/fan_hwmon.c b/drivers/acpi/fan_hwmon.c index 4b2c2007f2d7..d3374f8f524b 100644 --- a/drivers/acpi/fan_hwmon.c +++ b/drivers/acpi/fan_hwmon.c @@ -15,10 +15,6 @@ #include "fan.h" -/* Returned when the ACPI fan does not support speed reporting */ -#define FAN_SPEED_UNAVAILABLE U32_MAX -#define FAN_POWER_UNAVAILABLE U32_MAX - static struct acpi_fan_fps *acpi_fan_get_current_fps(struct acpi_fan *fan, u64 control) { unsigned int i; @@ -77,7 +73,7 @@ static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_ * when the associated attribute should not be created. */ for (i = 0; i < fan->fps_count; i++) { - if (fan->fps[i].power != FAN_POWER_UNAVAILABLE) + if (acpi_fan_power_valid(fan->fps[i].power)) return 0444; } @@ -106,7 +102,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_fan: switch (attr) { case hwmon_fan_input: - if (fst.speed == FAN_SPEED_UNAVAILABLE) + if (!acpi_fan_speed_valid(fst.speed)) return -ENODEV; if (fst.speed > LONG_MAX) @@ -134,7 +130,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, if (!fps) return -EIO; - if (fps->power == FAN_POWER_UNAVAILABLE) + if (!acpi_fan_power_valid(fps->power)) return -ENODEV; if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT) @@ -166,12 +162,19 @@ static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = { .info = acpi_fan_hwmon_info, }; +void acpi_fan_notify_hwmon(struct device *dev) +{ + struct acpi_fan *fan = dev_get_drvdata(dev); + + hwmon_notify_event(fan->hdev, hwmon_fan, hwmon_fan_input, 0); +} + int devm_acpi_fan_create_hwmon(struct device *dev) { struct acpi_fan *fan = dev_get_drvdata(dev); - struct device *hdev; - hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan, &acpi_fan_hwmon_chip_info, - NULL); - return PTR_ERR_OR_ZERO(hdev); + fan->hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan, + &acpi_fan_hwmon_chip_info, NULL); + + return PTR_ERR_OR_ZERO(fan->hdev); } diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 63354972ab0b..40f875b265a9 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -27,7 +27,6 @@ static inline void acpi_pci_link_init(void) {} void acpi_processor_init(void); void acpi_platform_init(void); void acpi_pnp_init(void); -void acpi_int340x_thermal_init(void); int acpi_sysfs_init(void); void acpi_gpe_apply_masked_gpes(void); void acpi_container_init(void); diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 5ff343096ece..05393a7315fe 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -398,7 +398,7 @@ static void acpi_os_drop_map_ref(struct acpi_ioremap *map) list_del_rcu(&map->list); INIT_RCU_WORK(&map->track.rwork, acpi_os_map_remove); - queue_rcu_work(system_wq, &map->track.rwork); + queue_rcu_work(system_percpu_wq, &map->track.rwork); } /** @@ -1694,8 +1694,8 @@ acpi_status __init acpi_os_initialize(void) acpi_status __init acpi_os_initialize1(void) { - kacpid_wq = alloc_workqueue("kacpid", 0, 1); - kacpi_notify_wq = alloc_workqueue("kacpi_notify", 0, 0); + kacpid_wq = alloc_workqueue("kacpid", WQ_PERCPU, 1); + kacpi_notify_wq = alloc_workqueue("kacpi_notify", WQ_PERCPU, 0); kacpi_hotplug_wq = alloc_ordered_workqueue("kacpi_hotplug", 0); BUG_ON(!kacpid_wq); BUG_ON(!kacpi_notify_wq); diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c index 54676e3d82dd..de5f8c018333 100644 --- a/drivers/acpi/pptt.c +++ b/drivers/acpi/pptt.c @@ -21,6 +21,25 @@ #include <linux/cacheinfo.h> #include <acpi/processor.h> +/* + * The acpi_pptt_cache_v1 in actbl2.h, which is imported from acpica, + * only contains the cache_id field rather than all the fields of the + * Cache Type Structure. Use this alternative structure until it is + * resolved in acpica. + */ +struct acpi_pptt_cache_v1_full { + struct acpi_subtable_header header; + u16 reserved; + u32 flags; + u32 next_level_of_cache; + u32 size; + u32 number_of_sets; + u8 associativity; + u8 attributes; + u16 line_size; + u32 cache_id; +} __packed; + static struct acpi_subtable_header *fetch_pptt_subtable(struct acpi_table_header *table_hdr, u32 pptt_ref) { @@ -56,6 +75,18 @@ static struct acpi_pptt_cache *fetch_pptt_cache(struct acpi_table_header *table_ return (struct acpi_pptt_cache *)fetch_pptt_subtable(table_hdr, pptt_ref); } +static struct acpi_pptt_cache_v1_full *upgrade_pptt_cache(struct acpi_pptt_cache *cache) +{ + if (cache->header.length < sizeof(struct acpi_pptt_cache_v1_full)) + return NULL; + + /* No use for v1 if the only additional field is invalid */ + if (!(cache->flags & ACPI_PPTT_CACHE_ID_VALID)) + return NULL; + + return (struct acpi_pptt_cache_v1_full *)cache; +} + static struct acpi_subtable_header *acpi_get_pptt_resource(struct acpi_table_header *table_hdr, struct acpi_pptt_processor *node, int resource) @@ -177,14 +208,14 @@ acpi_find_cache_level(struct acpi_table_header *table_hdr, } /** - * acpi_count_levels() - Given a PPTT table, and a CPU node, count the cache - * levels and split cache levels (data/instruction). + * acpi_count_levels() - Given a PPTT table, and a CPU node, count the + * total number of levels and split cache levels (data/instruction). * @table_hdr: Pointer to the head of the PPTT table * @cpu_node: processor node we wish to count caches for - * @levels: Number of levels if success. * @split_levels: Number of split cache levels (data/instruction) if * success. Can by NULL. * + * Return: number of levels. * Given a processor node containing a processing unit, walk into it and count * how many levels exist solely for it, and then walk up each level until we hit * the root node (ignore the package level because it may be possible to have @@ -192,14 +223,18 @@ acpi_find_cache_level(struct acpi_table_header *table_hdr, * split cache levels (data/instruction) that exist at each level on the way * up. */ -static void acpi_count_levels(struct acpi_table_header *table_hdr, - struct acpi_pptt_processor *cpu_node, - unsigned int *levels, unsigned int *split_levels) +static int acpi_count_levels(struct acpi_table_header *table_hdr, + struct acpi_pptt_processor *cpu_node, + unsigned int *split_levels) { + int current_level = 0; + do { - acpi_find_cache_level(table_hdr, cpu_node, levels, split_levels, 0, 0); + acpi_find_cache_level(table_hdr, cpu_node, ¤t_level, split_levels, 0, 0); cpu_node = fetch_pptt_node(table_hdr, cpu_node->parent); } while (cpu_node); + + return current_level; } /** @@ -351,7 +386,6 @@ static struct acpi_pptt_cache *acpi_find_cache_node(struct acpi_table_header *ta * @this_leaf: Kernel cache info structure being updated * @found_cache: The PPTT node describing this cache instance * @cpu_node: A unique reference to describe this cache instance - * @revision: The revision of the PPTT table * * The ACPI spec implies that the fields in the cache structures are used to * extend and correct the information probed from the hardware. Lets only @@ -361,10 +395,9 @@ static struct acpi_pptt_cache *acpi_find_cache_node(struct acpi_table_header *ta */ static void update_cache_properties(struct cacheinfo *this_leaf, struct acpi_pptt_cache *found_cache, - struct acpi_pptt_processor *cpu_node, - u8 revision) + struct acpi_pptt_processor *cpu_node) { - struct acpi_pptt_cache_v1* found_cache_v1; + struct acpi_pptt_cache_v1_full *found_cache_v1; this_leaf->fw_token = cpu_node; if (found_cache->flags & ACPI_PPTT_SIZE_PROPERTY_VALID) @@ -414,9 +447,8 @@ static void update_cache_properties(struct cacheinfo *this_leaf, found_cache->flags & ACPI_PPTT_CACHE_TYPE_VALID) this_leaf->type = CACHE_TYPE_UNIFIED; - if (revision >= 3 && (found_cache->flags & ACPI_PPTT_CACHE_ID_VALID)) { - found_cache_v1 = ACPI_ADD_PTR(struct acpi_pptt_cache_v1, - found_cache, sizeof(struct acpi_pptt_cache)); + found_cache_v1 = upgrade_pptt_cache(found_cache); + if (found_cache_v1) { this_leaf->id = found_cache_v1->cache_id; this_leaf->attributes |= CACHE_ID; } @@ -441,8 +473,7 @@ static void cache_setup_acpi_cpu(struct acpi_table_header *table, pr_debug("found = %p %p\n", found_cache, cpu_node); if (found_cache) update_cache_properties(this_leaf, found_cache, - ACPI_TO_POINTER(ACPI_PTR_DIFF(cpu_node, table)), - table->revision); + ACPI_TO_POINTER(ACPI_PTR_DIFF(cpu_node, table))); index++; } @@ -645,7 +676,7 @@ int acpi_get_cache_info(unsigned int cpu, unsigned int *levels, if (!cpu_node) return -ENOENT; - acpi_count_levels(table, cpu_node, levels, split_levels); + *levels = acpi_count_levels(table, cpu_node, split_levels); pr_debug("Cache Setup: last_level=%d split_levels=%d\n", *levels, split_levels ? *split_levels : -1); @@ -817,3 +848,218 @@ int find_acpi_cpu_topology_hetero_id(unsigned int cpu) return find_acpi_cpu_topology_tag(cpu, PPTT_ABORT_PACKAGE, ACPI_PPTT_ACPI_IDENTICAL); } + +/** + * acpi_pptt_get_child_cpus() - Find all the CPUs below a PPTT + * processor hierarchy node + * + * @table_hdr: A reference to the PPTT table + * @parent_node: A pointer to the processor hierarchy node in the + * table_hdr + * @cpus: A cpumask to fill with the CPUs below @parent_node + * + * Walks up the PPTT from every possible CPU to find if the provided + * @parent_node is a parent of this CPU. + */ +static void acpi_pptt_get_child_cpus(struct acpi_table_header *table_hdr, + struct acpi_pptt_processor *parent_node, + cpumask_t *cpus) +{ + struct acpi_pptt_processor *cpu_node; + u32 acpi_id; + int cpu; + + cpumask_clear(cpus); + + for_each_possible_cpu(cpu) { + acpi_id = get_acpi_id_for_cpu(cpu); + cpu_node = acpi_find_processor_node(table_hdr, acpi_id); + + while (cpu_node) { + if (cpu_node == parent_node) { + cpumask_set_cpu(cpu, cpus); + break; + } + cpu_node = fetch_pptt_node(table_hdr, cpu_node->parent); + } + } +} + +/** + * acpi_pptt_get_cpus_from_container() - Populate a cpumask with all CPUs in a + * processor container + * @acpi_cpu_id: The UID of the processor container + * @cpus: The resulting CPU mask + * + * Find the specified Processor Container, and fill @cpus with all the cpus + * below it. + * + * Not all 'Processor Hierarchy' entries in the PPTT are either a CPU + * or a Processor Container, they may exist purely to describe a + * Private resource. CPUs have to be leaves, so a Processor Container + * is a non-leaf that has the 'ACPI Processor ID valid' flag set. + */ +void acpi_pptt_get_cpus_from_container(u32 acpi_cpu_id, cpumask_t *cpus) +{ + struct acpi_table_header *table_hdr; + struct acpi_subtable_header *entry; + unsigned long table_end; + u32 proc_sz; + + cpumask_clear(cpus); + + table_hdr = acpi_get_pptt(); + if (!table_hdr) + return; + + table_end = (unsigned long)table_hdr + table_hdr->length; + entry = ACPI_ADD_PTR(struct acpi_subtable_header, table_hdr, + sizeof(struct acpi_table_pptt)); + proc_sz = sizeof(struct acpi_pptt_processor); + while ((unsigned long)entry + proc_sz <= table_end) { + if (entry->type == ACPI_PPTT_TYPE_PROCESSOR) { + struct acpi_pptt_processor *cpu_node; + + cpu_node = (struct acpi_pptt_processor *)entry; + if (cpu_node->flags & ACPI_PPTT_ACPI_PROCESSOR_ID_VALID && + !acpi_pptt_leaf_node(table_hdr, cpu_node) && + cpu_node->acpi_processor_id == acpi_cpu_id) { + acpi_pptt_get_child_cpus(table_hdr, cpu_node, cpus); + break; + } + } + entry = ACPI_ADD_PTR(struct acpi_subtable_header, entry, + entry->length); + } +} + +/** + * find_acpi_cache_level_from_id() - Get the level of the specified cache + * @cache_id: The id field of the cache + * + * Determine the level relative to any CPU for the cache identified by + * cache_id. This allows the property to be found even if the CPUs are offline. + * + * The returned level can be used to group caches that are peers. + * + * The PPTT table must be rev 3 or later. + * + * If one CPU's L2 is shared with another CPU as L3, this function will return + * an unpredictable value. + * + * Return: -ENOENT if the PPTT doesn't exist, the revision isn't supported or + * the cache cannot be found. + * Otherwise returns a value which represents the level of the specified cache. + */ +int find_acpi_cache_level_from_id(u32 cache_id) +{ + int cpu; + struct acpi_table_header *table; + + table = acpi_get_pptt(); + if (!table) + return -ENOENT; + + if (table->revision < 3) + return -ENOENT; + + for_each_possible_cpu(cpu) { + bool empty; + int level = 1; + u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + struct acpi_pptt_cache *cache; + struct acpi_pptt_processor *cpu_node; + + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); + if (!cpu_node) + continue; + + do { + int cache_type[] = {CACHE_TYPE_INST, CACHE_TYPE_DATA, CACHE_TYPE_UNIFIED}; + + empty = true; + for (int i = 0; i < ARRAY_SIZE(cache_type); i++) { + struct acpi_pptt_cache_v1_full *cache_v1; + + cache = acpi_find_cache_node(table, acpi_cpu_id, cache_type[i], + level, &cpu_node); + if (!cache) + continue; + + empty = false; + + cache_v1 = upgrade_pptt_cache(cache); + if (cache_v1 && cache_v1->cache_id == cache_id) + return level; + } + level++; + } while (!empty); + } + + return -ENOENT; +} + +/** + * acpi_pptt_get_cpumask_from_cache_id() - Get the cpus associated with the + * specified cache + * @cache_id: The id field of the cache + * @cpus: Where to build the cpumask + * + * Determine which CPUs are below this cache in the PPTT. This allows the property + * to be found even if the CPUs are offline. + * + * The PPTT table must be rev 3 or later, + * + * Return: -ENOENT if the PPTT doesn't exist, or the cache cannot be found. + * Otherwise returns 0 and sets the cpus in the provided cpumask. + */ +int acpi_pptt_get_cpumask_from_cache_id(u32 cache_id, cpumask_t *cpus) +{ + int cpu; + struct acpi_table_header *table; + + cpumask_clear(cpus); + + table = acpi_get_pptt(); + if (!table) + return -ENOENT; + + if (table->revision < 3) + return -ENOENT; + + for_each_possible_cpu(cpu) { + bool empty; + int level = 1; + u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + struct acpi_pptt_cache *cache; + struct acpi_pptt_processor *cpu_node; + + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); + if (!cpu_node) + continue; + + do { + int cache_type[] = {CACHE_TYPE_INST, CACHE_TYPE_DATA, CACHE_TYPE_UNIFIED}; + + empty = true; + for (int i = 0; i < ARRAY_SIZE(cache_type); i++) { + struct acpi_pptt_cache_v1_full *cache_v1; + + cache = acpi_find_cache_node(table, acpi_cpu_id, cache_type[i], + level, &cpu_node); + + if (!cache) + continue; + + empty = false; + + cache_v1 = upgrade_pptt_cache(cache); + if (cache_v1 && cache_v1->cache_id == cache_id) + cpumask_set_cpu(cpu, cpus); + } + level++; + } while (!empty); + } + + return 0; +} diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index 9b6b71a2ffb5..a4498357bd16 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -54,7 +54,7 @@ static int map_x2apic_id(struct acpi_subtable_header *entry, if (!(apic->lapic_flags & ACPI_MADT_ENABLED)) return -ENODEV; - if (device_declaration && (apic->uid == acpi_id)) { + if (apic->uid == acpi_id && (device_declaration || acpi_id < 255)) { *apic_id = apic->local_apic_id; return 0; } diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 4166090db642..89f2f08b2554 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -732,18 +732,16 @@ static int __cpuidle acpi_idle_enter_s2idle(struct cpuidle_device *dev, return 0; } -static int acpi_processor_setup_cpuidle_cx(struct acpi_processor *pr, - struct cpuidle_device *dev) +static void acpi_processor_setup_cpuidle_cx(struct acpi_processor *pr, + struct cpuidle_device *dev) { int i, count = ACPI_IDLE_STATE_START; struct acpi_processor_cx *cx; - struct cpuidle_state *state; if (max_cstate == 0) max_cstate = 1; for (i = 1; i < ACPI_PROCESSOR_MAX_POWER && i <= max_cstate; i++) { - state = &acpi_idle_driver.states[count]; cx = &pr->power.states[i]; if (!cx->valid) @@ -751,27 +749,13 @@ static int acpi_processor_setup_cpuidle_cx(struct acpi_processor *pr, per_cpu(acpi_cstate[count], dev->cpu) = cx; - if (lapic_timer_needs_broadcast(pr, cx)) - state->flags |= CPUIDLE_FLAG_TIMER_STOP; - - if (cx->type == ACPI_STATE_C3) { - state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; - if (pr->flags.bm_check) - state->flags |= CPUIDLE_FLAG_RCU_IDLE; - } - count++; if (count == CPUIDLE_STATE_MAX) break; } - - if (!count) - return -EINVAL; - - return 0; } -static int acpi_processor_setup_cstates(struct acpi_processor *pr) +static void acpi_processor_setup_cstates(struct acpi_processor *pr) { int i, count; struct acpi_processor_cx *cx; @@ -818,17 +802,21 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) if (cx->type != ACPI_STATE_C1 && !acpi_idle_fallback_to_c1(pr)) state->enter_s2idle = acpi_idle_enter_s2idle; + if (lapic_timer_needs_broadcast(pr, cx)) + state->flags |= CPUIDLE_FLAG_TIMER_STOP; + + if (cx->type == ACPI_STATE_C3) { + state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; + if (pr->flags.bm_check) + state->flags |= CPUIDLE_FLAG_RCU_IDLE; + } + count++; if (count == CPUIDLE_STATE_MAX) break; } drv->state_count = count; - - if (!count) - return -EINVAL; - - return 0; } static inline void acpi_processor_cstate_first_run_checks(void) @@ -1243,7 +1231,8 @@ static int acpi_processor_setup_cpuidle_states(struct acpi_processor *pr) if (pr->flags.has_lpi) return acpi_processor_setup_lpi_states(pr); - return acpi_processor_setup_cstates(pr); + acpi_processor_setup_cstates(pr); + return 0; } /** @@ -1263,7 +1252,8 @@ static int acpi_processor_setup_cpuidle_dev(struct acpi_processor *pr, if (pr->flags.has_lpi) return acpi_processor_ffh_lpi_probe(pr->id); - return acpi_processor_setup_cpuidle_cx(pr, dev); + acpi_processor_setup_cpuidle_cx(pr, dev); + return 0; } static int acpi_processor_get_power_info(struct acpi_processor *pr) diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 43d5e457814e..18e90067d567 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -1280,7 +1280,7 @@ static int acpi_data_prop_read(const struct acpi_device_data *data, ret = acpi_copy_property_array_uint(items, (u64 *)val, nval); break; case DEV_PROP_STRING: - nval = min_t(u32, nval, obj->package.count); + nval = min(nval, obj->package.count); if (nval == 0) return -ENODATA; @@ -1329,13 +1329,14 @@ static int stop_on_next(struct acpi_device *adev, void *data) return 0; } -/** +/* * acpi_get_next_subnode - Return the next child node handle for a fwnode * @fwnode: Firmware node to find the next child node for. * @child: Handle to one of the device's child nodes or a null handle. */ -struct fwnode_handle *acpi_get_next_subnode(const struct fwnode_handle *fwnode, - struct fwnode_handle *child) +static struct fwnode_handle * +acpi_get_next_subnode(const struct fwnode_handle *fwnode, + struct fwnode_handle *child) { struct acpi_device *adev = to_acpi_device_node(fwnode); @@ -1472,7 +1473,7 @@ static struct fwnode_handle *acpi_graph_get_next_endpoint( if (!prev) { do { - port = fwnode_get_next_child_node(fwnode, port); + port = acpi_get_next_subnode(fwnode, port); /* * The names of the port nodes begin with "port@" * followed by the number of the port node and they also @@ -1490,14 +1491,17 @@ static struct fwnode_handle *acpi_graph_get_next_endpoint( if (!port) return NULL; - endpoint = fwnode_get_next_child_node(port, prev); - while (!endpoint) { - port = fwnode_get_next_child_node(fwnode, port); - if (!port) + do { + endpoint = acpi_get_next_subnode(port, prev); + if (endpoint) break; - if (is_acpi_graph_node(port, "port")) - endpoint = fwnode_get_next_child_node(port, NULL); - } + + prev = NULL; + + do { + port = acpi_get_next_subnode(fwnode, port); + } while (port && !is_acpi_graph_node(port, "port")); + } while (port); /* * The names of the endpoint nodes begin with "endpoint@" followed by @@ -1714,6 +1718,7 @@ static int acpi_fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode, if (fwnode_property_read_u32(fwnode, "reg", &endpoint->id)) fwnode_property_read_u32(fwnode, "endpoint", &endpoint->id); + fwnode_handle_put(port_fwnode); return 0; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index ef16d58b2949..416d87f9bd10 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2397,7 +2397,7 @@ static bool acpi_scan_clear_dep_queue(struct acpi_device *adev) * initial enumeration of devices is complete, put it into the unbound * workqueue. */ - queue_work(system_unbound_wq, &cdw->work); + queue_work(system_dfl_wq, &cdw->work); return true; } @@ -2711,7 +2711,6 @@ void __init acpi_scan_init(void) acpi_watchdog_init(); acpi_pnp_init(); acpi_power_resources_init(); - acpi_int340x_thermal_init(); acpi_init_lpit(); acpi_scan_add_handler(&generic_device_handler); diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index c8ee8e42b0f6..68943b98333d 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -642,7 +642,7 @@ static int acpi_suspend_enter(suspend_state_t pm_state) /* * Disable all GPE and clear their status bits before interrupts are * enabled. Some GPEs (like wakeup GPEs) have no handlers and this can - * prevent them from producing spurious interrups. + * prevent them from producing spurious interrupts. * * acpi_leave_sleep_state() will reenable specific GPEs later. * diff --git a/drivers/acpi/sleep.h b/drivers/acpi/sleep.h index d960a238be4e..9c3cb109c5d2 100644 --- a/drivers/acpi/sleep.h +++ b/drivers/acpi/sleep.h @@ -17,10 +17,7 @@ static inline acpi_status acpi_set_waking_vector(u32 wakeup_address) extern int acpi_s2idle_begin(void); extern int acpi_s2idle_prepare(void); -extern int acpi_s2idle_prepare_late(void); -extern void acpi_s2idle_check(void); extern bool acpi_s2idle_wake(void); -extern void acpi_s2idle_restore_early(void); extern void acpi_s2idle_restore(void); extern void acpi_s2idle_end(void); diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index 57fc8bc56166..4286e4af1092 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -408,7 +408,7 @@ static const char table_sigs[][ACPI_NAMESEG_SIZE] __nonstring_array __initconst ACPI_SIG_PSDT, ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT, ACPI_SIG_IORT, ACPI_SIG_NFIT, ACPI_SIG_HMAT, ACPI_SIG_PPTT, ACPI_SIG_NHLT, ACPI_SIG_AEST, ACPI_SIG_CEDT, ACPI_SIG_AGDI, - ACPI_SIG_NBFT, ACPI_SIG_SWFT}; + ACPI_SIG_NBFT, ACPI_SIG_SWFT, ACPI_SIG_MPAM}; #define ACPI_HEADER_SIZE sizeof(struct acpi_table_header) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 8537395b417b..a511f9ea0267 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -1060,7 +1060,8 @@ static int __init acpi_thermal_init(void) } acpi_thermal_pm_queue = alloc_workqueue("acpi_thermal_pm", - WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_PERCPU, + 0); if (!acpi_thermal_pm_queue) return -ENODEV; diff --git a/drivers/acpi/x86/lpss.c b/drivers/acpi/x86/lpss.c index 6daa6372f980..1dcb80ab0d23 100644 --- a/drivers/acpi/x86/lpss.c +++ b/drivers/acpi/x86/lpss.c @@ -181,7 +181,7 @@ static void byt_i2c_setup(struct lpss_private_data *pdata) acpi_status status; u64 uid; - /* Expected to always be successfull, but better safe then sorry */ + /* Expected to always be successful, but better safe then sorry */ if (!acpi_dev_uid_to_integer(pdata->adev, &uid) && uid) { /* Detect I2C bus shared with PUNIT and ignore its d3 status */ status = acpi_evaluate_integer(handle, "_SEM", NULL, &shared_host); diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index dd0b40b9bbe8..6d4d06236f61 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -299,34 +299,13 @@ free_acpi_buffer: 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; + if (IS_ERR_OR_NULL(lpi_constraints_table)) + return; + for_each_lpi_constraint(entry) { struct acpi_device *adev = acpi_fetch_acpi_dev(entry->handle); @@ -508,11 +487,6 @@ static int lps0_device_attach(struct acpi_device *adev, lps0_device_handle = adev->handle; - if (acpi_s2idle_vendor_amd()) - lpi_device_get_constraints_amd(); - else - lpi_device_get_constraints(); - /* * Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in * the FADT and the default suspend mode was not set from the command @@ -539,7 +513,26 @@ static struct acpi_scan_handler lps0_handler = { .attach = lps0_device_attach, }; -int acpi_s2idle_prepare_late(void) +static int acpi_s2idle_begin_lps0(void) +{ + if (pm_debug_messages_on && !lpi_constraints_table) { + if (acpi_s2idle_vendor_amd()) + lpi_device_get_constraints_amd(); + else + lpi_device_get_constraints(); + + /* + * Try to retrieve the constraints only once because failures + * to do so usually are sticky. + */ + if (!lpi_constraints_table) + lpi_constraints_table = ERR_PTR(-ENODATA); + } + + return acpi_s2idle_begin(); +} + +static int acpi_s2idle_prepare_late_lps0(void) { struct acpi_s2idle_dev_ops *handler; @@ -585,7 +578,7 @@ int acpi_s2idle_prepare_late(void) return 0; } -void acpi_s2idle_check(void) +static void acpi_s2idle_check_lps0(void) { struct acpi_s2idle_dev_ops *handler; @@ -598,7 +591,7 @@ void acpi_s2idle_check(void) } } -void acpi_s2idle_restore_early(void) +static void acpi_s2idle_restore_early_lps0(void) { struct acpi_s2idle_dev_ops *handler; @@ -636,12 +629,12 @@ void acpi_s2idle_restore_early(void) } static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { - .begin = acpi_s2idle_begin, + .begin = acpi_s2idle_begin_lps0, .prepare = acpi_s2idle_prepare, - .prepare_late = acpi_s2idle_prepare_late, - .check = acpi_s2idle_check, + .prepare_late = acpi_s2idle_prepare_late_lps0, + .check = acpi_s2idle_check_lps0, .wake = acpi_s2idle_wake, - .restore_early = acpi_s2idle_restore_early, + .restore_early = acpi_s2idle_restore_early_lps0, .restore = acpi_s2idle_restore, .end = acpi_s2idle_end, }; |
