Merge branch 'thermal-intel' into linux-next * thermal-intel: thermal: intel: powerclamp: Do not log an error on unsupported CPUs
diff --git a/Documentation/ABI/testing/sysfs-platform_profile b/Documentation/ABI/obsolete/sysfs-platform_profile similarity index 70% rename from Documentation/ABI/testing/sysfs-platform_profile rename to Documentation/ABI/obsolete/sysfs-platform_profile index 125324a..c001c86 100644 --- a/Documentation/ABI/testing/sysfs-platform_profile +++ b/Documentation/ABI/obsolete/sysfs-platform_profile
@@ -2,6 +2,12 @@ Date: October 2020 Contact: Hans de Goede <hdegoede@redhat.com> Description: This file contains a space-separated list of profiles supported for this device. + Available with CONFIG_PLATFORM_PROFILE_LEGACY on active ACPI + systems for backwards compatibility. New userspace should use + /sys/class/platform-profile/platform-profile-X/choices instead. + This interface is deprecated, with removal intended after the + 2028 LTS kernel once userspace migration is complete. Accessing + either legacy attribute emits a one-time deprecation notice. Drivers must use the following standard profile-names: @@ -27,6 +33,12 @@ device. Writing this file with one of the strings from platform_profile_choices changes the profile to the new value. + This legacy interface requires CONFIG_PLATFORM_PROFILE_LEGACY. + Individual class devices remain available without that option. + Use /sys/class/platform-profile/platform-profile-X/profile + instead. This interface is deprecated, with removal intended + after the 2028 LTS kernel once userspace migration is complete. + This file can be monitored for changes by polling for POLLPRI, POLLPRI will be signalled on any changes, independent of those changes coming from a userspace write; or coming from another
diff --git a/Documentation/ABI/testing/sysfs-class-platform-profile b/Documentation/ABI/testing/sysfs-class-platform-profile index fcab268..75e0f6e 100644 --- a/Documentation/ABI/testing/sysfs-class-platform-profile +++ b/Documentation/ABI/testing/sysfs-class-platform-profile
@@ -35,7 +35,7 @@ KernelVersion: 6.14 Description: Reading this file gives the current selected profile for this device. Writing this file with one of the strings from - platform_profile_choices changes the profile to the new value. + choices changes the profile to the new value. This file can be monitored for changes by polling for POLLPRI, POLLPRI will be signaled on any changes, independent of those
diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 545687e..d219fc2 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
@@ -350,6 +350,37 @@ This file is only present if the cppc-cpufreq driver is in use. +What: /sys/devices/system/cpu/cpuX/cpufreq/ospm_nominal_freq +Date: October 2026 +Contact: linux-pm@vger.kernel.org +Description: OSPM Nominal Performance (kHz) + + OSPM uses this attribute to request a nominal performance level + lower than the platform-reported nominal. The platform treats + performance above this level as boost and below as throttle. + + Write a value in kHz, between the frequencies corresponding to + Lowest Performance and Nominal Performance. + + While boost is disabled, cpuinfo_max_freq is set to this value + and scaling_max_freq cannot go above it. While boost is + enabled, cpuinfo_max_freq is the Highest Performance frequency. + + The register cannot be read back, so a read returns the last + value written, rounded to a performance level. It returns + "<unset>" before the first write. Reads and writes fail with + -EOPNOTSUPP if the platform does not implement the register. + + Note that tasks may be migrated from one CPU to another by the + scheduler's load-balancing algorithm, and if different OSPM + Nominal Performance values are set for those CPUs (through + different cpufreq policies), that may lead to undesirable + outcomes. To avoid such issues it is better to set the same + value across all policies, or to pin every task potentially + sensitive to it to a specific CPU. + + This file is only present if the cppc-cpufreq driver is in use. + What: /sys/devices/system/cpu/cpu*/cache/index3/cache_disable_{0,1} Date: August 2008 KernelVersion: 2.6.27
diff --git a/Documentation/ABI/testing/sysfs-firmware-acpi b/Documentation/ABI/testing/sysfs-firmware-acpi index fa33dda..5fb50a7 100644 --- a/Documentation/ABI/testing/sysfs-firmware-acpi +++ b/Documentation/ABI/testing/sysfs-firmware-acpi
@@ -275,3 +275,15 @@ remote_region_id: ID associated with access by agents non-local to this range of addresses. + +What: /sys/firmware/acpi/tables/data/BDAT +Date: October 2026 +Contact: Jamie Nguyen <jamien@nvidia.com> +Description: + The BIOS data structure referenced by the ACPI BDAT (BIOS Data + ACPI Table). It starts with a "BDATHEAD" header and holds data + published by firmware, such as memory training results. The + file size is the structure size given in that header. + + The format is defined by the BDAT Interface Specification, + published by the UEFI Forum. The file is readable by root only.
diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 40a5c23..f4723ad 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst
@@ -222,11 +222,12 @@ I/O operations as needed. The runtime PM status of the device is then 'active'. - * If the resume callback returns an error code, the PM core regards this as a - fatal error and will refuse to run the helper functions described in - `Section 4`_ for the device, until its status is directly set to either - 'active', or 'suspended' (by means of special helper functions provided by - the PM core for this purpose). + * If the resume callback returns an error code, the PM core sets the + device's runtime PM status back to 'suspended' and returns the error + code from synchronous resume helpers. Unlike most suspend callback + errors, failures in the resume callback are never considered fatal, + so the helper functions described in `Section 4`_ are not blocked and + a subsequent resume request can run the resume callback again. The idle callback (a subsystem-level one, if present, or the driver one) is executed by the PM core whenever the device appears to be idle, which is @@ -250,9 +251,9 @@ To prevent this suspension (for example, if the callback routine has scheduled a delayed suspend or determined the device cannot be idle), the routine must return a non-zero value (typically -EBUSY or -EAGAIN). Unlike -->runtime_suspend() and ->runtime_resume(), the PM core does not treat negative -return codes from ->runtime_idle() as a fatal device error; any non-zero value -simply stops the PM core from suspending the device. +->runtime_suspend(), the PM core does not treat negative return codes from +->runtime_idle() as a fatal device error, and any non-zero value simply stops +the PM core from suspending the device. Core Guarantees and Synchronization Rules -----------------------------------------
diff --git a/Documentation/userspace-api/sysfs-platform_profile.rst b/Documentation/userspace-api/sysfs-platform_profile.rst index 6613e18..a3fc1ee 100644 --- a/Documentation/userspace-api/sysfs-platform_profile.rst +++ b/Documentation/userspace-api/sysfs-platform_profile.rst
@@ -1,6 +1,8 @@ -===================================================================== -Platform Profile Selection (e.g. /sys/firmware/acpi/platform_profile) -===================================================================== +.. SPDX-License-Identifier: GPL-2.0 + +========================== +Platform Profile Selection +========================== On modern systems the platform performance, temperature, fan and other hardware related characteristics are often dynamically configurable. The @@ -15,6 +17,30 @@ The purpose of the platform_profile attribute is to offer a generic sysfs API for selecting the platform profile of these automatic mechanisms. +The framework is independent of the firmware interface used by a provider. +Drivers can implement profile selection using ACPI, an embedded controller, +or another platform-specific mechanism. The core does not discover providers +or interpret firmware descriptions. + +The firmware-independent userspace interface is available under +``/sys/class/platform-profile/platform-profile-X/``, with ``name``, +``choices`` and ``profile`` attributes for each registered provider. + +On ACPI systems with ``CONFIG_PLATFORM_PROFILE_LEGACY`` enabled, +``/sys/firmware/acpi/platform_profile_choices`` and +``/sys/firmware/acpi/platform_profile`` remain available as compatibility +interfaces for aggregate profile selection across registered providers. +New userspace should select profiles through the individual provider class +devices. These remain available when the legacy interface is disabled and +on non-ACPI systems. Kernel profile cycling also remains available without +the legacy interface. + +The legacy aggregate interface is deprecated. Removal is intended after the +2028 LTS kernel, once userspace has migrated to released software using the +class devices. Compatibility remains enabled by default during the migration; +the date alone does not trigger removal. Accessing either legacy attribute +emits a one-time kernel notice directing userspace to the class interface. + Note that this API is only for selecting the platform profile, it is NOT a goal of this API to allow monitoring the resulting performance characteristics. Monitoring performance is best done with device/vendor @@ -52,13 +78,14 @@ ======================= When multiple drivers on a system advertise a platform profile handler, the platform profile handler core will only advertise the profiles that are -common between all drivers to the ``/sys/firmware/acpi`` interfaces. +common between all drivers to the ``/sys/firmware/acpi`` interfaces, when +ACPI is active and ``CONFIG_PLATFORM_PROFILE_LEGACY`` is enabled. This is to ensure there is no ambiguity on what the profile names mean when all handlers don't support a profile. -Individual drivers will register a 'platform_profile' class device that has -similar semantics as the ``/sys/firmware/acpi/platform_profile`` interface. +Individual drivers register a ``platform-profile`` class device with +``name``, ``choices`` and ``profile`` attributes. To discover which driver is associated with a platform profile handler the user can read the ``name`` attribute of the class device. @@ -71,10 +98,14 @@ This will allow users to set different profiles for different drivers on the same system. If the selected profile by individual drivers differs the -platform profile handler core will display the profile 'custom' to indicate -that the profiles are not the same. +ACPI aggregate interface will display the profile 'custom' to indicate that +the profiles are not the same. -While the ``platform_profile`` attribute has the value ``custom``, writing a -common profile from ``platform_profile_choices`` to the platform_profile -attribute of the platform profile handler core will set the profile for all -drivers. +While the ACPI ``platform_profile`` attribute has the value ``custom``, +writing a common profile from ``platform_profile_choices`` to it will set +the profile for all drivers. ``custom`` cannot be written to this aggregate +interface. + +Aggregate profile changes are applied to providers sequentially. If a provider +returns an error, providers updated earlier are not rolled back. Userspace can +read the per-provider interfaces to determine their resulting state.
diff --git a/MAINTAINERS b/MAINTAINERS index 222b7fc..48cde87 100644 --- a/MAINTAINERS +++ b/MAINTAINERS
@@ -283,12 +283,16 @@ Q: https://patchwork.kernel.org/project/linux-acpi/list/ B: https://bugzilla.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm +F: Documentation/ABI/obsolete/sysfs-platform_profile F: Documentation/ABI/testing/configfs-acpi F: Documentation/ABI/testing/sysfs-bus-acpi +F: Documentation/ABI/testing/sysfs-class-platform-profile F: Documentation/firmware-guide/acpi/ +F: Documentation/userspace-api/sysfs-platform_profile.rst F: arch/x86/kernel/acpi/ F: arch/x86/pci/acpi.c F: drivers/acpi/ +F: drivers/platform/profile/ F: drivers/pci/*/*acpi* F: drivers/pci/*acpi* F: drivers/pnp/pnpacpi/ @@ -296,6 +300,7 @@ F: include/linux/acpi.h F: include/linux/fwnode.h F: include/linux/fw_table.h +F: include/linux/platform_profile.h F: lib/fw_table.c F: rust/kernel/acpi.rs F: tools/power/acpi/
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index fa3eb52..2f20616 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig
@@ -351,9 +351,6 @@ To compile this driver as a module, choose M here: the module will be called thermal. -config ACPI_PLATFORM_PROFILE - tristate - config ACPI_CUSTOM_DSDT_FILE string "Custom DSDT Table file to include" default ""
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 91584a3..4cf9500 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile
@@ -91,7 +91,6 @@ obj-$(CONFIG_ACPI) += container.o obj-$(CONFIG_ACPI_THERMAL_LIB) += thermal_lib.o obj-$(CONFIG_ACPI_THERMAL) += thermal.o -obj-$(CONFIG_ACPI_PLATFORM_PROFILE) += platform_profile.o obj-$(CONFIG_ACPI_NFIT) += nfit/ obj-$(CONFIG_ACPI_NHLT) += nhlt.o obj-$(CONFIG_ACPI_NUMA) += numa/
diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 14a06c9..a5826de 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c
@@ -158,7 +158,7 @@ static int acpi_ac_battery_notify(struct notifier_block *nb, return NOTIFY_OK; } -static int __init thinkpad_e530_quirk(const struct dmi_system_id *d) +static int __init ac_sleep_quirk(const struct dmi_system_id *d) { ac_sleep_before_get_state_ms = 1000; return 0; @@ -173,6 +173,18 @@ static int __init ac_only_quirk(const struct dmi_system_id *d) /* Please keep this list alphabetically sorted */ static const struct dmi_system_id ac_dmi_table[] __initconst = { { + /* + * ASUS ROG Flow X13 GV301Q notifies AC on a plug-in about 400ms + * before _PSR reports it online. Seen on the GV301QH, and the + * GV301QE runs the same EC handler + */ + .callback = ac_sleep_quirk, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "ROG Flow X13 GV301Q"), + }, + }, + { /* Kodlix GK45 returning incorrect state */ .callback = ac_only_quirk, .matches = { @@ -181,7 +193,7 @@ static const struct dmi_system_id ac_dmi_table[] __initconst = { }, { /* Lenovo Thinkpad e530, see comment in acpi_ac_notify() */ - .callback = thinkpad_e530_quirk, + .callback = ac_sleep_quirk, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"),
diff --git a/drivers/acpi/acpi_configfs.c b/drivers/acpi/acpi_configfs.c index 6071699..89ef17d 100644 --- a/drivers/acpi/acpi_configfs.c +++ b/drivers/acpi/acpi_configfs.c
@@ -199,8 +199,21 @@ static struct configfs_attribute *acpi_table_attrs[] = { NULL, }; +static void acpi_table_release(struct config_item *cfg) +{ + struct acpi_table *table = container_of(cfg, struct acpi_table, cfg); + + /* ACPICA retains the table buffer for possible reload after unload. */ + kfree(table); +} + +static const struct configfs_item_operations acpi_table_item_ops = { + .release = acpi_table_release, +}; + static const struct config_item_type acpi_table_type = { .ct_owner = THIS_MODULE, + .ct_item_ops = &acpi_table_item_ops, .ct_bin_attrs = acpi_table_bin_attrs, .ct_attrs = acpi_table_attrs, }; @@ -224,7 +237,8 @@ static void acpi_table_drop_item(struct config_group *group, struct acpi_table *table = container_of(cfg, struct acpi_table, cfg); pr_debug("Host-directed Dynamic ACPI Table Unload\n"); - acpi_unload_table(table->index); + if (table->header) + acpi_unload_table(table->index); config_item_put(cfg); }
diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index 5fbab54..3179d04 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c
@@ -903,6 +903,13 @@ int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, continue; } + if (obj->buffer.length < sizeof(*reg)) { + acpi_handle_info(handle, + FW_BUG "Skipping _CST C%d due to data corruption\n", + i); + continue; + } + reg = (struct acpi_power_register *)obj->buffer.pointer; obj = &element->package.elements[1];
diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 5fcb0be..71a1572 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c
@@ -622,9 +622,52 @@ static const struct attribute_group acpi_tad_group = { __ATTRIBUTE_GROUPS(acpi_tad); +static const struct acpi_device_id acpi_tad_ids[] = { + {"ACPI000E", 0}, + {} +}; + #ifdef CONFIG_RTC_CLASS /* RTC class device interface */ +static int acpi_tad_rtc_match(struct device *dev, const void *data) +{ + unsigned long long caps; + acpi_status status; + + if (!acpi_match_device(acpi_tad_ids, dev)) + return 0; + + status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_GCP", NULL, &caps); + + return ACPI_SUCCESS(status) && (caps & ACPI_TAD_RT); +} + +/** + * acpi_has_rtc - Check if the RTC should be driven via the ACPI TAD + * + * Check if there is a TAD implementing the real time methods that this driver + * will register an RTC class device for. + * + * Return: %true if the RTC should be driven via the ACPI TAD, %false otherwise. + */ +bool acpi_has_rtc(void) +{ + struct device *dev; + + if (acpi_disabled) + return false; + + dev = bus_find_device(&platform_bus_type, NULL, NULL, + acpi_tad_rtc_match); + if (!dev) + return false; + + put_device(dev); + return true; +} +EXPORT_SYMBOL_GPL(acpi_has_rtc); + static void acpi_tad_rt_to_tm(struct acpi_tad_rt *rt, struct rtc_time *tm) { tm->tm_year = rt->year - 1900; @@ -894,11 +937,6 @@ static int acpi_tad_probe(struct platform_device *pdev) return 0; } -static const struct acpi_device_id acpi_tad_ids[] = { - {"ACPI000E", 0}, - {} -}; - static struct platform_driver acpi_tad_driver = { .driver = { .name = "acpi-tad",
diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 2a28225..44af7ff 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c
@@ -533,6 +533,13 @@ acpi_video_bqc_value_to_level(struct acpi_video_device *device, * items with special meaning (see enum acpi_video_level_idx), * so we need to compensate for that by offsetting ourselves */ + if (device->brightness->count <= ACPI_VIDEO_FIRST_LEVEL) + return U64_MAX; + + if (bqc_value >= device->brightness->count - + ACPI_VIDEO_FIRST_LEVEL) + return U64_MAX; + if (device->brightness->flags._BCL_reversed) bqc_value = device->brightness->count - ACPI_VIDEO_FIRST_LEVEL - 1 - bqc_value; @@ -781,7 +788,7 @@ static int acpi_video_bqc_quirk(struct acpi_video_device *device, if (level != test_level) { /* buggy _BQC found, need to find out if it uses index */ - if (level < br->count) { + if (level < br->count - ACPI_VIDEO_FIRST_LEVEL) { if (br->flags._BCL_reversed) level = br->count - ACPI_VIDEO_FIRST_LEVEL - 1 - level; if (br->levels[level + ACPI_VIDEO_FIRST_LEVEL] == test_level)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 663dc2e6..5d888c8 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c
@@ -899,7 +899,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL"); static void ghes_log_hwerr(int sev, guid_t *sec_type) { - if (sev != CPER_SEV_RECOVERABLE) + if (sev != GHES_SEV_RECOVERABLE) return; if (guid_equal(sec_type, &CPER_SEC_PROC_ARM) ||
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 05a547b..11a9f9e 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c
@@ -463,6 +463,7 @@ static bool cpc_reg_is_writable(unsigned int reg_idx) case AUTO_SEL_ENABLE: case AUTO_ACT_WINDOW: case ENERGY_PERF: + case OSPM_NOMINAL_PERF: return true; default: return false; @@ -1192,7 +1193,19 @@ static bool cpc_disable_new_sysmem_writer(struct cpc_desc *cpc_desc, pr_warn_once("CPU%d: ignoring optional _CPC register %u sharing CPU%d Performance Limited access unit\n", cpc_desc->cpu_id, reg_idx, status_cpu); + + /* + * Keep a write-only range reserved. Its readback is undefined, so the + * overlap checks must still reject another field that reads these bits + * or writes them back through read-modify-write. + */ + if (cpc_reg_is_write_only(cpc_desc, reg_idx)) { + reg->cpc_entry.read_unsupported = true; + reg->cpc_entry.write_unsupported = true; + return false; + } cpc_disable_reg(cpc_desc, reg_idx); + return true; } @@ -3230,6 +3243,49 @@ int cppc_set_epp(int cpu, u64 epp_val) EXPORT_SYMBOL_GPL(cppc_set_epp); /** + * cppc_set_ospm_nominal_perf() - Write OSPM Nominal Performance register. + * @cpu: CPU on which to write register. + * @ospm_nominal_perf: Value to write to the OSPM Nominal Performance register. + * + * OSPM Nominal Performance conveys the desired nominal performance level + * at which the platform may run. Per ACPI 6.6, s8.4.6.1.2.6, the value + * must lie within [Lowest Performance, Nominal Performance] and may be + * set independently of Minimum, Maximum and Desired performance. The + * caller is responsible for validating the range. + * + * Return: 0 on success, -EOPNOTSUPP if the platform does not implement the + * register as writable, or another negative error code. + */ +int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf) +{ + return cppc_set_reg_val(cpu, OSPM_NOMINAL_PERF, ospm_nominal_perf); +} +EXPORT_SYMBOL_GPL(cppc_set_ospm_nominal_perf); + +/** + * cppc_ospm_nominal_perf_supported() - Check OSPM Nominal Performance support. + * @cpu: CPU to query. + * + * The OSPM Nominal Performance register is write-only, so its value + * cannot be read back. This only reports whether the platform implements + * it as a writable register. + * + * Context: Process context. + * Return: true if the register is supported, false otherwise. + */ +bool cppc_ospm_nominal_perf_supported(int cpu) +{ + struct cpc_desc *cpc_desc; + + guard(mutex)(&cpc_desc_lock); + cpc_desc = per_cpu(cpc_desc_ptr, cpu); + + return cpc_desc && + cpc_is_writable(&cpc_desc->cpc_regs[OSPM_NOMINAL_PERF]); +} +EXPORT_SYMBOL_GPL(cppc_ospm_nominal_perf_supported); + +/** * cppc_get_auto_act_window() - Read autonomous activity window register. * @cpu: CPU from which to read register. * @auto_act_window: Return address. @@ -3301,23 +3357,11 @@ EXPORT_SYMBOL_GPL(cppc_set_auto_act_window); /** * cppc_get_auto_sel() - Read autonomous selection register. * @cpu: CPU from which to read register. - * @enable: Return address. + * @enable: Return address, set to 0 or 1. */ -int cppc_get_auto_sel(int cpu, bool *enable) +int cppc_get_auto_sel(int cpu, u64 *enable) { - u64 auto_sel; - int ret; - - if (enable == NULL) - return -EINVAL; - - ret = cppc_get_reg_val(cpu, AUTO_SEL_ENABLE, &auto_sel); - if (ret) - return ret; - - *enable = (bool)auto_sel; - - return 0; + return cppc_get_reg_val(cpu, AUTO_SEL_ENABLE, enable); } EXPORT_SYMBOL_GPL(cppc_get_auto_sel); @@ -3348,7 +3392,7 @@ EXPORT_SYMBOL_GPL(cppc_auto_sel_is_immutable); * @cpu : CPU to which to write register. * @enable : the desired value of autonomous selection resiter to be updated. */ -int cppc_set_auto_sel(int cpu, bool enable) +int cppc_set_auto_sel(int cpu, u64 enable) { return cppc_set_reg_val(cpu, AUTO_SEL_ENABLE, enable); } @@ -3468,11 +3512,214 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) } EXPORT_SYMBOL_GPL(cppc_get_perf); +enum cppc_perf_update { + CPPC_UPDATE_DESIRED = BIT(0), + CPPC_UPDATE_MIN = BIT(1), + CPPC_UPDATE_MAX = BIT(2), +}; + +/* The caller holds pcc_lock for write if either limit is in PCC. */ +static int cppc_read_perf_limits(int cpu, u32 *min_perf, u32 *max_perf) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF]; + struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF]; + u64 min = 0, max = U32_MAX; + int ret; + + if (!cpc_is_readable(min_reg) || !cpc_is_readable(max_reg)) + return -EOPNOTSUPP; + + /* Firmware may have reset the controls, so read the live limits. */ + if (CPC_IN_PCC(min_reg) || CPC_IN_PCC(max_reg)) { + ret = send_pcc_cmd(per_cpu(cpu_pcc_subspace_idx, cpu), CMD_READ); + if (ret) + return ret; + } + + /* An absent optional limit leaves that side of the interval open. */ + if (CPC_SUPPORTED(min_reg)) { + ret = cpc_read(cpu, min_reg, &min); + if (ret) + return ret; + } + if (CPC_SUPPORTED(max_reg)) { + ret = cpc_read(cpu, max_reg, &max); + if (ret) + return ret; + } + if (min > U32_MAX || max > U32_MAX) + return -EFAULT; + if (min > max) + pr_debug("CPU:%d live MIN_PERF %llu above MAX_PERF %llu\n", + cpu, min, max); + + *min_perf = min; + *max_perf = max; + return 0; +} + +static bool cppc_perf_updates_in_pcc(int cpu, unsigned int updates) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + + return ((updates & CPPC_UPDATE_DESIRED) && + CPC_IN_PCC(&cpc_desc->cpc_regs[DESIRED_PERF])) || + ((updates & CPPC_UPDATE_MIN) && + CPC_IN_PCC(&cpc_desc->cpc_regs[MIN_PERF])) || + ((updates & CPPC_UPDATE_MAX) && + CPC_IN_PCC(&cpc_desc->cpc_regs[MAX_PERF])); +} + +/* The caller holds pcc_lock for write if an updated control is in PCC. */ +static int cppc_write_perf_controls(int cpu, + struct cppc_perf_ctrls *perf_ctrls, + unsigned int updates, bool sync_desired) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + struct cpc_register_resource *desired_reg = + &cpc_desc->cpc_regs[DESIRED_PERF]; + struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF]; + struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF]; + struct cppc_pcc_data *pcc_ss_data; + int pcc_ss_id, ret; + bool pcc_update = false; + + /* Direct writes precede the PCC command in each ordered phase. */ + if ((updates & CPPC_UPDATE_DESIRED) && !CPC_IN_PCC(desired_reg)) { + ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (ret) + return ret; + } + if ((updates & CPPC_UPDATE_MIN) && !CPC_IN_PCC(min_reg)) { + ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf); + if (ret) + return ret; + } + if ((updates & CPPC_UPDATE_MAX) && !CPC_IN_PCC(max_reg)) { + ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf); + if (ret) + return ret; + } + + /* Keep Desired Performance valid in every intermediate PCC command. */ + if (((updates & CPPC_UPDATE_DESIRED) || sync_desired) && + CPC_IN_PCC(desired_reg)) { + ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (ret) + return ret; + pcc_update = true; + } + if ((updates & CPPC_UPDATE_MIN) && CPC_IN_PCC(min_reg)) { + ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf); + if (ret) + return ret; + pcc_update = true; + } + if ((updates & CPPC_UPDATE_MAX) && CPC_IN_PCC(max_reg)) { + ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf); + if (ret) + return ret; + pcc_update = true; + } + if (!pcc_update) + return 0; + + pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); + pcc_ss_data = pcc_data[pcc_ss_id]; + WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true); + cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; + cpc_desc->write_cmd_status = 0; + return send_pcc_cmd(pcc_ss_id, CMD_WRITE); +} + +/* + * Move from the live interval to the requested interval without exposing an + * invalid control tuple: widen, update Desired Performance, then narrow. + */ +static int cppc_write_ordered_perf(int cpu, + struct cppc_perf_ctrls *perf_ctrls, + bool desired_update, bool min_update, + bool max_update) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + struct cpc_register_resource *desired_reg = + &cpc_desc->cpc_regs[DESIRED_PERF]; + struct cppc_perf_ctrls ctrls = *perf_ctrls; + unsigned int widen = 0, narrow = 0; + u32 current_min, current_max; + u32 target_min, target_max; + bool desired_done = false; + bool sync_desired; + int ret; + + ret = cppc_read_perf_limits(cpu, ¤t_min, ¤t_max); + if (ret) + return ret; + + target_min = min_update ? perf_ctrls->min_perf : current_min; + target_max = max_update ? perf_ctrls->max_perf : current_max; + + /* + * Check only the new interval. If the live one is inverted, this + * update fixes it. + */ + if (target_min > target_max) + return -EINVAL; + + /* Callers can round Desired just outside the new interval. */ + if (desired_update && ctrls.desired_perf) + ctrls.desired_perf = clamp(ctrls.desired_perf, target_min, + target_max); + + if (min_update) { + if (perf_ctrls->min_perf < current_min) + widen |= CPPC_UPDATE_MIN; + else + narrow |= CPPC_UPDATE_MIN; + } + if (max_update) { + if (perf_ctrls->max_perf > current_max) + widen |= CPPC_UPDATE_MAX; + else + narrow |= CPPC_UPDATE_MAX; + } + + if (widen) { + sync_desired = desired_update && CPC_IN_PCC(desired_reg) && + cppc_perf_updates_in_pcc(cpu, widen); + ret = cppc_write_perf_controls(cpu, &ctrls, widen, + sync_desired); + if (ret) + return ret; + desired_done = sync_desired; + } + + if (desired_update && !desired_done) { + ret = cppc_write_perf_controls(cpu, &ctrls, + CPPC_UPDATE_DESIRED, false); + if (ret) + return ret; + } + + if (!narrow) + return 0; + + sync_desired = desired_update && CPC_IN_PCC(desired_reg) && + cppc_perf_updates_in_pcc(cpu, narrow); + return cppc_write_perf_controls(cpu, &ctrls, narrow, sync_desired); +} + /** * cppc_set_perf - Set a CPU's performance controls. * @cpu: CPU for which to set performance controls. * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h * + * Callers must serialize updates to the same CPU's performance controls. + * Limit updates preserve MIN <= MAX, including intermediate states on + * directly accessed registers. An error may leave an earlier write applied. + * A nonzero Desired is clamped to the new [MIN, MAX] when a limit is updated. + * * Return: 0 for success, -ERRNO otherwise. */ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) @@ -3484,6 +3731,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) bool desired_update, min_update, max_update; bool desired_pcc, min_pcc, max_pcc, pcc_update; bool pcc_layout, direct_layout, mixed_layout; + u32 desired_perf; int ret = 0; if (!cpc_desc) { @@ -3499,10 +3747,21 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) (perf_ctrls->min_perf || perf_ctrls->min_perf_valid); max_update = cpc_is_writable(max_perf_reg) && perf_ctrls->max_perf; + + if (min_update && max_update && + perf_ctrls->min_perf > perf_ctrls->max_perf) + return -EINVAL; + + desired_perf = perf_ctrls->desired_perf; + if (desired_perf && min_update && max_update) + desired_perf = clamp(desired_perf, perf_ctrls->min_perf, + perf_ctrls->max_perf); + desired_pcc = desired_update && CPC_IN_PCC(desired_reg); min_pcc = min_update && CPC_IN_PCC(min_perf_reg); max_pcc = max_update && CPC_IN_PCC(max_perf_reg); pcc_update = desired_pcc || min_pcc || max_pcc; + pcc_layout = (cpc_is_writable(desired_reg) && CPC_IN_PCC(desired_reg)) || (cpc_is_writable(min_perf_reg) && CPC_IN_PCC(min_perf_reg)) || (cpc_is_writable(max_perf_reg) && CPC_IN_PCC(max_perf_reg)); @@ -3528,9 +3787,10 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) * A mixed layout cannot batch fallible direct writes safely: another * CPU's staged PCC values may no longer match if a direct write fails. * Serialize the complete mixed transaction and drain an older batch - * before changing a direct control. + * before changing a direct control. A single PCC limit update also + * needs the exclusive lock to read and check the unchanged limit. */ - if (mixed_layout) { + if (mixed_layout || (pcc_update && min_update != max_update)) { down_write(&pcc_ss_data->pcc_lock); if (pcc_ss_data->pending_pcc_write_cmd) { ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); @@ -3544,50 +3804,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) goto out_mixed_unlock; } - if (desired_update && !desired_pcc) { - ret = cpc_write(cpu, desired_reg, - perf_ctrls->desired_perf); - if (ret) - goto out_mixed_unlock; - } - if (min_update && !min_pcc) { - ret = cpc_write(cpu, min_perf_reg, - perf_ctrls->min_perf); - if (ret) - goto out_mixed_unlock; - } - if (max_update && !max_pcc) { - ret = cpc_write(cpu, max_perf_reg, - perf_ctrls->max_perf); - if (ret) - goto out_mixed_unlock; - } - - if (desired_pcc) { - ret = cpc_write(cpu, desired_reg, - perf_ctrls->desired_perf); - if (ret) - goto out_mixed_unlock; - } - if (min_pcc) { - ret = cpc_write(cpu, min_perf_reg, - perf_ctrls->min_perf); - if (ret) - goto out_mixed_unlock; - } - if (max_pcc) { - ret = cpc_write(cpu, max_perf_reg, - perf_ctrls->max_perf); - if (ret) - goto out_mixed_unlock; - } - - if (pcc_update) { - WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true); - cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; - cpc_desc->write_cmd_status = 0; - ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); - } + if (min_update || max_update) + ret = cppc_write_ordered_perf(cpu, perf_ctrls, + desired_update, + min_update, max_update); + else if (desired_update) + ret = cppc_write_perf_controls(cpu, perf_ctrls, + CPPC_UPDATE_DESIRED, + false); out_mixed_unlock: up_write(&pcc_ss_data->pcc_lock); @@ -3596,22 +3820,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) /* A request without PCC updates has no payload to coordinate. */ if (!pcc_update) { - if (desired_update) { - ret = cpc_write(cpu, desired_reg, - perf_ctrls->desired_perf); - if (ret) - return ret; - } - if (min_update) { - ret = cpc_write(cpu, min_perf_reg, - perf_ctrls->min_perf); - if (ret) - return ret; - } - if (max_update) - ret = cpc_write(cpu, max_perf_reg, - perf_ctrls->max_perf); - return ret; + if (min_update || max_update) + return cppc_write_ordered_perf(cpu, perf_ctrls, + desired_update, + min_update, max_update); + if (desired_update) + return cpc_write(cpu, desired_reg, + perf_ctrls->desired_perf); + return 0; } down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */ @@ -3629,7 +3845,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) * achieve that goal here. */ if (desired_pcc) { - ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + ret = cpc_write(cpu, desired_reg, desired_perf); if (ret) goto out_pcc_read_unlock; }
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index a89f102..2da0095 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c
@@ -2009,6 +2009,16 @@ static const struct dmi_system_id ec_dmi_table[] __initconst = { }, { /* + * Lenovo Yoga S740-14IIL + */ + .callback = ec_honor_dsdt_gpe, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga S740-14IIL"), + }, + }, + { + /* * Samsung hardware * https://bugzilla.kernel.org/show_bug.cgi?id=44161 */ @@ -2167,19 +2177,19 @@ static bool acpi_ec_work_in_progress(struct acpi_ec *ec) return ec->events_in_progress + ec->queries_in_progress > 0; } -bool acpi_ec_dispatch_gpe(void) +int acpi_ec_dispatch_gpe(void) { bool work_in_progress = false; if (!first_ec) - return acpi_any_gpe_status_set(U32_MAX); + return -ENODEV; /* * Report wakeup if the status bit is set for any enabled GPE other * than the EC one. */ if (acpi_any_gpe_status_set(first_ec->gpe)) - return true; + return 1; /* * Cancel the SCI wakeup and process all pending events in case there @@ -2207,7 +2217,7 @@ bool acpi_ec_dispatch_gpe(void) spin_unlock_irq(&first_ec->lock); if (!work_in_progress) - return false; + return 0; pm_pr_dbg("ACPI EC GPE dispatched\n"); @@ -2224,7 +2234,7 @@ bool acpi_ec_dispatch_gpe(void) spin_unlock_irq(&first_ec->lock); } while (work_in_progress && !pm_wakeup_pending()); - return false; + return 0; } #endif /* CONFIG_PM_SLEEP */
diff --git a/drivers/acpi/fan_attr.c b/drivers/acpi/fan_attr.c index 9b7fa52..6b3d91c 100644 --- a/drivers/acpi/fan_attr.c +++ b/drivers/acpi/fan_attr.c
@@ -15,6 +15,7 @@ #include "fan.h" MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Extra sysfs attributes for the ACPI fan driver"); static ssize_t show_state(struct device *dev, struct device_attribute *attr, char *buf) {
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 011da4e..472c383 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h
@@ -237,7 +237,7 @@ void acpi_ec_register_opregions(struct acpi_device *adev); #ifdef CONFIG_PM_SLEEP void acpi_ec_flush_work(void); -bool acpi_ec_dispatch_gpe(void); +int acpi_ec_dispatch_gpe(void); #endif #else @@ -257,9 +257,9 @@ static inline void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit static inline void acpi_ec_register_opregions(struct acpi_device *adev) {} static inline void acpi_ec_flush_work(void) {} -static inline bool acpi_ec_dispatch_gpe(void) +static inline int acpi_ec_dispatch_gpe(void) { - return false; + return -EOPNOTSUPP; } #endif
diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index a1a9c06..89c389a 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c
@@ -563,9 +563,14 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm) * Apple always return failure on _OSC calls when _OSI("Darwin") has * been called successfully. We know the feature set supported by the * platform, so avoid calling _OSC at all + * + * Assume control of the same features that would have been requested, + * PCIe PME included. Leaving PME out disables the PME service for + * every root port, and nothing takes over: a device that runtime + * suspends asserts PME# and is never resumed. */ if (x86_apple_machine) { - root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL; + root->osc_control_set = calculate_control(); decode_osc_control(root, "OS assumes control of", root->osc_control_set); return;
diff --git a/drivers/acpi/pfr_update.c b/drivers/acpi/pfr_update.c index 459c293..797956d 100644 --- a/drivers/acpi/pfr_update.c +++ b/drivers/acpi/pfr_update.c
@@ -16,9 +16,11 @@ #include <linux/file.h> #include <linux/fs.h> #include <linux/idr.h> +#include <linux/kref.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/slab.h> #include <linux/string.h> #include <linux/uaccess.h> #include <linux/uio.h> @@ -82,6 +84,8 @@ struct pfru_device { u32 rev_id, index; struct device *parent_dev; struct miscdevice miscdev; + struct kref kref; + bool removed; }; static DEFINE_IDA(pfru_ida); @@ -428,6 +432,9 @@ static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg) u32 rev; int ret; + if (READ_ONCE(pfru_dev->removed)) + return -ENODEV; + switch (cmd) { case PFRU_IOC_QUERY_CAP: ret = query_capability(&cap_hdr, pfru_dev); @@ -476,6 +483,9 @@ static ssize_t pfru_write(struct file *file, const char __user *buf, char *buf_ptr; int ret; + if (READ_ONCE(pfru_dev->removed)) + return -ENODEV; + ret = query_buffer(&buf_info, pfru_dev); if (ret) return ret; @@ -518,8 +528,39 @@ static ssize_t pfru_write(struct file *file, const char __user *buf, return ret ?: len; } +static void pfru_free(struct kref *kref) +{ + struct pfru_device *pfru_dev = + container_of(kref, struct pfru_device, kref); + + put_device(pfru_dev->parent_dev); + kfree(pfru_dev); +} + +static void pfru_put(void *data) +{ + struct pfru_device *pfru_dev = data; + + kref_put(&pfru_dev->kref, pfru_free); +} + +/* misc_open() holds misc_mtx here, and misc_deregister() takes it. */ +static int pfru_open(struct inode *inode, struct file *file) +{ + kref_get(&to_pfru_dev(file)->kref); + return 0; +} + +static int pfru_release(struct inode *inode, struct file *file) +{ + pfru_put(to_pfru_dev(file)); + return 0; +} + static const struct file_operations acpi_pfru_fops = { .owner = THIS_MODULE, + .open = pfru_open, + .release = pfru_release, .write = pfru_write, .unlocked_ioctl = pfru_ioctl, .llseek = noop_llseek, @@ -530,6 +571,7 @@ static void acpi_pfru_remove(struct platform_device *pdev) struct pfru_device *pfru_dev = platform_get_drvdata(pdev); misc_deregister(&pfru_dev->miscdev); + WRITE_ONCE(pfru_dev->removed, true); } static void pfru_put_idx(void *data) @@ -554,10 +596,16 @@ static int acpi_pfru_probe(struct platform_device *pdev) return -ENODEV; } - pfru_dev = devm_kzalloc_obj(&pdev->dev, *pfru_dev); + pfru_dev = kzalloc_obj(*pfru_dev); if (!pfru_dev) return -ENOMEM; + kref_init(&pfru_dev->kref); + pfru_dev->parent_dev = get_device(&pdev->dev); + ret = devm_add_action_or_reset(&pdev->dev, pfru_put, pfru_dev); + if (ret) + return ret; + ret = ida_alloc(&pfru_ida, GFP_KERNEL); if (ret < 0) return ret; @@ -568,7 +616,6 @@ static int acpi_pfru_probe(struct platform_device *pdev) return ret; pfru_dev->rev_id = PFRU_DEFAULT_REV_ID; - pfru_dev->parent_dev = &pdev->dev; pfru_dev->miscdev.minor = MISC_DYNAMIC_MINOR; pfru_dev->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index e113bcb..49768b1 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c
@@ -105,6 +105,10 @@ static const struct dmi_system_id processor_power_dmi_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), DMI_MATCH(DMI_PRODUCT_NAME,"L8400B series Notebook PC")}, (void *)1}, + { set_max_cstate, "HA0QING A520", { + DMI_MATCH(DMI_BOARD_VENDOR, "HA0QING"), + DMI_MATCH(DMI_BOARD_NAME, "A520") }, + (void *)1}, {}, };
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 8ee5a1f..f5e0dc8 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c
@@ -194,7 +194,8 @@ static bool acpi_add_nondev_subnodes(acpi_handle scope, link = &links->package.elements[i]; /* Only two elements allowed. */ - if (link->package.count != 2) + if (link->type != ACPI_TYPE_PACKAGE || + link->package.count != 2) continue; /* The first one (the key) must be a string. */ @@ -262,6 +263,9 @@ static bool acpi_enumerate_nondev_subnodes(acpi_handle scope, int i; /* Look for the ACPI data subnodes GUID. */ + if (desc->package.count % 2) + return false; + for (i = 0; i < desc->package.count; i += 2) { const union acpi_object *guid; union acpi_object *links; @@ -331,9 +335,10 @@ static bool acpi_properties_format_valid(const union acpi_object *properties) * Only two elements allowed, the first one must be a string and * the second one has to satisfy certain conditions. */ - if (property->package.count != 2 - || property->package.elements[0].type != ACPI_TYPE_STRING - || !acpi_property_value_ok(&property->package.elements[1])) + if (property->type != ACPI_TYPE_PACKAGE || + property->package.count != 2 || + property->package.elements[0].type != ACPI_TYPE_STRING || + !acpi_property_value_ok(&property->package.elements[1])) return false; } return true;
diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c index 56df459..0a944ba 100644 --- a/drivers/acpi/resource.c +++ b/drivers/acpi/resource.c
@@ -554,6 +554,12 @@ static const struct dmi_system_id irq1_edge_low_force_override[] = { }, }, { + /* MECHREVO Jiaolong17 Series GM7RG7M */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GM7RG7M"), + }, + }, + { /* XMG APEX 17 (M23) */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "GMxBGxx"),
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index f10bbf1..861739b 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c
@@ -55,6 +55,7 @@ struct acpi_battery { struct power_supply_desc bat_desc; struct acpi_sbs *sbs; unsigned long update_time; + struct mutex state_lock; /* serializes state refreshes */ char name[8]; char manufacturer_name[ACPI_SBS_BLOCK_MAX]; char device_name[ACPI_SBS_BLOCK_MAX]; @@ -355,6 +356,13 @@ static int acpi_battery_get_state(struct acpi_battery *battery) { int i, result = 0; + /* + * Callers that find the cache stale at the same time would each + * re-read the whole state over SMBus. Serialize them so that only + * the first one does; the others then find a fresh cache. + */ + guard(mutex)(&battery->state_lock); + if (battery->update_time && time_before(jiffies, battery->update_time + msecs_to_jiffies(cache_time))) @@ -367,9 +375,8 @@ static int acpi_battery_get_state(struct acpi_battery *battery) (u8 *)battery + state_readers[i].offset); if (result) - goto end; + break; } - end: battery->update_time = jiffies; return result; } @@ -530,6 +537,7 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) battery->id = id; battery->sbs = sbs; + mutex_init(&battery->state_lock); result = acpi_battery_read(battery); if (result) return result;
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 96b8b0d..9218264 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c
@@ -115,11 +115,7 @@ bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent) bool offline = true; char *envp[] = { "EVENT=offline", NULL }; - /* - * acpi_container_offline() calls this for all of the container's - * children under the container's physical_node_lock lock. - */ - mutex_lock_nested(&adev->physical_node_lock, SINGLE_DEPTH_NESTING); + mutex_lock(&adev->physical_node_lock); list_for_each_entry(pn, &adev->physical_node_list, node) if (device_supports_offline(pn->dev) && !dev_offline(pn->dev)) { @@ -134,6 +130,59 @@ bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent) return offline; } +static struct acpi_device_physical_node * +acpi_find_physical_node(struct acpi_device *adev, struct device *dev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->dev == dev) + return pn; + + return NULL; +} + +static struct device *acpi_next_offline_pending(struct acpi_device *adev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + /* + * Find the first physical node that is still to be offlined, clear its + * offline_pending flag and return its device with a reference held. + */ + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->offline_pending) { + pn->offline_pending = false; + return get_device(pn->dev); + } + + return NULL; +} + +static struct device *acpi_next_put_online(struct acpi_device *adev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + /* + * Find the first physical node that has been offlined by + * acpi_bus_offline(), clear its put_online flag and return its device + * with a reference held. + */ + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->put_online) { + pn->put_online = false; + return get_device(pn->dev); + } + + return NULL; +} + static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, void **ret_p) { @@ -141,6 +190,7 @@ static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, struct acpi_device_physical_node *pn; bool second_pass = (bool)data; acpi_status status = AE_OK; + struct device *dev; if (!device) return AE_OK; @@ -153,25 +203,43 @@ static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, mutex_lock(&device->physical_node_lock); list_for_each_entry(pn, &device->physical_node_list, node) { - int ret; - if (second_pass) { /* Skip devices offlined by the first pass. */ - if (pn->put_online) - continue; + pn->offline_pending = !pn->put_online; } else { + pn->offline_pending = true; pn->put_online = false; } - ret = device_offline(pn->dev); + } + + /* + * Drop physical_node_lock around device_offline(), because the latter + * acquires locks, like the CPU hotplug ones, that must not nest under + * it. + */ + while ((dev = acpi_next_offline_pending(device))) { + int ret; + + mutex_unlock(&device->physical_node_lock); + ret = device_offline(dev); + mutex_lock(&device->physical_node_lock); + if (ret >= 0) { - pn->put_online = !ret; + /* The device may have been unbound in the meantime. */ + pn = acpi_find_physical_node(device, dev); + if (pn) + pn->put_online = !ret; } else { - *ret_p = pn->dev; + *ret_p = &device->dev; if (second_pass) { + dev_warn(dev, "Offline failed.\n"); status = AE_ERROR; - break; } } + + put_device(dev); + if (ACPI_FAILURE(status)) + break; } mutex_unlock(&device->physical_node_lock); @@ -183,18 +251,19 @@ static acpi_status acpi_bus_online(acpi_handle handle, u32 lvl, void *data, void **ret_p) { struct acpi_device *device = acpi_fetch_acpi_dev(handle); - struct acpi_device_physical_node *pn; + struct device *dev; if (!device) return AE_OK; mutex_lock(&device->physical_node_lock); - list_for_each_entry(pn, &device->physical_node_list, node) - if (pn->put_online) { - device_online(pn->dev); - pn->put_online = false; - } + while ((dev = acpi_next_put_online(device))) { + mutex_unlock(&device->physical_node_lock); + device_online(dev); + mutex_lock(&device->physical_node_lock); + put_device(dev); + } mutex_unlock(&device->physical_node_lock); @@ -236,7 +305,6 @@ static int acpi_scan_try_to_offline(struct acpi_device *device) (void **)&errdev); if (errdev) { - dev_warn(errdev, "Offline failed.\n"); acpi_bus_online(handle, 0, NULL, NULL); acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX, acpi_bus_online,
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index a94bac4..641056e 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c
@@ -760,6 +760,8 @@ bool acpi_s2idle_wake(void) return pm_wakeup_pending(); while (pm_wakeup_pending()) { + int ret; + /* * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the * SCI has not triggered while suspended, so bail out (the @@ -788,11 +790,16 @@ bool acpi_s2idle_wake(void) /* * Check non-EC GPE wakeups and if there are none, cancel the - * SCI-related wakeup and dispatch the EC GPE. + * SCI-related wakeup and dispatch the EC GPE. If there is no + * EC to consult, check the GPEs armed for wakeup via _Ixx + * methods instead. */ - if (acpi_ec_dispatch_gpe()) { + ret = acpi_ec_dispatch_gpe(); + if (ret > 0) { pm_pr_dbg("ACPI non-EC GPE wakeup\n"); return true; + } else if (ret < 0 && acpi_x86_s2idle_check_ixx_gpes()) { + return true; } acpi_os_wait_events_complete();
diff --git a/drivers/acpi/sleep.h b/drivers/acpi/sleep.h index 9c3cb10..955e9c2 100644 --- a/drivers/acpi/sleep.h +++ b/drivers/acpi/sleep.h
@@ -20,6 +20,14 @@ extern int acpi_s2idle_prepare(void); extern bool acpi_s2idle_wake(void); extern void acpi_s2idle_restore(void); extern void acpi_s2idle_end(void); +#if defined(CONFIG_X86) && defined(CONFIG_SUSPEND) +extern bool acpi_x86_s2idle_check_ixx_gpes(void); +#else +static inline bool acpi_x86_s2idle_check_ixx_gpes(void) +{ + return acpi_any_gpe_status_set(U32_MAX); +} +#endif extern void acpi_s2idle_setup(void);
diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index a4534b3..49a820b 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c
@@ -454,14 +454,13 @@ static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr) struct acpi_table_bert *bert = th; if (bert->header.length < sizeof(struct acpi_table_bert) || - bert->region_length < sizeof(struct acpi_bert_region)) { - kfree(data_attr); + bert->region_length < sizeof(struct acpi_bert_region)) return -EINVAL; - } + data_attr->addr = bert->address; data_attr->attr.size = bert->region_length; - return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr); + return 0; } static int acpi_ccel_data_init(void *th, struct acpi_data_attr *data_attr) @@ -469,14 +468,62 @@ static int acpi_ccel_data_init(void *th, struct acpi_data_attr *data_attr) struct acpi_table_ccel *ccel = th; if (ccel->header.length < sizeof(struct acpi_table_ccel) || - !ccel->log_area_start_address || !ccel->log_area_minimum_length) { - kfree(data_attr); + !ccel->log_area_start_address || !ccel->log_area_minimum_length) return -EINVAL; - } + data_attr->addr = ccel->log_area_start_address; data_attr->attr.size = ccel->log_area_minimum_length; - return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr); + return 0; +} + +/* + * The BDAT table only holds the address of the BIOS data structure. Its + * size is in the structure's own header ("BDAT Interface Specification" + * v4.0, section 4). + */ +struct acpi_bdat_data_header { + char signature[8]; /* "BDATHEAD" */ + u32 size; /* whole structure, including this header */ + u16 crc16; + u16 reserved; + u16 primary_version; + u16 secondary_version; + u32 oem_offset; + u32 reserved1; + u32 reserved2; +} __packed; + +static int acpi_bdat_data_init(void *th, struct acpi_data_attr *data_attr) +{ + struct acpi_table_bdat *bdat = th; + struct acpi_bdat_data_header hdr; + void __iomem *base; + u64 end; + + if (bdat->header.length < sizeof(struct acpi_table_bdat) || + bdat->gas.space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY || + !bdat->gas.address) + return -EINVAL; + + /* acpi_os_map_iomem() can't handle a range that wraps around */ + if (check_add_overflow(bdat->gas.address, sizeof(hdr), &end)) + return -EINVAL; + + base = acpi_os_map_iomem(bdat->gas.address, sizeof(hdr)); + if (!base) + return -ENOMEM; + memcpy_fromio(&hdr, base, sizeof(hdr)); + acpi_os_unmap_iomem(base, sizeof(hdr)); + + if (memcmp(hdr.signature, "BDATHEAD", sizeof(hdr.signature)) || + hdr.size < sizeof(hdr)) + return -EINVAL; + + data_attr->addr = bdat->gas.address; + data_attr->attr.size = hdr.size; + + return 0; } static struct acpi_data_obj { @@ -485,6 +532,7 @@ static struct acpi_data_obj { } acpi_data_objs[] = { { ACPI_SIG_BERT, acpi_bert_data_init }, { ACPI_SIG_CCEL, acpi_ccel_data_init }, + { ACPI_SIG_BDAT, acpi_bdat_data_init }, }; #define NUM_ACPI_DATA_OBJS ARRAY_SIZE(acpi_data_objs) @@ -492,7 +540,7 @@ static struct acpi_data_obj { static int acpi_table_data_init(struct acpi_table_header *th, struct acpi_table_attr *table_attr) { struct acpi_data_attr *data_attr; - int i; + int i, ret; for (i = 0; i < NUM_ACPI_DATA_OBJS; i++) { if (ACPI_COMPARE_NAMESEG(th->signature, acpi_data_objs[i].name)) { @@ -504,7 +552,13 @@ static int acpi_table_data_init(struct acpi_table_header *th, struct acpi_table_ data_attr->attr.attr.mode = 0400; strscpy(data_attr->filename, table_attr->filename); data_attr->attr.attr.name = data_attr->filename; - return acpi_data_objs[i].fn(th, data_attr); + ret = acpi_data_objs[i].fn(th, data_attr); + if (!ret) + ret = sysfs_create_bin_file(tables_data_kobj, + &data_attr->attr); + if (ret) + kfree(data_attr); + return ret; } } return 0;
diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index c278ea1..b79bf0f 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c
@@ -24,6 +24,13 @@ #ifdef CONFIG_SUSPEND +/* + * First GPE armed for wake via an _Ixx method, U32_MAX if none. On + * platforms without an ACPI-enumerated EC, this GPE plays the role of + * the EC GPE in acpi_ec_dispatch_gpe(). + */ +static u32 acpi_s2idle_ixx_gpe = U32_MAX; + static void acpi_setup_ixx_gpes(void) { acpi_handle gpe_root; @@ -47,11 +54,65 @@ static void acpi_setup_ixx_gpes(void) acpi_mark_gpe_for_wake(NULL, i); acpi_set_gpe_wake_mask(NULL, i, ACPI_GPE_ENABLE); + if (acpi_s2idle_ixx_gpe == U32_MAX) + acpi_s2idle_ixx_gpe = i; pm_pr_dbg("ACPI: GPE0x%02x armed for wake via %s\n", i, gpe_nr_str); } } +/* + * acpi_x86_s2idle_check_ixx_gpes - Handle an SCI wakeup without an EC + * + * Called to decide whether or not to wake up the system when an SCI has + * triggered during suspend-to-idle on a platform without an EC instance, + * mirroring acpi_ec_dispatch_gpe() with the first GPE armed for wake via + * an _Ixx method playing the role of the EC GPE. + * + * Report a wakeup if the status bit is set for any enabled GPE other than + * the _Ixx one. If no _Ixx GPE has been armed, acpi_s2idle_ixx_gpe is + * U32_MAX and this amounts to checking all of the GPEs, which is the + * previous behavior. + * + * Otherwise, cancel the pending SCI-related wakeup, so it does not cause + * the pm_wakeup_pending() check in the caller to succeed by itself, and + * dispatch the _Ixx GPE if it is active, so the events behind it can be + * processed. If any of those events turn out to be genuine wake events, + * the drivers handling them are expected to call pm_wakeup_event() and + * the caller will catch that via pm_wakeup_pending() after synchronizing + * the event processing. + */ +bool acpi_x86_s2idle_check_ixx_gpes(void) +{ + acpi_event_status status = 0; + + /* + * Report wakeup if the status bit is set for any enabled GPE other + * than the _Ixx one. + */ + if (acpi_any_gpe_status_set(acpi_s2idle_ixx_gpe)) + return true; + + if (acpi_s2idle_ixx_gpe == U32_MAX) + return false; + + /* + * Cancel the SCI wakeup and dispatch the _Ixx GPE in case there are + * any wakeup events behind it. + */ + pm_system_cancel_wakeup(); + + acpi_get_gpe_status(NULL, acpi_s2idle_ixx_gpe, &status); + if (status & ACPI_EVENT_FLAG_STATUS_SET) { + pm_pr_dbg("ACPI _Ixx GPE 0x%02x dispatched\n", + acpi_s2idle_ixx_gpe); + + acpi_dispatch_gpe(NULL, acpi_s2idle_ixx_gpe); + } + + return false; +} + static bool sleep_no_lps0 __read_mostly; module_param(sleep_no_lps0, bool, 0644); MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 304a2b1..4e9c230 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c
@@ -343,6 +343,9 @@ static void __rpm_put_suppliers(struct device *dev, bool try_to_suspend) list_for_each_entry_rcu(link, &dev->links.suppliers, c_node, device_links_read_lock_held()) { + if (!device_link_test(link, DL_FLAG_PM_RUNTIME)) + continue; + pm_runtime_release_supplier(link); if (try_to_suspend) pm_request_idle(link->supplier); @@ -819,6 +822,12 @@ static int rpm_resume(struct device *dev, int rpmflags) pm_runtime_deactivate_timer(dev); if (dev->power.runtime_status == RPM_ACTIVE) { + /* + * The attempt to resume the device means that it is needed for + * something, so update its last_busy timestamp as in the case + * in which it actually resumes below. + */ + pm_runtime_mark_last_busy(dev); retval = 1; goto out; } @@ -1910,7 +1919,7 @@ void pm_runtime_reinit(struct device *dev) void pm_runtime_remove(struct device *dev) { __pm_runtime_disable(dev, false); - flush_work(&dev->power.work); + cancel_work_sync(&dev->power.work); pm_runtime_reinit(dev); }
diff --git a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86 index d6190b8..9e1e339 100644 --- a/drivers/cpufreq/Kconfig.x86 +++ b/drivers/cpufreq/Kconfig.x86
@@ -39,7 +39,7 @@ select ACPI_PROCESSOR select ACPI_CPPC_LIB if X86_64 select CPU_FREQ_GOV_SCHEDUTIL if SMP - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select POWER_SUPPLY help This driver adds a CPUFreq driver which utilizes a fine grain
diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 2e74a00..f64fa37 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c
@@ -197,14 +197,13 @@ static unsigned extract_io(struct cpufreq_policy *policy, u32 value) { struct acpi_cpufreq_data *data = policy->driver_data; struct acpi_processor_performance *perf; - int i; + struct cpufreq_frequency_table *pos; perf = to_perf_data(data); - for (i = 0; i < perf->state_count; i++) { - if (value == perf->states[i].status) - return policy->freq_table[i].frequency; - } + cpufreq_for_each_entry(pos, policy->freq_table) + if (value == perf->states[pos->driver_data].status) + return pos->frequency; return 0; }
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 4bedfbf..bf7d25e 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c
@@ -546,7 +546,7 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) struct cppc_perf_caps cppc_perf; union perf_cached perf = READ_ONCE(cpudata->perf); u64 numerator; - bool auto_sel; + u64 auto_sel; int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); if (ret)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 957fa77..a62589c 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -29,6 +29,215 @@ static struct cpufreq_driver cppc_cpufreq_driver; +/* + * OSPM-set CPPC registers tracked for save/restore. A value the OS wrote is + * reapplied from online() across CPU hotplug, and the firmware value is + * restored from offline(). + * + * Autonomous Selection (auto_sel) splits the list: the registers before it are + * independent of it, and those after it have meaning only while autonomous + * selection is enabled. Place a new register on the matching side. + */ +enum cppc_saved_reg_id { + CPPC_SAVED_OSPM_NOMINAL_PERF, + CPPC_SAVED_AUTO_SEL, /* Entries below need auto_sel enabled. */ + CPPC_SAVED_EPP, + CPPC_SAVED_AUTO_ACT_WINDOW, + CPPC_NR_SAVED_REGS, +}; + +struct cppc_saved_reg { + const char *name; + int (*get)(int cpu, u64 *val); + int (*set)(int cpu, u64 val); +}; + +static const struct cppc_saved_reg cppc_saved_regs[CPPC_NR_SAVED_REGS] = { + /* The register is write-only, so the driver tracks the requested value. */ + [CPPC_SAVED_OSPM_NOMINAL_PERF] = { + .name = "ospm_nominal_perf", + .set = cppc_set_ospm_nominal_perf, + }, + [CPPC_SAVED_AUTO_SEL] = { + .name = "auto_sel", + .get = cppc_get_auto_sel, + .set = cppc_set_auto_sel, + }, + [CPPC_SAVED_EPP] = { + .name = "epp", + .get = cppc_get_epp_perf, + .set = cppc_set_epp, + }, + [CPPC_SAVED_AUTO_ACT_WINDOW] = { + .name = "auto_act_window", + .get = cppc_get_auto_act_window, + .set = cppc_set_auto_act_window, + }, +}; + +enum cppc_saved_type { + CPPC_SAVED_FIRMWARE, + CPPC_SAVED_REQUESTED, + CPPC_NR_SAVED_TYPES, +}; + +/* + * Per-policy values saved for each register in cppc_saved_regs[], U64_MAX + * if the register is not implemented or not readable: + * CPPC_SAVED_FIRMWARE - value before the driver touched it, saved at + * init() and written back when the policy goes + * offline + * CPPC_SAVED_REQUESTED - value the OS asked for, reapplied at online(). + * It starts as the firmware value and changes only + * when a write from the OS succeeds. + * + * A write-only register cannot be read back. init() leaves both values unset, + * and its store handler sets them. + */ +struct cppc_policy_state { + u64 regs[CPPC_NR_SAVED_REGS][CPPC_NR_SAVED_TYPES]; + /* + * Set by suspend() after it restores the firmware values. While it is + * set, offline() skips that restore and resume() calls online(). + * Cleared by init() and online(). + */ + bool suspend_regs_handled; +}; + +static DEFINE_PER_CPU(struct cppc_policy_state, cppc_policy_state); + +/* + * Per-policy state is kept in the per-CPU variable of the first CPU the policy + * manages. related_cpus (the policy's full set of CPUs) never changes while the + * policy exists, so this CPU (unlike policy->cpu) stays the same across CPU + * hotplug, and every callback reaches the same copy. + */ +static struct cppc_policy_state * +cppc_cpufreq_policy_state(struct cpufreq_policy *policy) +{ + const struct cpumask *policy_cpus = policy->related_cpus; + + /* + * related_cpus is empty during init(), as the core fills it in only + * after init() returns. Use policy->cpus then, which has the same + * first CPU. + */ + if (cpumask_empty(policy_cpus)) + policy_cpus = policy->cpus; + + return &per_cpu(cppc_policy_state, cpumask_first(policy_cpus)); +} + +/* + * Save each tracked register's firmware value before the driver programs + * anything. Also start the requested value from it. If the OS never writes a + * register and the platform resets it while the CPU is offline, online() then + * writes the firmware value back. + * + * Skip a register that is not implemented or not readable. Fail on any other + * read error, as the firmware value is needed to put the register back later. + */ +static int cppc_cpufreq_save_firmware_regs(struct cpufreq_policy *policy) +{ + struct cppc_policy_state *st = cppc_cpufreq_policy_state(policy); + unsigned int cpu = policy->cpu; + u64 val; + int i, ret; + + for (i = 0; i < CPPC_NR_SAVED_REGS; i++) { + const struct cppc_saved_reg *reg = &cppc_saved_regs[i]; + + if (!reg->get) { + /* A write-only register has no value to read. */ + val = U64_MAX; + } else { + ret = reg->get(cpu, &val); + /* Leave a register the platform does not implement unset. */ + if (ret == -EOPNOTSUPP) + val = U64_MAX; + else if (ret) + return ret; + } + + st->regs[i][CPPC_SAVED_FIRMWARE] = val; + st->regs[i][CPPC_SAVED_REQUESTED] = val; + } + + return 0; +} + +/* + * After a successful write to a tracked register, save the value so that + * online() can reapply it. + */ +static void cppc_cpufreq_save_requested(struct cpufreq_policy *policy, + enum cppc_saved_reg_id reg, u64 val) +{ + cppc_cpufreq_policy_state(policy)->regs[reg][CPPC_SAVED_REQUESTED] = val; +} + +/* + * Write one tracked register, skipping it when there is no saved value. + * A register the platform does not allow writing is not an error. + */ +static void cppc_cpufreq_write_saved_reg(unsigned int cpu, + enum cppc_saved_reg_id reg, u64 val, + enum cppc_saved_type saved_type) +{ + const char *op = (saved_type == CPPC_SAVED_FIRMWARE) ? + "restore firmware" : "reapply requested"; + int ret; + + if (val == U64_MAX) + return; + + ret = cppc_saved_regs[reg].set(cpu, val); + if (ret == -EOPNOTSUPP) + return; + if (ret) + pr_debug("Failed to %s %s=%llu on CPU%u (%d)\n", op, + cppc_saved_regs[reg].name, val, cpu, ret); +} + +/* + * Apply the saved firmware or requested value to each tracked register. + * + * Write auto_sel first when the value being applied enables autonomous + * selection and last when it disables it, so the writes to the dependent + * registers can still take effect. While autonomous selection stays disabled, + * the platform may ignore those writes. Do not enable it temporarily to force + * them through. + */ +static void cppc_cpufreq_apply_saved_regs(struct cpufreq_policy *policy, + enum cppc_saved_type saved_type) +{ + const struct cppc_policy_state *st = cppc_cpufreq_policy_state(policy); + unsigned int cpu = policy->cpu; + u64 auto_sel, val; + int i; + + /* Registers before auto_sel do not depend on it. */ + for (i = 0; i < CPPC_SAVED_AUTO_SEL; i++) { + val = st->regs[i][saved_type]; + cppc_cpufreq_write_saved_reg(cpu, i, val, saved_type); + } + + auto_sel = st->regs[CPPC_SAVED_AUTO_SEL][saved_type]; + + if (auto_sel) + cppc_cpufreq_write_saved_reg(cpu, CPPC_SAVED_AUTO_SEL, auto_sel, + saved_type); + + for (i = CPPC_SAVED_AUTO_SEL + 1; i < CPPC_NR_SAVED_REGS; i++) { + val = st->regs[i][saved_type]; + cppc_cpufreq_write_saved_reg(cpu, i, val, saved_type); + } + + if (!auto_sel) + cppc_cpufreq_write_saved_reg(cpu, CPPC_SAVED_AUTO_SEL, auto_sel, + saved_type); +} + #ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE static enum { FIE_UNSET = -1, @@ -153,6 +362,7 @@ static struct scale_freq_data cppc_sftd_pcc = { static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) { + const struct cpumask *cpus = policy->related_cpus; struct scale_freq_data *sftd; struct cppc_freq_invariance *cppc_fi; int cpu, ret; @@ -160,7 +370,15 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) if (fie_disabled) return; - for_each_cpu(cpu, policy->cpus) { + /* + * When resume() calls online(), policy->cpus has only the online CPUs. + * online() does not run again for a CPU in the same policy that comes + * up later, so use related_cpus. It is empty only during init(). + */ + if (cpumask_empty(cpus)) + cpus = policy->cpus; + + for_each_cpu(cpu, cpus) { cppc_fi = &per_cpu(cppc_freq_inv, cpu); cppc_fi->cpu = cpu; cppc_fi->cpu_data = policy->driver_data; @@ -175,10 +393,7 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs); - /* - * Don't abort as the CPU was offline while the driver was - * getting registered. - */ + /* Don't abort for a CPU that is offline. */ if (ret && cpu_online(cpu)) { pr_debug("%s: failed to read perf counters for cpu:%d: %d\n", __func__, cpu, ret); @@ -187,7 +402,7 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) } /* A shared policy may contain both PCC and non-PCC counters. */ - for_each_cpu(cpu, policy->cpus) { + for_each_cpu(cpu, cpus) { cppc_fi = &per_cpu(cppc_freq_inv, cpu); if (cppc_fi->pcc_work_initialized) sftd = &cppc_sftd_pcc; @@ -209,7 +424,7 @@ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy) if (fie_disabled) return; - /* policy->cpus will be empty here, use related_cpus instead */ + /* policy->cpus has only the online CPUs, so use related_cpus */ topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_CPPC, policy->related_cpus); for_each_cpu(cpu, policy->related_cpus) { @@ -321,60 +536,96 @@ static void cppc_cpufreq_get_perf_limits(struct cppc_cpudata *cpu_data, *max_perf = clamp_t(u32, max, caps->lowest_perf, caps->highest_perf); } -static void cppc_cpufreq_update_perf_limits(struct cppc_cpudata *cpu_data, - struct cpufreq_policy *policy) -{ - cppc_cpufreq_get_perf_limits(cpu_data, policy, - &cpu_data->perf_ctrls.min_perf, - &cpu_data->perf_ctrls.max_perf); -} - static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { struct cppc_cpudata *cpu_data = policy->driver_data; + struct cppc_perf_ctrls perf_ctrls = {}; unsigned int cpu = policy->cpu; struct cpufreq_freqs freqs; + u32 desired_perf; int ret = 0; - cpu_data->perf_ctrls.desired_perf = - cppc_khz_to_perf(&cpu_data->perf_caps, target_freq); - cppc_cpufreq_update_perf_limits(cpu_data, policy); + desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq); + cppc_cpufreq_get_perf_limits(cpu_data, policy, &perf_ctrls.min_perf, + &perf_ctrls.max_perf); + perf_ctrls.min_perf_valid = true; + + /* + * Keep the cached Desired within [MIN, MAX]. Rounding can put it just + * outside at the ends of the range. The policy limits may also change + * after the governor picked target_freq. + */ + perf_ctrls.desired_perf = clamp(desired_perf, perf_ctrls.min_perf, + perf_ctrls.max_perf); freqs.old = policy->cur; freqs.new = target_freq; cpufreq_freq_transition_begin(policy, &freqs); - ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); + ret = cppc_set_perf(cpu, &perf_ctrls); cpufreq_freq_transition_end(policy, &freqs, ret != 0); - if (ret) + if (ret) { + WRITE_ONCE(cpu_data->perf_limits_valid, false); pr_debug("Failed to set target on CPU:%d. ret:%d\n", cpu, ret); + return ret; + } - return ret; + WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, + perf_ctrls.desired_perf); + WRITE_ONCE(cpu_data->perf_ctrls.min_perf, perf_ctrls.min_perf); + WRITE_ONCE(cpu_data->perf_ctrls.max_perf, perf_ctrls.max_perf); + WRITE_ONCE(cpu_data->perf_limits_valid, true); + + return 0; } static unsigned int cppc_cpufreq_fast_switch(struct cpufreq_policy *policy, unsigned int target_freq) { struct cppc_cpudata *cpu_data = policy->driver_data; + struct cppc_perf_ctrls perf_ctrls = {}; unsigned int cpu = policy->cpu; - u32 desired_perf; + u32 desired_perf, min_perf, max_perf; + bool update_limits; int ret; desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq); - cpu_data->perf_ctrls.desired_perf = desired_perf; - cppc_cpufreq_update_perf_limits(cpu_data, policy); + cppc_cpufreq_get_perf_limits(cpu_data, policy, &min_perf, &max_perf); - ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); + /* + * cppc_set_perf() does not check a Desired-only request, and rounding + * or a policy limit change can put Desired outside [MIN, MAX]. + */ + desired_perf = clamp(desired_perf, min_perf, max_perf); + perf_ctrls.desired_perf = desired_perf; + update_limits = !READ_ONCE(cpu_data->perf_limits_valid) || + min_perf != READ_ONCE(cpu_data->perf_ctrls.min_perf) || + max_perf != READ_ONCE(cpu_data->perf_ctrls.max_perf); + if (update_limits) { + perf_ctrls.min_perf = min_perf; + perf_ctrls.max_perf = max_perf; + perf_ctrls.min_perf_valid = true; + } + + ret = cppc_set_perf(cpu, &perf_ctrls); if (ret) { + WRITE_ONCE(cpu_data->perf_limits_valid, false); pr_debug("Failed to set target on CPU:%d. ret:%d\n", cpu, ret); return 0; } + WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, desired_perf); + if (update_limits) { + WRITE_ONCE(cpu_data->perf_ctrls.min_perf, min_perf); + WRITE_ONCE(cpu_data->perf_ctrls.max_perf, max_perf); + WRITE_ONCE(cpu_data->perf_limits_valid, true); + } + return target_freq; } @@ -664,11 +915,35 @@ static void cppc_cpufreq_put_cpu_data(struct cpufreq_policy *policy) policy->driver_data = NULL; } +/* + * Return the non-boost performance ceiling. This is the OSPM Nominal + * Performance the driver last requested, or the platform-reported Nominal + * Performance if none was requested. + * + * The register is write-only, so the requested value comes from the + * software-tracked state rather than from hardware. + */ +static u32 cppc_cpufreq_effective_nominal(struct cpufreq_policy *policy) +{ + const struct cppc_policy_state *st = cppc_cpufreq_policy_state(policy); + struct cppc_cpudata *cpu_data = policy->driver_data; + u64 ospm_nominal; + + ospm_nominal = st->regs[CPPC_SAVED_OSPM_NOMINAL_PERF][CPPC_SAVED_REQUESTED]; + + /* U64_MAX means OSPM has not selected a nominal level. */ + if (ospm_nominal == U64_MAX) + return cpu_data->perf_caps.nominal_perf; + + return (u32)ospm_nominal; +} + static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) { unsigned int cpu = policy->cpu; struct cppc_cpudata *cpu_data; struct cppc_perf_caps *caps; + u32 desired_perf; int ret; cpu_data = cppc_cpufreq_get_cpu_data(cpu); @@ -714,7 +989,17 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) pr_debug("Unsupported CPU co-ord type: %d\n", policy->shared_type); ret = -EFAULT; - goto out; + goto out_put; + } + + /* + * Enable CPPC. The Enable register is optional - some platforms + * may not support it. + */ + ret = cppc_set_enable(cpu, true); + if (ret && ret != -EOPNOTSUPP) { + pr_err("Failed to enable CPPC for CPU%u (%d)\n", cpu, ret); + goto out_put; } policy->fast_switch_possible = cppc_allow_fast_switch(policy->cpus); @@ -722,47 +1007,225 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) /* * If 'highest_perf' is greater than 'nominal_perf', we assume CPU Boost - * is supported. + * is supported. A writable OSPM Nominal Performance register can also + * open a boost range at runtime by lowering the nominal, so assume + * boost is supported in that case too, letting the core register its + * QoS request up front. */ - if (caps->highest_perf > caps->nominal_perf) + if (caps->highest_perf > caps->nominal_perf || + (caps->highest_perf > caps->lowest_perf && + cppc_ospm_nominal_perf_supported(cpu))) policy->boost_supported = true; - /* Set policy->cur to max now. The governors will adjust later. */ - policy->cur = cppc_perf_to_khz(caps, caps->highest_perf); - cpu_data->perf_ctrls.desired_perf = caps->highest_perf; + /* + * The request starts from the live limits, which may be unusable. + * Firmware may leave them zeroed. An interrupted earlier update may + * have left MIN above MAX. cppc_set_perf() would reject either, so + * request the full range instead. + */ + if (!cpu_data->perf_ctrls.max_perf || + cpu_data->perf_ctrls.min_perf > cpu_data->perf_ctrls.max_perf) { + cpu_data->perf_ctrls.min_perf = caps->lowest_perf; + cpu_data->perf_ctrls.min_perf_valid = true; + cpu_data->perf_ctrls.max_perf = caps->highest_perf; + } + + /* + * Keep the initial request within controls left by an earlier driver + * instance or firmware. The governor will update the full tuple later. + */ + desired_perf = caps->highest_perf; + if (cpu_data->perf_ctrls.max_perf) + desired_perf = min(desired_perf, cpu_data->perf_ctrls.max_perf); + if (cpu_data->perf_ctrls.min_perf_valid) + desired_perf = max(desired_perf, cpu_data->perf_ctrls.min_perf); + + policy->cur = cppc_perf_to_khz(caps, desired_perf); + cpu_data->perf_ctrls.desired_perf = desired_perf; + + cppc_cpufreq_policy_state(policy)->suspend_regs_handled = false; + ret = cppc_cpufreq_save_firmware_regs(policy); + if (ret) { + pr_debug("Err reading CPU%d firmware values: ret:%d\n", cpu, ret); + goto out; + } ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); if (ret) { pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n", - caps->highest_perf, cpu, ret); + desired_perf, cpu, ret); goto out; } + cpu_data->perf_limits_valid = true; cppc_cpufreq_cpu_fie_init(policy); return 0; out: + cppc_set_enable(cpu, false); +out_put: cppc_cpufreq_put_cpu_data(policy); return ret; } static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy) { + /* + * The core always calls offline() first, so only the driver data is + * left to free. + */ + cppc_cpufreq_put_cpu_data(policy); +} + +/* + * Run when the policy's first CPU comes back online, the counterpart of + * offline(). + * + * The platform may have disabled CPPC and reset the performance controls + * (desired, min and max performance) while the CPU was offline. Re-enable + * CPPC, reprogram the controls and reapply the requested values of the + * OSPM-set registers, which offline() reset to firmware values. + * + * Log failures but do not return them, as the core would free the policy. + * Continue after a failure to enable CPPC, as the writes that follow fail + * on their own if the platform does not take them. If the controls write + * fails, still reapply the OSPM-set registers, as frequency changes rewrite + * only the controls. + */ +static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy) +{ struct cppc_cpudata *cpu_data = policy->driver_data; - struct cppc_perf_caps *caps = &cpu_data->perf_caps; + u32 desired_perf, min_perf, max_perf; + struct cppc_perf_ctrls perf_ctrls = {}; unsigned int cpu = policy->cpu; int ret; + cppc_cpufreq_policy_state(policy)->suspend_regs_handled = false; + + /* + * The platform may have reset MIN and MAX, so mark the copy that + * fast_switch() uses as out of date until the restore below succeeds. + */ + WRITE_ONCE(cpu_data->perf_limits_valid, false); + + ret = cppc_set_enable(cpu, true); + if (ret && ret != -EOPNOTSUPP) + pr_warn("Failed to re-enable CPPC for CPU%u (%d)\n", cpu, ret); + + /* + * Recompute min/max from the policy, clamp the last request into range, + * and reprogram the performance controls. Update the driver's copy only + * if the write succeeds. Otherwise the next frequency change writes the + * limits again. + */ + cppc_cpufreq_get_perf_limits(cpu_data, policy, &min_perf, &max_perf); + desired_perf = clamp_t(u32, cpu_data->perf_ctrls.desired_perf, + min_perf, max_perf); + + perf_ctrls.desired_perf = desired_perf; + perf_ctrls.min_perf = min_perf; + perf_ctrls.max_perf = max_perf; + perf_ctrls.min_perf_valid = true; + + ret = cppc_set_perf(cpu, &perf_ctrls); + if (ret) { + pr_debug("Failed to restore perf controls on CPU%u (%d)\n", + cpu, ret); + } else { + WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, desired_perf); + WRITE_ONCE(cpu_data->perf_ctrls.min_perf, min_perf); + WRITE_ONCE(cpu_data->perf_ctrls.max_perf, max_perf); + WRITE_ONCE(cpu_data->perf_limits_valid, true); + } + + cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_REQUESTED); + + /* Restart what offline() stopped, with a new counter snapshot. */ + cppc_cpufreq_cpu_fie_init(policy); + + return 0; +} + +/* + * Run when the policy's last online CPU goes down, the counterpart of online(). + * Having offline() makes the core keep the policy rather than call exit(). + */ +static int cppc_cpufreq_cpu_offline(struct cpufreq_policy *policy) +{ + struct cppc_cpudata *cpu_data = policy->driver_data; + struct cppc_perf_ctrls perf_ctrls = cpu_data->perf_ctrls; + unsigned int cpu = policy->cpu; + int ret; + + /* + * Leave the platform in its pre-driver state, unless suspend() already + * did so earlier in this suspend cycle. + */ + if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled) + cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE); + + /* + * Stop the frequency invariance updates and cancel the pending work, so + * that no sample spans the offline window. online() restarts them with + * a new counter snapshot. + */ cppc_cpufreq_cpu_fie_exit(policy); - cpu_data->perf_ctrls.desired_perf = caps->lowest_perf; + /* + * Request the lowest performance while the policy has no online CPU, + * lowering MIN with it so that the request stays a valid tuple. MAX + * keeps the value the policy last programmed. + */ + perf_ctrls.desired_perf = cpu_data->perf_caps.lowest_perf; + perf_ctrls.min_perf = cpu_data->perf_caps.lowest_perf; + perf_ctrls.min_perf_valid = true; - ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); + ret = cppc_set_perf(cpu, &perf_ctrls); if (ret) - pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n", - caps->lowest_perf, cpu, ret); + pr_debug("Err setting perf value:%u on CPU:%u. ret:%d\n", + cpu_data->perf_caps.lowest_perf, cpu, ret); - cppc_cpufreq_put_cpu_data(policy); + return 0; +} + +/* + * Run for every active policy when the system suspends, before any CPU goes + * offline. + * + * Restore the firmware values. Doing it here covers a policy that keeps a CPU + * online, for which offline() never runs. offline() does not write them again + * in the same suspend cycle. + * + * Stop the frequency invariance updates too, so that no sample spans the + * suspend window. online() restarts them on the way back, from hotplug or + * from resume(). + */ +static int cppc_cpufreq_cpu_suspend(struct cpufreq_policy *policy) +{ + cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE); + cppc_cpufreq_policy_state(policy)->suspend_regs_handled = true; + + cppc_cpufreq_cpu_fie_exit(policy); + + return 0; +} + +/* + * Run the online() restore for a policy that kept a CPU online through + * suspend. + * + * For any other policy, online() ran when its first CPU came back, before the + * core calls resume(). That cleared the flag. Only a policy that still has it + * set needs online() here. Either way every policy goes through online(), so + * resume() does not need to mark the copy of MIN and MAX that fast_switch() + * uses as out of date. + */ +static int cppc_cpufreq_cpu_resume(struct cpufreq_policy *policy) +{ + if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled) + return 0; + + return cppc_cpufreq_cpu_online(policy); } static inline u64 get_delta(u64 t1, u64 t0) @@ -857,11 +1320,12 @@ static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state) { struct cppc_cpudata *cpu_data = policy->driver_data; struct cppc_perf_caps *caps = &cpu_data->perf_caps; + u32 nominal = cppc_cpufreq_effective_nominal(policy); if (state) policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->highest_perf); else - policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->nominal_perf); + policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, nominal); return 0; } @@ -875,7 +1339,7 @@ static ssize_t show_freqdomain_cpus(struct cpufreq_policy *policy, char *buf) static ssize_t show_auto_select(struct cpufreq_policy *policy, char *buf) { - bool val; + u64 val; int ret; ret = cppc_get_auto_sel(policy->cpu, &val); @@ -887,7 +1351,7 @@ static ssize_t show_auto_select(struct cpufreq_policy *policy, char *buf) if (ret) return ret; - return sysfs_emit(buf, "%d\n", val); + return sysfs_emit(buf, "%llu\n", val); } static ssize_t store_auto_select(struct cpufreq_policy *policy, @@ -909,26 +1373,38 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy, cpu_data->perf_ctrls.auto_sel = val; if (val) { - u32 old_min_perf = cpu_data->perf_ctrls.min_perf; - u32 old_max_perf = cpu_data->perf_ctrls.max_perf; + struct cppc_perf_ctrls perf_ctrls = {}; /* * When enabling autonomous selection, program MIN_PERF and * MAX_PERF from current policy limits so that the platform * uses the correct performance bounds immediately. */ - cppc_cpufreq_update_perf_limits(cpu_data, policy); + cppc_cpufreq_get_perf_limits(cpu_data, policy, + &perf_ctrls.min_perf, + &perf_ctrls.max_perf); + perf_ctrls.min_perf_valid = true; + /* The cached Desired may be outside the new MIN and MAX. */ + perf_ctrls.desired_perf = + clamp(cpu_data->perf_ctrls.desired_perf, + perf_ctrls.min_perf, perf_ctrls.max_perf); - ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls); + ret = cppc_set_perf(policy->cpu, &perf_ctrls); if (ret) { - cpu_data->perf_ctrls.min_perf = old_min_perf; - cpu_data->perf_ctrls.max_perf = old_max_perf; + WRITE_ONCE(cpu_data->perf_limits_valid, false); cppc_set_auto_sel(policy->cpu, old_auto_sel); cpu_data->perf_ctrls.auto_sel = old_auto_sel; return ret; } + WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, + perf_ctrls.desired_perf); + WRITE_ONCE(cpu_data->perf_ctrls.min_perf, perf_ctrls.min_perf); + WRITE_ONCE(cpu_data->perf_ctrls.max_perf, perf_ctrls.max_perf); + WRITE_ONCE(cpu_data->perf_limits_valid, true); } + cppc_cpufreq_save_requested(policy, CPPC_SAVED_AUTO_SEL, val); + return count; } @@ -976,8 +1452,30 @@ static ssize_t store_##_name(struct cpufreq_policy *policy, \ buf, count); \ } -CPPC_CPUFREQ_ATTR_RW_U64(auto_act_window, cppc_get_auto_act_window, - cppc_set_auto_act_window) +static ssize_t show_auto_act_window(struct cpufreq_policy *policy, char *buf) +{ + return cppc_cpufreq_sysfs_show_u64(policy->cpu, cppc_get_auto_act_window, + buf); +} + +static ssize_t store_auto_act_window(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + u64 val; + int ret; + + ret = kstrtou64(buf, 0, &val); + if (ret) + return ret; + + ret = cppc_set_auto_act_window(policy->cpu, val); + if (ret) + return ret; + + cppc_cpufreq_save_requested(policy, CPPC_SAVED_AUTO_ACT_WINDOW, val); + + return count; +} static ssize_t show_energy_performance_preference_val(struct cpufreq_policy *policy, char *buf) @@ -1002,6 +1500,7 @@ store_energy_performance_preference_val(struct cpufreq_policy *policy, return ret; cpu_data->perf_ctrls.energy_perf = val; + cppc_cpufreq_save_requested(policy, CPPC_SAVED_EPP, val); return count; } @@ -1036,11 +1535,110 @@ static int cppc_get_perf_limited_filtered(int cpu, u64 *perf_limited) CPPC_CPUFREQ_ATTR_RW_U64(perf_limited, cppc_get_perf_limited_filtered, cppc_set_perf_limited) +/* + * While boost is disabled, the nominal is the ceiling. Set cpuinfo.max_freq to + * it and update the core's boost_freq_req to match. The core only syncs + * boost_freq_req when boost is enabled or disabled, so a plain nominal change + * must update it here. Without that, raising the nominal again would leave the + * stale request capping the policy. + * + * Refresh the limits directly rather than leave it to the QoS notifier, which + * only schedules the update, so the new ceiling is in place once the write + * returns. The sysfs store path holds policy->rwsem, which the refresh needs. + */ +static void cppc_cpufreq_update_nominal_limits(struct cpufreq_policy *policy) +{ + struct cppc_cpudata *cpu_data = policy->driver_data; + u32 nominal; + int ret; + + if (policy->boost_enabled) + return; + + nominal = cppc_cpufreq_effective_nominal(policy); + policy->cpuinfo.max_freq = cppc_perf_to_khz(&cpu_data->perf_caps, + nominal); + + if (freq_qos_request_active(&policy->boost_freq_req)) { + ret = freq_qos_update_request(&policy->boost_freq_req, + policy->cpuinfo.max_freq); + if (ret < 0) + pr_debug("Failed to update boost limit on CPU%u (%d)\n", + policy->cpu, ret); + } + + refresh_frequency_limits(policy); +} + +static ssize_t show_ospm_nominal_freq(struct cpufreq_policy *policy, char *buf) +{ + const struct cppc_policy_state *st = cppc_cpufreq_policy_state(policy); + struct cppc_cpudata *cpu_data = policy->driver_data; + u64 perf; + + if (!cppc_ospm_nominal_perf_supported(policy->cpu)) + return -EOPNOTSUPP; + + /* The register cannot be read back, so report the tracked request. */ + perf = st->regs[CPPC_SAVED_OSPM_NOMINAL_PERF][CPPC_SAVED_REQUESTED]; + if (perf == U64_MAX) + return sysfs_emit(buf, "<unset>\n"); + + return sysfs_emit(buf, "%u\n", + cppc_perf_to_khz(&cpu_data->perf_caps, perf)); +} + +static ssize_t store_ospm_nominal_freq(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + struct cppc_cpudata *cpu_data = policy->driver_data; + struct cppc_policy_state *st; + unsigned int freq_khz; + u32 perf; + int ret; + + ret = kstrtouint(buf, 0, &freq_khz); + if (ret) + return ret; + + perf = cppc_khz_to_perf(&cpu_data->perf_caps, freq_khz); + if (perf < cpu_data->perf_caps.lowest_perf || + perf > cpu_data->perf_caps.nominal_perf) + return -EINVAL; + + ret = cppc_set_ospm_nominal_perf(policy->cpu, perf); + if (ret) + return ret; + + /* + * The OS can write this register but cannot read it back. + * + * The register lets the OS ask for a nominal level below the platform + * Nominal Performance. offline() and driver unload write the platform + * Nominal in place of the requested value. This removes the lower + * level the OS asked for. The value from before the first OS write + * cannot be read, so the driver cannot restore it. online() writes + * the requested value again. + * + * Set both values here and not at init(). The driver then writes the + * register only after the OS has set it. + */ + st = cppc_cpufreq_policy_state(policy); + st->regs[CPPC_SAVED_OSPM_NOMINAL_PERF][CPPC_SAVED_REQUESTED] = perf; + st->regs[CPPC_SAVED_OSPM_NOMINAL_PERF][CPPC_SAVED_FIRMWARE] = + cpu_data->perf_caps.nominal_perf; + + cppc_cpufreq_update_nominal_limits(policy); + + return count; +} + cpufreq_freq_attr_ro(freqdomain_cpus); cpufreq_freq_attr_rw(auto_select); cpufreq_freq_attr_rw(auto_act_window); cpufreq_freq_attr_rw(energy_performance_preference_val); cpufreq_freq_attr_rw(perf_limited); +cpufreq_freq_attr_rw(ospm_nominal_freq); static struct freq_attr *cppc_cpufreq_attr[] = { &freqdomain_cpus, @@ -1048,6 +1646,7 @@ static struct freq_attr *cppc_cpufreq_attr[] = { &auto_act_window, &energy_performance_preference_val, &perf_limited, + &ospm_nominal_freq, NULL, }; @@ -1059,6 +1658,10 @@ static struct cpufreq_driver cppc_cpufreq_driver = { .fast_switch = cppc_cpufreq_fast_switch, .init = cppc_cpufreq_cpu_init, .exit = cppc_cpufreq_cpu_exit, + .online = cppc_cpufreq_cpu_online, + .offline = cppc_cpufreq_cpu_offline, + .suspend = cppc_cpufreq_cpu_suspend, + .resume = cppc_cpufreq_cpu_resume, .set_boost = cppc_cpufreq_set_boost, .attr = cppc_cpufreq_attr, .name = "cppc_cpufreq",
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 54dde84..4332cb1 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c
@@ -35,6 +35,9 @@ static LIST_HEAD(cpufreq_policy_list); /* Macros to iterate over CPU policies */ +#define for_each_policy(__policy) \ + list_for_each_entry(__policy, &cpufreq_policy_list, policy_list) + #define for_each_suitable_policy(__policy, __active) \ list_for_each_entry(__policy, &cpufreq_policy_list, policy_list) \ if ((__active) == !policy_is_inactive(__policy)) @@ -474,7 +477,7 @@ EXPORT_SYMBOL_GPL(cpufreq_disable_fast_switch); static unsigned int __resolve_freq(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int min, unsigned int max, - unsigned int relation) + unsigned int relation, unsigned int *idx_p) { unsigned int idx; @@ -484,8 +487,9 @@ static unsigned int __resolve_freq(struct cpufreq_policy *policy, return target_freq; idx = cpufreq_frequency_table_target(policy, target_freq, min, max, relation); - policy->cached_resolved_idx = idx; - policy->cached_target_freq = target_freq; + if (idx_p) + *idx_p = idx; + return policy->freq_table[idx].frequency; } @@ -517,7 +521,10 @@ unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy, if (unlikely(min > max)) min = max; - return __resolve_freq(policy, target_freq, min, max, CPUFREQ_RELATION_LE); + policy->cached_target_freq = __resolve_freq(policy, target_freq, min, max, + CPUFREQ_RELATION_LE, + &policy->cached_resolved_idx); + return policy->cached_target_freq; } EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq); @@ -576,6 +583,8 @@ static int policy_set_boost(struct cpufreq_policy *policy, bool enable) { int ret; + lockdep_assert_held_write(&policy->rwsem); + if (policy->boost_enabled == enable) return 0; @@ -1629,6 +1638,8 @@ static int cpufreq_online(unsigned int cpu) */ if (cpufreq_driver->set_boost && policy->boost_supported && (new_policy || !cpufreq_boost_enabled())) { + guard(cpufreq_policy_write)(policy); + ret = policy_set_boost(policy, cpufreq_boost_enabled()); if (ret) { /* If the set_boost fails, the online operation is not affected */ @@ -1956,6 +1967,14 @@ int cpufreq_generic_suspend(struct cpufreq_policy *policy) } EXPORT_SYMBOL(cpufreq_generic_suspend); +static void cpufreq_suspend_policy(struct cpufreq_policy *policy) +{ + guard(cpufreq_policy_write)(policy); + + cpufreq_stop_governor(policy); + policy->suspended = true; +} + /** * cpufreq_suspend() - Suspend CPUFreq governors. * @@ -1978,11 +1997,8 @@ void cpufreq_suspend(void) pr_debug("%s: Suspending Governors\n", __func__); for_each_active_policy(policy) { - if (has_target()) { - scoped_guard(cpufreq_policy_write, policy) { - cpufreq_stop_governor(policy); - } - } + if (has_target()) + cpufreq_suspend_policy(policy); if (cpufreq_driver->suspend && cpufreq_driver->suspend(policy)) pr_err("%s: Failed to suspend driver: %s\n", __func__, @@ -1994,6 +2010,41 @@ void cpufreq_suspend(void) cpus_read_unlock(); } +static void cpufreq_resume_policy(struct cpufreq_policy *policy, bool start) +{ + guard(cpufreq_policy_write)(policy); + + if (!policy->suspended) + return; + + policy->suspended = false; + + if (policy_is_inactive(policy)) { + /* + * The policy was active when it was suspended, so its governor + * is still there. Let the governor go. + */ + cpufreq_exit_governor(policy); + return; + } + + if (!start) + return; + + /* + * Force executing the driver .target() callback, if present, in the + * next invocation of __cpufreq_driver_target() in case policy->cur has + * become stale while suspended, but do it only for drivers without a + * .get() callback for which cpufreq_verify_current_freq() is a NOP. + */ + if (cpufreq_driver->target && !cpufreq_driver->get) + policy->force_target = true; + + if (cpufreq_start_governor(policy)) + pr_err("%s: Failed to start governor for CPU%u's policy\n", + __func__, policy->cpu); +} + /** * cpufreq_resume() - Resume CPUFreq governors. * @@ -2003,11 +2054,12 @@ void cpufreq_suspend(void) void cpufreq_resume(void) { struct cpufreq_policy *policy; - int ret; if (!cpufreq_driver) return; + guard(cpus_read_lock)(); + if (unlikely(!cpufreq_suspended)) return; @@ -2018,19 +2070,18 @@ void cpufreq_resume(void) pr_debug("%s: Resuming Governors\n", __func__); - for_each_active_policy(policy) { - if (cpufreq_driver->resume && cpufreq_driver->resume(policy)) { - pr_err("%s: Failed to resume driver: %s\n", __func__, - cpufreq_driver->name); - } else if (has_target()) { - scoped_guard(cpufreq_policy_write, policy) { - ret = cpufreq_start_governor(policy); - } + for_each_policy(policy) { + int ret = 0; + if (cpufreq_driver->resume && !policy_is_inactive(policy)) { + ret = cpufreq_driver->resume(policy); if (ret) - pr_err("%s: Failed to start governor for CPU%u's policy\n", - __func__, policy->cpu); + pr_err("%s: Failed to resume driver: %s\n", __func__, + cpufreq_driver->name); } + + if (has_target()) + cpufreq_resume_policy(policy, ret == 0); } } @@ -2355,28 +2406,26 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy, unsigned int relation) { unsigned int old_target_freq = target_freq; + unsigned int idx = 0; if (cpufreq_disabled()) return -ENODEV; target_freq = __resolve_freq(policy, target_freq, policy->min, - policy->max, relation); + policy->max, relation, &idx); pr_debug("CPU %u: cur %u kHz -> target %u kHz (req %u kHz, rel %u)\n", policy->cpu, policy->cur, target_freq, old_target_freq, relation); /* - * This might look like a redundant call as we are checking it again - * after finding index. But it is left intentionally for cases where - * exactly same freq is called again and so we can save on few function - * calls. + * Unless invoking the target callback is forced, skip the update if the + * frequency is not really changing. */ - if (target_freq == policy->cur) { - if (!(cpufreq_driver->flags & CPUFREQ_NEED_UPDATE_LIMITS) || - !policy->update_limits) - return 0; - - policy->update_limits = false; + if (policy->force_target) { + /* Avoid forcing the driver callback unnecessarily next time. */ + policy->force_target = false; + } else if (target_freq == policy->cur) { + return 0; } if (cpufreq_driver->target) { @@ -2393,7 +2442,7 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy, if (!cpufreq_driver->target_index) return -EINVAL; - return __target_index(policy, policy->cached_resolved_idx); + return __target_index(policy, idx); } EXPORT_SYMBOL_GPL(__cpufreq_driver_target); @@ -2403,6 +2452,14 @@ int cpufreq_driver_target(struct cpufreq_policy *policy, { guard(cpufreq_policy_write)(policy); + /* + * The last CPU in the policy may have gone offline after the caller + * obtained it, in which case the policy is inactive and must not be + * used for changing the frequency. + */ + if (unlikely(policy_is_inactive(policy))) + return -EBUSY; + return __cpufreq_driver_target(policy, target_freq, relation); } EXPORT_SYMBOL_GPL(cpufreq_driver_target); @@ -2412,12 +2469,17 @@ __weak struct cpufreq_governor *cpufreq_fallback_governor(void) return NULL; } +static bool cpufreq_is_suspended(struct cpufreq_policy *policy) +{ + return cpufreq_suspended || policy->suspended; +} + static int cpufreq_init_governor(struct cpufreq_policy *policy) { int ret; - /* Don't start any governor operations if we are entering suspend */ - if (cpufreq_suspended) + /* Don't start any governor operations for suspended policies. */ + if (cpufreq_is_suspended(policy)) return 0; /* * Governor might not be initiated here if ACPI _PPC changed @@ -2460,7 +2522,7 @@ static int cpufreq_init_governor(struct cpufreq_policy *policy) static void cpufreq_exit_governor(struct cpufreq_policy *policy) { - if (cpufreq_suspended || !policy->governor) + if (cpufreq_is_suspended(policy) || !policy->governor) return; pr_debug("%s: for CPU %u\n", __func__, policy->cpu); @@ -2475,7 +2537,7 @@ int cpufreq_start_governor(struct cpufreq_policy *policy) { int ret; - if (cpufreq_suspended) + if (cpufreq_is_suspended(policy)) return 0; if (!policy->governor) @@ -2499,7 +2561,7 @@ int cpufreq_start_governor(struct cpufreq_policy *policy) void cpufreq_stop_governor(struct cpufreq_policy *policy) { - if (cpufreq_suspended || !policy->governor) + if (cpufreq_is_suspended(policy) || !policy->governor) return; pr_debug("%s: for CPU %u\n", __func__, policy->cpu); @@ -2510,7 +2572,7 @@ void cpufreq_stop_governor(struct cpufreq_policy *policy) static void cpufreq_governor_limits(struct cpufreq_policy *policy) { - if (cpufreq_suspended || !policy->governor) + if (cpufreq_is_suspended(policy) || !policy->governor) return; pr_debug("%s: for CPU %u\n", __func__, policy->cpu); @@ -2630,6 +2692,7 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy, struct cpufreq_governor *new_gov, unsigned int new_pol) { + bool need_update_limits = cpufreq_driver->flags & CPUFREQ_NEED_UPDATE_LIMITS; struct cpufreq_policy_data new_data; struct cpufreq_governor *old_gov; unsigned int freq; @@ -2666,26 +2729,26 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy, * concurrently by cpufreq_driver_resolve_freq() during the update. */ freq = __resolve_freq(policy, new_data.max, new_data.min, new_data.max, - CPUFREQ_RELATION_H); + CPUFREQ_RELATION_H, NULL); if (freq != policy->max) { WRITE_ONCE(policy->max, freq); - policy->update_limits = true; + if (need_update_limits) + policy->force_target = true; } freq = __resolve_freq(policy, new_data.min, new_data.min, new_data.max, - CPUFREQ_RELATION_L); + CPUFREQ_RELATION_L, NULL); freq = min(freq, policy->max); if (freq != policy->min) { WRITE_ONCE(policy->min, freq); - policy->update_limits = true; + if (need_update_limits) + policy->force_target = true; } trace_cpu_frequency_limits(policy); cpufreq_update_pressure(policy); - policy->cached_target_freq = UINT_MAX; - pr_debug("new min and max freqs are %u - %u kHz\n", policy->min, policy->max); @@ -2711,6 +2774,12 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy, cpufreq_exit_governor(policy); } + /* + * Initialize cached_target_freq while no governor is running to avoid + * racing with cpufreq_driver_resolve_freq(), which updates it. + */ + policy->cached_target_freq = UINT_MAX; + /* start new governor */ policy->governor = new_gov; ret = cpufreq_init_governor(policy); @@ -2747,7 +2816,8 @@ static void cpufreq_policy_refresh(struct cpufreq_policy *policy) * -> ask driver for current freq and notify governors about a change */ if (cpufreq_driver->get && has_target() && - (cpufreq_suspended || WARN_ON(!cpufreq_verify_current_freq(policy, false)))) + (cpufreq_is_suspended(policy) || + WARN_ON(!cpufreq_verify_current_freq(policy, false)))) return; refresh_frequency_limits(policy); @@ -2810,18 +2880,24 @@ int cpufreq_boost_set_sw(struct cpufreq_policy *policy, int state) } EXPORT_SYMBOL_GPL(cpufreq_boost_set_sw); +static DEFINE_MUTEX(cpufreq_boost_mutex); + static int cpufreq_boost_trigger_state(int state) { struct cpufreq_policy *policy; unsigned long flags; + bool boost_enabled; int ret = -EOPNOTSUPP; + guard(mutex)(&cpufreq_boost_mutex); + /* * Don't compare 'cpufreq_driver->boost_enabled' with 'state' here to * make sure all policies are in sync with global boost flag. */ write_lock_irqsave(&cpufreq_driver_lock, flags); + boost_enabled = cpufreq_driver->boost_enabled; cpufreq_driver->boost_enabled = state; write_unlock_irqrestore(&cpufreq_driver_lock, flags); @@ -2830,6 +2906,8 @@ static int cpufreq_boost_trigger_state(int state) if (!policy->boost_supported) continue; + guard(cpufreq_policy_write)(policy); + ret = policy_set_boost(policy, state); if (unlikely(ret)) break; @@ -2841,7 +2919,7 @@ static int cpufreq_boost_trigger_state(int state) return 0; write_lock_irqsave(&cpufreq_driver_lock, flags); - cpufreq_driver->boost_enabled = !state; + cpufreq_driver->boost_enabled = boost_enabled; write_unlock_irqrestore(&cpufreq_driver_lock, flags); pr_err("%s: Cannot %s BOOST\n",
diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 0e6ae4c..c78dcd6 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c
@@ -3389,10 +3389,11 @@ static void intel_cpufreq_cpu_exit(struct cpufreq_policy *policy) static int intel_cpufreq_suspend(struct cpufreq_policy *policy) { + struct cpudata *cpu = all_cpu_data[policy->cpu]; + intel_pstate_suspend(policy); if (hwp_active) { - struct cpudata *cpu = all_cpu_data[policy->cpu]; u64 value = READ_ONCE(cpu->hwp_req_cached); /* @@ -3403,6 +3404,17 @@ static int intel_cpufreq_suspend(struct cpufreq_policy *policy) value &= ~HWP_DESIRED_PERF(~0L); wrmsrq_on_cpu(cpu->cpu, MSR_HWP_REQUEST, value); WRITE_ONCE(cpu->hwp_req_cached, value); + } else { + /* + * Invalidate the cached P-state value that may be stale when + * the governor is started again during resume and may prevent + * MSR_IA32_PERF_CTL from being updated. + * + * This does not require synchronization because the core + * prevents frequency updates for suspended policies from + * occurring. + */ + cpu->pstate.current_pstate = 0; } return 0; @@ -3649,12 +3661,12 @@ static struct acpi_platform_list plat_info[] __initdata = { static bool __init intel_pstate_platform_pwr_mgmt_exists(void) { const struct x86_cpu_id *id; - u64 misc_pwr; + u64 misc_pwr = 0; int idx; id = x86_match_cpu(intel_pstate_cpu_oob_ids); if (id) { - rdmsrq(MSR_MISC_PWR_MGMT, misc_pwr); + rdmsrq_safe(MSR_MISC_PWR_MGMT, &misc_pwr); if (misc_pwr & BITMASK_OOB) { pr_debug("Bit 8 or 18 in the MISC_PWR_MGMT MSR set\n"); pr_debug("P states are controlled in Out of Band mode by the firmware/hardware\n");
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 312788f..7270f24 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig
@@ -1,4 +1,39 @@ # SPDX-License-Identifier: GPL-2.0-only +config PLATFORM_PROFILE + tristate "Platform profile support" + help + Provide a common sysfs interface for selecting platform power, + performance and cooling profiles. Platform-specific provider drivers + map their operating modes onto standard profile names, independently + of the firmware interface they use. + + The core exposes per-provider controls under /sys/class/platform-profile/. + A provider driver must register before profile attributes become + available. ACPI is not required. The legacy system-wide interface under + /sys/firmware/acpi/ can be enabled separately for existing userspace. + + To compile this as a module, choose M here: the module will be called + platform_profile. + +config PLATFORM_PROFILE_LEGACY + bool "Legacy ACPI platform profile sysfs interface (DEPRECATED)" + depends on PLATFORM_PROFILE && ACPI + default y + help + Expose platform_profile and platform_profile_choices under + /sys/firmware/acpi/ for compatibility with existing userspace. These + attributes aggregate the profiles of all registered providers. + + This interface is deprecated and is intended for removal after the + 2028 LTS kernel, once userspace has migrated to the class interface. + It remains enabled by default until replacement userspace releases + are available and have had time to reach users. + + New userspace should use the individual devices under + /sys/class/platform-profile/ instead. Disabling this option removes + the legacy sysfs interface but retains class devices and kernel + profile cycling. If unsure, say Y to preserve compatibility. + source "drivers/platform/mips/Kconfig" source "drivers/platform/loongarch/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index fa322e7..b6732f2 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile
@@ -3,6 +3,8 @@ # Makefile for linux/drivers/platform # +obj-$(CONFIG_PLATFORM_PROFILE) += profile/ + obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_LOONGARCH) += loongarch/ obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/
diff --git a/drivers/platform/profile/Makefile b/drivers/platform/profile/Makefile new file mode 100644 index 0000000..787fc9b --- /dev/null +++ b/drivers/platform/profile/Makefile
@@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PLATFORM_PROFILE) += platform_profile.o + +platform_profile-y := core.o +platform_profile-$(CONFIG_PLATFORM_PROFILE_LEGACY) += acpi.o
diff --git a/drivers/platform/profile/acpi.c b/drivers/platform/profile/acpi.c new file mode 100644 index 0000000..ff925de --- /dev/null +++ b/drivers/platform/profile/acpi.c
@@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Compatibility with the original ACPI platform profile sysfs ABI. */ + +#include <linux/acpi.h> +#include <linux/kobject.h> + +#include "profile.h" + +struct kobject *platform_profile_compat_kobj(void) +{ + if (acpi_disabled) + return NULL; + + return kobject_get(acpi_kobj); +}
diff --git a/drivers/acpi/platform_profile.c b/drivers/platform/profile/core.c similarity index 87% rename from drivers/acpi/platform_profile.c rename to drivers/platform/profile/core.c index b4c91d5..148f14a 100644 --- a/drivers/acpi/platform_profile.c +++ b/drivers/platform/profile/core.c
@@ -4,15 +4,21 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/acpi.h> +#include <linux/bitmap.h> #include <linux/bits.h> #include <linux/cleanup.h> #include <linux/init.h> +#include <linux/idr.h> +#include <linux/kobject.h> #include <linux/list.h> +#include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_profile.h> +#include <linux/slab.h> #include <linux/sysfs.h> +#include "profile.h" + #define to_pprof_handler(d) (container_of(d, struct platform_profile_handler, dev)) /* @@ -24,6 +30,19 @@ static DEFINE_MUTEX(profile_lock); /* Serialize registration and sysfs updates without blocking sysfs callbacks. */ static DEFINE_MUTEX(profile_registration_lock); static LIST_HEAD(profile_handlers); +#ifdef CONFIG_PLATFORM_PROFILE_LEGACY +static struct kobject *compat_kobj; +#endif + +static void profile_notify(void) +{ +#ifdef CONFIG_PLATFORM_PROFILE_LEGACY + if (compat_kobj) + sysfs_notify(compat_kobj, NULL, "platform_profile"); +#endif +} + +static int profile_update(void); struct platform_profile_handler { struct list_head node; @@ -241,9 +260,10 @@ static ssize_t profile_store(struct device *dev, ret = _store_class_profile(dev, &index); if (ret) return ret; + _notify_class_profile(dev, NULL); } - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + profile_notify(); return count; } @@ -294,6 +314,12 @@ static int _aggregate_choices(struct device *dev, void *arg) return 0; } +#ifdef CONFIG_PLATFORM_PROFILE_LEGACY +static void profile_legacy_notice(void) +{ + pr_notice_once("The ACPI platform_profile sysfs interface is deprecated and planned for removal after the 2028 LTS kernel once userspace has migrated; use /sys/class/platform-profile/ instead\n"); +} + /** * _remove_hidden_choices - Remove hidden choices from aggregate data * @dev: The device @@ -315,7 +341,7 @@ static int _remove_hidden_choices(struct device *dev, void *arg) } /** - * platform_profile_choices_show - Show the available profile choices for legacy sysfs interface + * platform_profile_choices_show - Show the aggregate profile choices * @kobj: The kobject * @attr: The attribute * @buf: The buffer to write to @@ -332,6 +358,8 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj, }; int err; + profile_legacy_notice(); + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { err = profile_for_each_handler(&data, _aggregate_choices); if (err) @@ -349,9 +377,10 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj, return _commmon_choices_show(data.aggregate, buf); } +#endif /** - * _aggregate_profiles - Aggregate the profiles for legacy sysfs interface + * _aggregate_profiles - Aggregate the profiles across providers * @dev: The device * @data: The profile to return * @@ -376,7 +405,7 @@ static int _aggregate_profiles(struct device *dev, void *data) } /** - * _store_and_notify - Store and notify a class from legacy sysfs interface + * _store_and_notify - Store and notify a provider from an aggregate write * @dev: The device * @data: The profile to return * @@ -393,8 +422,9 @@ static int _store_and_notify(struct device *dev, void *data) return _notify_class_profile(dev, NULL); } +#ifdef CONFIG_PLATFORM_PROFILE_LEGACY /** - * platform_profile_show - Show the current profile for legacy sysfs interface + * platform_profile_show - Show the current aggregate profile * @kobj: The kobject * @attr: The attribute * @buf: The buffer to write to @@ -408,6 +438,8 @@ static ssize_t platform_profile_show(struct kobject *kobj, enum platform_profile_option profile = PLATFORM_PROFILE_LAST; int err; + profile_legacy_notice(); + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { err = profile_for_each_handler(&profile, _aggregate_profiles); if (err) @@ -422,7 +454,7 @@ static ssize_t platform_profile_show(struct kobject *kobj, } /** - * platform_profile_store - Set the profile for legacy sysfs interface + * platform_profile_store - Set the profile across all providers * @kobj: The kobject * @attr: The attribute * @buf: The buffer to read from @@ -441,6 +473,8 @@ static ssize_t platform_profile_store(struct kobject *kobj, int ret; int i; + profile_legacy_notice(); + /* Scan for a matching profile */ i = sysfs_match_string(profile_names, buf); if (i < 0 || i == PLATFORM_PROFILE_CUSTOM) @@ -458,7 +492,7 @@ static ssize_t platform_profile_store(struct kobject *kobj, return ret; } - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + profile_notify(); return count; } @@ -484,8 +518,54 @@ static const struct attribute_group platform_profile_group = { .is_visible = profile_class_is_visible, }; +static int profile_update(void) +{ + if (compat_kobj) + return sysfs_update_group(compat_kobj, &platform_profile_group); + + return 0; +} + +static int profile_compat_init(void) +{ + int err; + + compat_kobj = platform_profile_compat_kobj(); + if (!compat_kobj) + return 0; + + err = sysfs_create_group(compat_kobj, &platform_profile_group); + if (err) + kobject_put(compat_kobj); + + return err; +} + +static void profile_compat_exit(void) +{ + if (compat_kobj) { + sysfs_remove_group(compat_kobj, &platform_profile_group); + kobject_put(compat_kobj); + } +} +#else +static int profile_update(void) +{ + return 0; +} + +static int profile_compat_init(void) +{ + return 0; +} + +static void profile_compat_exit(void) +{ +} +#endif + /** - * platform_profile_notify - Notify class device and legacy sysfs interface + * platform_profile_notify - Notify the provider and aggregate sysfs interfaces * @dev: The class device */ void platform_profile_notify(struct device *dev) @@ -493,7 +573,7 @@ void platform_profile_notify(struct device *dev) scoped_cond_guard(mutex_intr, return, &profile_lock) { _notify_class_profile(dev, NULL); } - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + profile_notify(); } EXPORT_SYMBOL_GPL(platform_profile_notify); @@ -540,7 +620,7 @@ int platform_profile_cycle(void) return err; } - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + profile_notify(); return 0; } @@ -615,9 +695,9 @@ struct device *platform_profile_register(struct device *dev, const char *name, scoped_guard(mutex, &profile_lock) list_add_tail(&to_pprof_handler(ppdev)->node, &profile_handlers); - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + profile_notify(); - err = sysfs_update_group(acpi_kobj, &platform_profile_group); + err = profile_update(); if (err) goto cleanup_cur; @@ -627,6 +707,7 @@ struct device *platform_profile_register(struct device *dev, const char *name, scoped_guard(mutex, &profile_lock) list_del(&to_pprof_handler(ppdev)->node); + profile_update(); device_unregister(ppdev); cleanup_ida: @@ -657,8 +738,8 @@ void platform_profile_remove(struct device *dev) ida_free(&platform_profile_ida, pprof->minor); device_unregister(&pprof->dev); - sysfs_notify(acpi_kobj, NULL, "platform_profile"); - sysfs_update_group(acpi_kobj, &platform_profile_group); + profile_notify(); + profile_update(); } EXPORT_SYMBOL_GPL(platform_profile_remove); @@ -706,14 +787,11 @@ static int __init platform_profile_init(void) { int err; - if (acpi_disabled) - return -EOPNOTSUPP; - err = class_register(&platform_profile_class); if (err) return err; - err = sysfs_create_group(acpi_kobj, &platform_profile_group); + err = profile_compat_init(); if (err) class_unregister(&platform_profile_class); @@ -722,12 +800,12 @@ static int __init platform_profile_init(void) static void __exit platform_profile_exit(void) { - sysfs_remove_group(acpi_kobj, &platform_profile_group); + profile_compat_exit(); class_unregister(&platform_profile_class); } -module_init(platform_profile_init); +subsys_initcall_sync(platform_profile_init); module_exit(platform_profile_exit); MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); -MODULE_DESCRIPTION("ACPI platform profile sysfs interface"); +MODULE_DESCRIPTION("Platform profile sysfs interface"); MODULE_LICENSE("GPL");
diff --git a/drivers/platform/profile/profile.h b/drivers/platform/profile/profile.h new file mode 100644 index 0000000..362eb8cc --- /dev/null +++ b/drivers/platform/profile/profile.h
@@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _PLATFORM_PROFILE_INTERNAL_H +#define _PLATFORM_PROFILE_INTERNAL_H + +#include <linux/kconfig.h> +#include <linux/kobject.h> + +#ifdef CONFIG_PLATFORM_PROFILE_LEGACY +struct kobject *platform_profile_compat_kobj(void); +#else +static inline struct kobject *platform_profile_compat_kobj(void) +{ + return NULL; +} +#endif + +#endif
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index f775c6c..08f15f7 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig
@@ -203,12 +203,12 @@ tristate "Surface Platform Profile Driver" depends on ACPI depends on SURFACE_AGGREGATOR_REGISTRY - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help - Provides support for the ACPI platform profile on 5th- and later + Provides support for platform profiles on 5th- and later generation Microsoft Surface devices. - More specifically, this driver provides ACPI platform profile support + More specifically, this driver provides platform profile support on Microsoft Surface devices with a Surface System Aggregator Module (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In other words, this driver provides platform profile support on the
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9ce384e..4154045 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig
@@ -108,7 +108,7 @@ depends on INPUT depends on LEDS_CLASS depends on POWER_SUPPLY - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select INPUT_SPARSEKMAP help This is a driver for Bitland MiInterface based laptops. @@ -168,7 +168,7 @@ select INPUT_SPARSEKMAP select LEDS_CLASS select NEW_LEDS - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help This is a driver for newer Acer (and Wistron) laptops. It adds wireless radio and bluetooth control, and on some laptops, @@ -279,7 +279,7 @@ select INPUT_SPARSEKMAP select LEDS_CLASS select NEW_LEDS - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new Asus Notebooks). @@ -646,7 +646,7 @@ depends on INPUT depends on LEDS_CLASS depends on SERIO_I8042 - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select FW_ATTR_CLASS help This is a driver for Samsung Galaxy Book series notebooks. It adds @@ -905,7 +905,7 @@ config INSPUR_PLATFORM_PROFILE tristate "Inspur WMI platform profile driver" depends on ACPI_WMI - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help This will allow users to determine and control the platform modes between low-power, balanced and performance modes.
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig index ad4faf1..31e13d6 100644 --- a/drivers/platform/x86/amd/pmf/Kconfig +++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -8,7 +8,7 @@ depends on ACPI && PCI depends on POWER_SUPPLY depends on AMD_NODE - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE depends on TEE && AMDTEE depends on AMD_SFH_HID depends on HAS_IOMEM
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index 9f00160..f6acf1c 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig
@@ -44,7 +44,7 @@ bool "Alienware WMAX WMI device driver" default y depends on ALIENWARE_WMI - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and AWCC thermal control capabilities. @@ -120,7 +120,7 @@ depends on ACPI depends on DMI depends on DELL_SMBIOS - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE help This driver adds support for controlling the fan modes via platform_profile on supported Dell systems regardless of formfactor.
diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig index dd51491..81a5219 100644 --- a/drivers/platform/x86/hp/Kconfig +++ b/drivers/platform/x86/hp/Kconfig
@@ -43,7 +43,7 @@ depends on RFKILL || RFKILL = n select POWER_SUPPLY select INPUT_SPARSEKMAP - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select HWMON help Say Y here if you want to support WMI-based hotkeys on HP laptops and
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 5165949..2291e1d 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig
@@ -12,7 +12,7 @@ depends on BACKLIGHT_CLASS_DEVICE depends on ACPI_VIDEO || ACPI_VIDEO = n depends on ACPI_WMI || ACPI_WMI = n - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select INPUT_SPARSEKMAP select NEW_LEDS select LEDS_CLASS @@ -77,7 +77,7 @@ depends on BACKLIGHT_CLASS_DEVICE depends on I2C depends on DRM - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select DRM_PRIVACY_SCREEN select HWMON select NVRAM @@ -264,7 +264,7 @@ tristate "Lenovo GameZone WMI Driver" depends on ACPI_WMI depends on DMI - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS help
diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c index b7a65e5..3413318 100644 --- a/drivers/powercap/dtpm.c +++ b/drivers/powercap/dtpm.c
@@ -296,7 +296,7 @@ static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power) return 0; } -static struct powercap_zone_constraint_ops constraint_ops = { +static const struct powercap_zone_constraint_ops constraint_ops = { .set_power_limit_uw = set_power_limit_uw, .get_power_limit_uw = get_power_limit_uw, .set_time_window_us = set_time_window_us, @@ -305,7 +305,7 @@ static struct powercap_zone_constraint_ops constraint_ops = { .get_name = get_constraint_name, }; -static struct powercap_zone_ops zone_ops = { +static const struct powercap_zone_ops zone_ops = { .get_max_power_range_uw = get_max_power_range_uw, .get_power_uw = get_power_uw, .release = dtpm_release_zone, @@ -470,7 +470,7 @@ static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy, typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *); -static dtpm_node_callback_t dtpm_node_callback[] = { +static const dtpm_node_callback_t dtpm_node_callback[] = { [DTPM_NODE_VIRTUAL] = dtpm_setup_virtual, [DTPM_NODE_DT] = dtpm_setup_dt, };
diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index 70cab5f..f58649f 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c
@@ -1139,21 +1139,23 @@ static struct rapl_pmu rapl_pmu; /* PMU helpers */ -static void set_pmu_cpumask(struct rapl_package *rp, cpumask_var_t mask) +static int get_pmu_cpu(struct rapl_package *rp) { int cpu; if (!rp->has_pmu) - return; + return nr_cpu_ids; /* Only TPMI & MSR RAPL are supported for now */ if (rp->priv->type != RAPL_IF_TPMI && rp->priv->type != RAPL_IF_MSR) - return; + return nr_cpu_ids; /* TPMI/MSR RAPL uses any CPU in the package for PMU */ for_each_online_cpu(cpu) if (topology_physical_package_id(cpu) == rp->id) - cpumask_set_cpu(cpu, mask); + return cpu; + + return nr_cpu_ids; } static bool is_rp_pmu_cpu(struct rapl_package *rp, int cpu) @@ -1435,6 +1437,7 @@ static ssize_t cpumask_show(struct device *dev, { struct rapl_package *rp; cpumask_var_t cpu_mask; + int cpu; int ret; if (!alloc_cpumask_var(&cpu_mask, GFP_KERNEL)) @@ -1446,7 +1449,9 @@ static ssize_t cpumask_show(struct device *dev, /* Choose a cpu for each RAPL Package */ list_for_each_entry(rp, &rapl_packages, plist) { - set_pmu_cpumask(rp, cpu_mask); + cpu = get_pmu_cpu(rp); + if (cpu < nr_cpu_ids) + cpumask_set_cpu(cpu, cpu_mask); } cpus_read_unlock();
diff --git a/drivers/powercap/powercap_sys.c b/drivers/powercap/powercap_sys.c index 9197fa2..eacbc3c 100644 --- a/drivers/powercap/powercap_sys.c +++ b/drivers/powercap/powercap_sys.c
@@ -398,7 +398,7 @@ static void powercap_release(struct device *dev) kfree(power_zone->name); kfree(power_zone->zone_dev_attrs); kfree(power_zone->constraints); - if (power_zone->ops->release) + if (power_zone->ops && power_zone->ops->release) power_zone->ops->release(power_zone); if (allocated) kfree(power_zone); @@ -555,6 +555,12 @@ struct powercap_zone *powercap_register_zone( power_zone->id); result = device_register(&power_zone->dev); if (result) { + /* + * As on the other failures, the zone is left to the caller: + * the release frees what was allocated here, but must not call + * ->release(), which may free the zone. + */ + power_zone->ops = NULL; put_device(&power_zone->dev); mutex_unlock(&control_type->lock);
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 73ec923..ba8fb1d 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig
@@ -1264,10 +1264,15 @@ config RTC_DRV_EFI tristate "EFI RTC" depends on EFI && !X86 + depends on ACPI_TAD || !ACPI_TAD help If you say yes here you will get support for the EFI Real Time Clock. + On ACPI systems, the ACPI Time and Alarm Device (TAD) driver + is used instead of this one if the TAD can drive the RTC (assuming + it's enabled). + This driver can also be built as a module. If so, the module will be called rtc-efi.
diff --git a/drivers/rtc/rtc-efi.c b/drivers/rtc/rtc-efi.c index a8c18eb..de4821c 100644 --- a/drivers/rtc/rtc-efi.c +++ b/drivers/rtc/rtc-efi.c
@@ -10,6 +10,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/acpi.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/stringify.h> @@ -252,6 +253,9 @@ static int __init efi_rtc_probe(struct platform_device *dev) efi_time_cap_t cap; efi_bool_t enabled, pending; + if (acpi_has_rtc()) + return -ENODEV; + /* First check if the RTC is usable */ if (efi.get_time(&eft, &cap) != EFI_SUCCESS) return -ENODEV;
diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig index 4ced7bdcd..3026a0a 100644 --- a/drivers/thermal/intel/int340x_thermal/Kconfig +++ b/drivers/thermal/intel/int340x_thermal/Kconfig
@@ -12,7 +12,7 @@ select ACPI_THERMAL_LIB select INTEL_SOC_DTS_IOSF_CORE select INTEL_TCC - select ACPI_PLATFORM_PROFILE + select PLATFORM_PROFILE select PROC_THERMAL_MMIO_RAPL if POWERCAP help Newer laptops and tablets that use ACPI may have thermal sensors and
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f59b2de..fad52a9 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h
@@ -327,6 +327,7 @@ struct acpi_device_physical_node { struct device *dev; unsigned int node_id; bool put_online:1; + bool offline_pending:1; }; struct acpi_device_properties {
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 5bcdb0e..2cec54b 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h
@@ -165,6 +165,7 @@ struct cppc_cpudata { struct cppc_perf_fb_ctrs perf_fb_ctrs; unsigned int shared_type; cpumask_var_t shared_cpu_map; + bool perf_limits_valid; }; #ifdef CONFIG_ACPI_CPPC_LIB @@ -194,11 +195,13 @@ extern int cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val); extern int cppc_get_epp_perf(int cpunum, u64 *epp_perf); extern int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable); extern int cppc_set_epp(int cpu, u64 epp_val); +extern int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf); +extern bool cppc_ospm_nominal_perf_supported(int cpu); extern int cppc_get_auto_act_window(int cpu, u64 *auto_act_window); extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window); -extern int cppc_get_auto_sel(int cpu, bool *enable); +extern int cppc_get_auto_sel(int cpu, u64 *enable); bool cppc_auto_sel_is_immutable(int cpu); -extern int cppc_set_auto_sel(int cpu, bool enable); +extern int cppc_set_auto_sel(int cpu, u64 enable); extern int cppc_get_perf_limited(int cpu, u64 *perf_limited); extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear); extern int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf); @@ -289,6 +292,14 @@ static inline int cppc_set_epp(int cpu, u64 epp_val) { return -EOPNOTSUPP; } +static inline int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf) +{ + return -EOPNOTSUPP; +} +static inline bool cppc_ospm_nominal_perf_supported(int cpu) +{ + return false; +} static inline int cppc_get_auto_act_window(int cpu, u64 *auto_act_window) { return -EOPNOTSUPP; @@ -297,7 +308,7 @@ static inline int cppc_set_auto_act_window(int cpu, u64 auto_act_window) { return -EOPNOTSUPP; } -static inline int cppc_get_auto_sel(int cpu, bool *enable) +static inline int cppc_get_auto_sel(int cpu, u64 *enable) { return -EOPNOTSUPP; } @@ -307,7 +318,7 @@ static inline bool cppc_auto_sel_is_immutable(int cpu) return false; } -static inline int cppc_set_auto_sel(int cpu, bool enable) +static inline int cppc_set_auto_sel(int cpu, u64 enable) { return -EOPNOTSUPP; }
diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 9d3f039..30ec68b 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h
@@ -1548,6 +1548,12 @@ extern bool acpi_has_watchdog(void); static inline bool acpi_has_watchdog(void) { return false; } #endif +#if IS_REACHABLE(CONFIG_ACPI_TAD) && IS_ENABLED(CONFIG_RTC_CLASS) +bool acpi_has_rtc(void); +#else +static inline bool acpi_has_rtc(void) { return false; } +#endif + #ifdef CONFIG_ACPI_SPCR_TABLE extern bool qdf2400_e44_present; int acpi_parse_spcr(bool enable_earlycon, bool enable_console);
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index d3d0d9d..be1eaa4 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h
@@ -146,8 +146,11 @@ struct cpufreq_policy { /* Per policy boost supported flag. */ bool boost_supported; - /* Pending policy->min/max update for the driver */ - bool update_limits; + /* Force passing target frequency to the driver. */ + bool force_target; + + /* The policy has been suspended. */ + bool suspended; /* Cached frequency lookup from cpufreq_driver_resolve_freq. */ unsigned int cached_target_freq;
diff --git a/include/linux/platform_profile.h b/include/linux/platform_profile.h index 855b283..f23f982 100644 --- a/include/linux/platform_profile.h +++ b/include/linux/platform_profile.h
@@ -2,6 +2,9 @@ /* * Platform profile sysfs interface * + * Providers may use any firmware interface or implement profiles in software. + * The parent device does not need an ACPI companion or a firmware node. + * * See Documentation/userspace-api/sysfs-platform_profile.rst for more * information. */
diff --git a/tools/power/acpi/Makefile.rules b/tools/power/acpi/Makefile.rules index b71aada..b520a96 100644 --- a/tools/power/acpi/Makefile.rules +++ b/tools/power/acpi/Makefile.rules
@@ -13,22 +13,23 @@ $(ECHO) " STRIP " $(subst $(OUTPUT),,$@) $(QUIET) $(STRIPCMD) $@ -$(KERNEL_INCLUDE): - $(ECHO) " MKDIR " $(subst $(OUTPUT),,$@) +$(KERNEL_INCLUDE)/acpi: FORCE $(QUIET) mkdir -p $(KERNEL_INCLUDE) - $(ECHO) " CP " $(subst $(OUTPUT),,$@) - $(QUIET) cp -rf $(srctree)/../../../include/acpi $(KERNEL_INCLUDE)/ + $(QUIET) rm -rf $@ + $(QUIET) ln -s $(abspath $(srctree)/../../../include/acpi) $@ -$(objdir)%.o: %.c $(KERNEL_INCLUDE) +$(objdir)%.o: %.c | $(KERNEL_INCLUDE)/acpi $(ECHO) " CC " $(subst $(OUTPUT),,$@) $(QUIET) $(MKDIR) -p $(objdir) 2>/dev/null - $(QUIET) $(CC) -c $(CFLAGS) -o $@ $< + $(QUIET) $(CC) -c $(CFLAGS) -MMD -MP -o $@ $< + +-include $(toolobjs:.o=.d) all: $(OUTPUT)$(TOOL) clean: $(ECHO) " RMOBJ " $(subst $(OUTPUT),,$(objdir)) $(QUIET) find $(objdir) \( -not -type d \)\ - -and \( -name '*~' -o -name '*.[oas]' \)\ + -and \( -name '*~' -o -name '*.[oasd]' \)\ -type f -print | xargs rm -f $(ECHO) " RM " $(TOOL) $(QUIET) rm -f $(OUTPUT)$(TOOL)
diff --git a/tools/testing/selftests/cpufreq/special-tests.sh b/tools/testing/selftests/cpufreq/special-tests.sh index e87ed7c..0665da8 100755 --- a/tools/testing/selftests/cpufreq/special-tests.sh +++ b/tools/testing/selftests/cpufreq/special-tests.sh
@@ -16,6 +16,8 @@ source cpufreq.sh source governor.sh +HOTPLUG_PID="" + # Test 1 # $1: policy __simple_lockdep() @@ -92,6 +94,16 @@ restore_all_governors } +hotplug_cleanup() +{ + if [[ -n "$HOTPLUG_PID" ]]; then + kill -s SIGTERM "$HOTPLUG_PID" + wait "$HOTPLUG_PID" + + HOTPLUG_PID="" + fi +} + # Test 4 # $1: cpu hotplug_with_updates_cpu() @@ -101,14 +113,18 @@ # switch to ondemand __switch_governor_for_cpu $1 "ondemand" - for i in `seq 1 5000` - do - reboot_cpu $1 - done & - local freqs=$(cat $filepath/scaling_available_frequencies) local oldfreq=$(cat $filepath/scaling_min_freq) + ( + local quit=0 + trap 'quit=1' SIGTERM + while [ "$quit" -eq 0 ]; do + reboot_cpu "$1" + done + ) & + HOTPLUG_PID=$! + for j in `seq 1 5000` do # Set all frequencies one-by-one @@ -117,14 +133,16 @@ done done + hotplug_cleanup + # restore old freq echo $oldfreq > $filepath/scaling_min_freq } hotplug_with_updates() { + trap 'hotplug_cleanup' EXIT save_all_governors for_each_non_boot_cpu hotplug_with_updates_cpu - wait restore_all_governors }