From patchwork Tue Nov 29 23:34:17 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Srinivas Pandruvada X-Patchwork-Id: 629356 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 825E5C433FE for ; Tue, 29 Nov 2022 23:34:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229778AbiK2Xei (ORCPT ); Tue, 29 Nov 2022 18:34:38 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35870 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229679AbiK2Xeh (ORCPT ); Tue, 29 Nov 2022 18:34:37 -0500 Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C351A13D46; Tue, 29 Nov 2022 15:34:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1669764876; x=1701300876; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=Hvisxqquf2yYaCVhVJZxHzOaZVsvlPXCmm2MsGkCZx8=; b=YOK9OlxLztQdsHakZ2JkVlgV+INQoIbtgZLvJ9CgrCmIxOh/YYCOh0Yl 0R6JxO9IQUSrO4x+Uz5fmlU7PceH2/LfU6/n9uBwXCxfxf5A69RMZu2dW UD2Y9fqxiMAKvT1BaY3m1oLFzUyrjR/Xpkw8zqpxGc1IaxVYOKsQgCm92 DxBSO278mnVDaNwpzRXZy7JXox7NTHwG6GNOOPL3hVJL7n+3fHwrOTka5 dh+HjaaQte3hyo6UYXo1aq6e0cKOKr7mpSAJo0KIG/KWx31kLKBnzzHMm nEreD7NdDlp3imd1l42pYm/nxJVjyl4X2+pb12Tbvy7SUGChnU1Qpbm8m Q==; X-IronPort-AV: E=McAfee;i="6500,9779,10546"; a="342178170" X-IronPort-AV: E=Sophos;i="5.96,204,1665471600"; d="scan'208";a="342178170" Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 29 Nov 2022 15:34:35 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10546"; a="768617604" X-IronPort-AV: E=Sophos;i="5.96,204,1665471600"; d="scan'208";a="768617604" Received: from spandruv-desk.jf.intel.com ([10.54.75.8]) by orsmga004.jf.intel.com with ESMTP; 29 Nov 2022 15:34:35 -0800 From: Srinivas Pandruvada To: rafael@kernel.org Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, daniel.lezcano@linaro.org, rui.zhang@intel.com, amitk@kernel.org, Srinivas Pandruvada Subject: [PATCH v2 2/4] powercap: idle_inject: Add prepare/complete callbacks Date: Tue, 29 Nov 2022 15:34:17 -0800 Message-Id: <20221129233419.4022830-3-srinivas.pandruvada@linux.intel.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20221129233419.4022830-1-srinivas.pandruvada@linux.intel.com> References: <20221129233419.4022830-1-srinivas.pandruvada@linux.intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The actual idle percentage can be less than the desired because of interrupts. Since the objective for CPU Idle injection is for thermal control, there should be some way to compensate for lost idle percentage. Some architectures provide interface to get actual idle percent observed by the hardware. So, the idle percent can be adjusted using the hardware feedback. For example, Intel CPUs provides package idle counters, which is currently used by intel powerclamp driver to adjust idle time. The only way this can be done currently is by monitoring hardware idle percent from a different software thread. This can be avoided by adding callbacks. Add a capability to register a prepare and complete callback during idle inject registry. Add a new register function idle_inject_register_full() which also allows to register callbacks. If they are not NULL, then prepare callback is called before calling play_idle_precise() and complete callback is called after calling play_idle_precise(). If prepare callback is present and returns non 0 value then play_idle_precise() is not called to avoid over compensation. Signed-off-by: Srinivas Pandruvada --- v2 - Replace begin/end with prepare/complete - Add new interface idle_inject_register_full with callbacks - Update kernel doc - Update commit description drivers/powercap/idle_inject.c | 62 +++++++++++++++++++++++++++++++--- include/linux/idle_inject.h | 4 +++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/drivers/powercap/idle_inject.c b/drivers/powercap/idle_inject.c index dfa989182e71..f48e71501429 100644 --- a/drivers/powercap/idle_inject.c +++ b/drivers/powercap/idle_inject.c @@ -63,13 +63,31 @@ struct idle_inject_thread { * @idle_duration_us: duration of CPU idle time to inject * @run_duration_us: duration of CPU run time to allow * @latency_us: max allowed latency + * @prepare: Callback function which is called before calling + * play_idle_precise() + * @complete: Callback function which is called after calling + * play_idle_precise() * @cpumask: mask of CPUs affected by idle injection + * + * This structure is used to define per instance idle inject device data. Each + * instance has an idle duration, a run duration and mask of CPUs to inject + * idle. + * Actual idle is injected by calling kernel scheduler interface + * play_idle_precise(). There are two optional callbacks which the caller can + * register by calling idle_inject_register_full(): + * prepare() - This callback is called just before calling play_idle_precise() + * If this callback returns non zero value then + * play_idle_precise() is not called. This means skip injecting + * idle during this period. + * complete() - This callback is called after calling play_idle_precise(). */ struct idle_inject_device { struct hrtimer timer; unsigned int idle_duration_us; unsigned int run_duration_us; unsigned int latency_us; + int (*prepare)(unsigned int cpu); + void (*complete)(unsigned int cpu); unsigned long cpumask[]; }; @@ -132,6 +150,7 @@ static void idle_inject_fn(unsigned int cpu) { struct idle_inject_device *ii_dev; struct idle_inject_thread *iit; + int ret; ii_dev = per_cpu(idle_inject_device, cpu); iit = per_cpu_ptr(&idle_inject_thread, cpu); @@ -141,8 +160,18 @@ static void idle_inject_fn(unsigned int cpu) */ iit->should_run = 0; + if (ii_dev->prepare) { + ret = ii_dev->prepare(cpu); + if (ret) + goto skip; + } + play_idle_precise(READ_ONCE(ii_dev->idle_duration_us) * NSEC_PER_USEC, READ_ONCE(ii_dev->latency_us) * NSEC_PER_USEC); + +skip: + if (ii_dev->complete) + ii_dev->complete(cpu); } /** @@ -295,17 +324,23 @@ static int idle_inject_should_run(unsigned int cpu) } /** - * idle_inject_register - initialize idle injection on a set of CPUs + * idle_inject_register_full - initialize idle injection on a set of CPUs * @cpumask: CPUs to be affected by idle injection + * @prepare: callback called before calling play_idle_precise() + * @complete: callback called after calling play_idle_precise() * * This function creates an idle injection control device structure for the - * given set of CPUs and initializes the timer associated with it. It does not - * start any injection cycles. + * given set of CPUs and initializes the timer associated with it. This + * function also allows to register prepare() and complete() callbacks. + * It does not start any injection cycles. * * Return: NULL if memory allocation fails, idle injection control device * pointer on success. */ -struct idle_inject_device *idle_inject_register(struct cpumask *cpumask) + +struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask, + int (*prepare)(unsigned int cpu), + void (*complete)(unsigned int cpu)) { struct idle_inject_device *ii_dev; int cpu, cpu_rb; @@ -318,6 +353,8 @@ struct idle_inject_device *idle_inject_register(struct cpumask *cpumask) hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ii_dev->timer.function = idle_inject_timer_fn; ii_dev->latency_us = UINT_MAX; + ii_dev->prepare = prepare; + ii_dev->complete = complete; for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) { @@ -342,6 +379,23 @@ struct idle_inject_device *idle_inject_register(struct cpumask *cpumask) return NULL; } +EXPORT_SYMBOL_NS_GPL(idle_inject_register_full, IDLE_INJECT); + +/** + * idle_inject_register - initialize idle injection on a set of CPUs + * @cpumask: CPUs to be affected by idle injection + * + * This function creates an idle injection control device structure for the + * given set of CPUs and initializes the timer associated with it. It does not + * start any injection cycles. + * + * Return: NULL if memory allocation fails, idle injection control device + * pointer on success. + */ +struct idle_inject_device *idle_inject_register(struct cpumask *cpumask) +{ + return idle_inject_register_full(cpumask, NULL, NULL); +} EXPORT_SYMBOL_NS_GPL(idle_inject_register, IDLE_INJECT); /** diff --git a/include/linux/idle_inject.h b/include/linux/idle_inject.h index fb88e23a99d3..e18d89793490 100644 --- a/include/linux/idle_inject.h +++ b/include/linux/idle_inject.h @@ -13,6 +13,10 @@ struct idle_inject_device; struct idle_inject_device *idle_inject_register(struct cpumask *cpumask); +struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask, + int (*prepare)(unsigned int cpu), + void (*complete)(unsigned int cpu)); + void idle_inject_unregister(struct idle_inject_device *ii_dev); int idle_inject_start(struct idle_inject_device *ii_dev); From patchwork Tue Nov 29 23:34:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Srinivas Pandruvada X-Patchwork-Id: 629355 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 27084C46467 for ; Tue, 29 Nov 2022 23:34:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229853AbiK2Xel (ORCPT ); Tue, 29 Nov 2022 18:34:41 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35880 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229751AbiK2Xei (ORCPT ); Tue, 29 Nov 2022 18:34:38 -0500 Received: from mga01.intel.com (mga01.intel.com [192.55.52.88]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7D3D4F020; Tue, 29 Nov 2022 15:34:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1669764877; x=1701300877; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=UB+AsI11L5AiZWNtd3SOlQlASLZ7XjEIFfhbV5ckI+E=; b=HuYim+0Qa99/TKxsuDUMKGp6YI+43Yfi0/3UZ0N4okpSye7zuJrYfaDW 7xyyGTZTQBoFcroF10lbPzMkIBOs2PyPiZIyJk/r7OkVrRgnawfh13NGj E1b3zQN7Pjcg14KER4kmMaRZqezAMxrcqxkMQeBPFS7r533VuD6hMaZT1 kY7tW7r4QSsZ6vu/GH6l/3LrpAiw+LrG1TNQc0OvauTsF9ag+nIkmvfRb OntBwKkLMVHerBU8d/PQ5LXpOvN9AGiLRNfiGYd6ZeBY6nOwCaY8a8kPc ZsDEE2cJB2kmcjBe1rNogSWCeGwtVMUVvgpFm0OVLDB/y8CZjs6qR2Ig9 w==; X-IronPort-AV: E=McAfee;i="6500,9779,10546"; a="342178175" X-IronPort-AV: E=Sophos;i="5.96,204,1665471600"; d="scan'208";a="342178175" Received: from orsmga004.jf.intel.com ([10.7.209.38]) by fmsmga101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 29 Nov 2022 15:34:36 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6500,9779,10546"; a="768617611" X-IronPort-AV: E=Sophos;i="5.96,204,1665471600"; d="scan'208";a="768617611" Received: from spandruv-desk.jf.intel.com ([10.54.75.8]) by orsmga004.jf.intel.com with ESMTP; 29 Nov 2022 15:34:35 -0800 From: Srinivas Pandruvada To: rafael@kernel.org Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, daniel.lezcano@linaro.org, rui.zhang@intel.com, amitk@kernel.org, Srinivas Pandruvada Subject: [PATCH v2 4/4] thermal/drivers/intel_cpu_idle_cooling: Introduce Intel cpu idle cooling driver Date: Tue, 29 Nov 2022 15:34:19 -0800 Message-Id: <20221129233419.4022830-5-srinivas.pandruvada@linux.intel.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20221129233419.4022830-1-srinivas.pandruvada@linux.intel.com> References: <20221129233419.4022830-1-srinivas.pandruvada@linux.intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The cpu idle cooling is used to cool down a CPU by injecting idle cycles at runtime. The objective is similar to intel_powerclamp driver, which is used for system wide cooling by injecting idle on each CPU. This driver is modeled after drivers/thermal/cpuidle_cooling.c by reusing powercap/idle_inject framework. On each CPU online a thermal cooling device is registered. The minimum state of the cooling device is 0 and maximum is 100. When user space changes the current state to non zero, then register with idle inject framework and start idle inject. The default idle duration is 24 milli seconds, matching intel_powerclamp, which doesn't change based on the current state of cooling device. The runtime is changed based on the current state. Signed-off-by: Srinivas Pandruvada --- v2: - Removed callback arguments for idle_inject_register drivers/thermal/intel/Kconfig | 10 + drivers/thermal/intel/Makefile | 1 + .../thermal/intel/intel_cpu_idle_cooling.c | 261 ++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 drivers/thermal/intel/intel_cpu_idle_cooling.c diff --git a/drivers/thermal/intel/Kconfig b/drivers/thermal/intel/Kconfig index 6c2a95f41c81..8c88d6e18414 100644 --- a/drivers/thermal/intel/Kconfig +++ b/drivers/thermal/intel/Kconfig @@ -115,3 +115,13 @@ config INTEL_HFI_THERMAL These capabilities may change as a result of changes in the operating conditions of the system such power and thermal limits. If selected, the kernel relays updates in CPUs' capabilities to userspace. + +config INTEL_CPU_IDLE_COOLING + tristate "Intel CPU idle cooling device" + depends on IDLE_INJECT + help + This implements the CPU cooling mechanism through + idle injection. This will throttle the CPU by injecting + idle cycle. + Unlike Intel Power clamp driver, this driver provides + idle injection for each CPU. diff --git a/drivers/thermal/intel/Makefile b/drivers/thermal/intel/Makefile index 9a8d8054f316..8d5f7b5cf9b7 100644 --- a/drivers/thermal/intel/Makefile +++ b/drivers/thermal/intel/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_INTEL_TCC_COOLING) += intel_tcc_cooling.o obj-$(CONFIG_X86_THERMAL_VECTOR) += therm_throt.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_HFI_THERMAL) += intel_hfi.o +obj-$(CONFIG_INTEL_CPU_IDLE_COOLING) += intel_cpu_idle_cooling.o diff --git a/drivers/thermal/intel/intel_cpu_idle_cooling.c b/drivers/thermal/intel/intel_cpu_idle_cooling.c new file mode 100644 index 000000000000..cdd62756cc3d --- /dev/null +++ b/drivers/thermal/intel/intel_cpu_idle_cooling.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Per CPU Idle injection cooling device implementation + * + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Duration match with intel_powerclamp driver */ +#define IDLE_DURATION 24000 +#define IDLE_LATENCY UINT_MAX + +static int idle_duration_us = IDLE_DURATION; +static int idle_latency_us = IDLE_LATENCY; + +module_param(idle_duration_us, int, 0644); +MODULE_PARM_DESC(idle_duration_us, + "Idle duration in us."); + +module_param(idle_latency_us, int, 0644); +MODULE_PARM_DESC(idle_latency_us, + "Idle latency in us."); + +/** + * struct cpuidle_cooling - Per instance data for cooling device + * @cpu: CPU number for this cooling device + * @ii_dev: Idle inject core instance pointer + * @cdev: Thermal core cooling device instance + * @state: Current cooling device state + * + * Stores per instance cooling device state. + */ +struct cpuidle_cooling { + int cpu; + struct idle_inject_device *ii_dev; + struct thermal_cooling_device *cdev; + unsigned long state; +}; + +static DEFINE_PER_CPU(struct cpuidle_cooling, cooling_devs); +static cpumask_t cpuidle_cpu_mask; + +/* Used for module unload protection with idle injection operations */ +static DEFINE_MUTEX(idle_cooling_lock); + +static unsigned int cpuidle_cooling_runtime(unsigned int idle_duration_us, + unsigned long state) +{ + if (!state) + return 0; + + return ((idle_duration_us * 100) / state) - idle_duration_us; +} + +static int cpuidle_idle_injection_register(struct cpuidle_cooling *cooling_dev) +{ + struct idle_inject_device *ii_dev; + + ii_dev = idle_inject_register((struct cpumask *)cpumask_of(cooling_dev->cpu)); + if (!ii_dev) { + /* + * It is busy as some other device claimed idle injection for this CPU + * Also it is possible that memory allocation failure. + */ + pr_err("idle_inject_register failed for cpu:%d\n", cooling_dev->cpu); + return -EAGAIN; + } + + idle_inject_set_duration(ii_dev, TICK_USEC, idle_duration_us); + idle_inject_set_latency(ii_dev, idle_latency_us); + + cooling_dev->ii_dev = ii_dev; + + return 0; +} + +static void cpuidle_idle_injection_unregister(struct cpuidle_cooling *cooling_dev) +{ + idle_inject_unregister(cooling_dev->ii_dev); +} + +static int cpuidle_cooling_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = 100; + + return 0; +} + +static int cpuidle_cooling_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cpuidle_cooling *cooling_dev = cdev->devdata; + + *state = READ_ONCE(cooling_dev->state); + + return 0; +} + +static int cpuidle_cooling_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cpuidle_cooling *cooling_dev = cdev->devdata; + unsigned int runtime_us; + unsigned long curr_state; + int ret = 0; + + mutex_lock(&idle_cooling_lock); + + curr_state = READ_ONCE(cooling_dev->state); + + if (!curr_state && state > 0) { + /* + * This is the first time to start cooling, so register with + * idle injection framework. + */ + if (!cooling_dev->ii_dev) { + ret = cpuidle_idle_injection_register(cooling_dev); + if (ret) + goto unlock_set_state; + } + + runtime_us = cpuidle_cooling_runtime(idle_duration_us, state); + + idle_inject_set_duration(cooling_dev->ii_dev, runtime_us, idle_duration_us); + idle_inject_start(cooling_dev->ii_dev); + } else if (curr_state > 0 && state) { + /* Simply update runtime */ + runtime_us = cpuidle_cooling_runtime(idle_duration_us, state); + idle_inject_set_duration(cooling_dev->ii_dev, runtime_us, idle_duration_us); + } else if (curr_state > 0 && !state) { + idle_inject_stop(cooling_dev->ii_dev); + cpuidle_idle_injection_unregister(cooling_dev); + cooling_dev->ii_dev = NULL; + } + + WRITE_ONCE(cooling_dev->state, state); + +unlock_set_state: + mutex_unlock(&idle_cooling_lock); + + return ret; +} + +/** + * cpuidle_cooling_ops - thermal cooling device ops + */ +static struct thermal_cooling_device_ops cpuidle_cooling_ops = { + .get_max_state = cpuidle_cooling_get_max_state, + .get_cur_state = cpuidle_cooling_get_cur_state, + .set_cur_state = cpuidle_cooling_set_cur_state, +}; + +static int cpuidle_cooling_register(int cpu) +{ + struct cpuidle_cooling *cooling_dev = &per_cpu(cooling_devs, cpu); + struct thermal_cooling_device *cdev; + char name[14]; /* storage for cpuidle-XXXX */ + int ret = 0; + + mutex_lock(&idle_cooling_lock); + + snprintf(name, sizeof(name), "cpuidle-%d", cpu); + cdev = thermal_cooling_device_register(name, cooling_dev, &cpuidle_cooling_ops); + if (IS_ERR(cdev)) { + ret = PTR_ERR(cdev); + goto unlock_register; + } + + cooling_dev->cdev = cdev; + cpumask_set_cpu(cpu, &cpuidle_cpu_mask); + cooling_dev->cpu = cpu; + +unlock_register: + mutex_unlock(&idle_cooling_lock); + + return ret; +} + +static void cpuidle_cooling_unregister(int cpu) +{ + struct cpuidle_cooling *cooling_dev = &per_cpu(cooling_devs, cpu); + + mutex_lock(&idle_cooling_lock); + + if (cooling_dev->state) { + idle_inject_stop(cooling_dev->ii_dev); + cpuidle_idle_injection_unregister(cooling_dev); + } + + thermal_cooling_device_unregister(cooling_dev->cdev); + cooling_dev->state = 0; + + mutex_unlock(&idle_cooling_lock); +} + +static int cpuidle_cooling_cpu_online(unsigned int cpu) +{ + cpuidle_cooling_register(cpu); + + return 0; +} + +static int cpuidle_cooling_cpu_offline(unsigned int cpu) +{ + cpuidle_cooling_unregister(cpu); + + return 0; +} + +static enum cpuhp_state cpuidle_cooling_hp_state __read_mostly; + +static const struct x86_cpu_id intel_cpuidle_cooling_ids[] __initconst = { + X86_MATCH_VENDOR_FEATURE(INTEL, X86_FEATURE_MWAIT, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_cpuidle_cooling_ids); + +static int __init cpuidle_cooling_init(void) +{ + int ret; + + if (!x86_match_cpu(intel_cpuidle_cooling_ids)) + return -ENODEV; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "thermal/cpuidle_cooling:online", + cpuidle_cooling_cpu_online, + cpuidle_cooling_cpu_offline); + if (ret < 0) + return ret; + + cpuidle_cooling_hp_state = ret; + + return 0; +} +module_init(cpuidle_cooling_init) + +static void __exit cpuidle_cooling_exit(void) +{ + cpuhp_remove_state(cpuidle_cooling_hp_state); +} +module_exit(cpuidle_cooling_exit) + +MODULE_IMPORT_NS(IDLE_INJECT); + +MODULE_LICENSE("GPL");