mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: John Madieu <john.madieu.xa@bp.renesas.com>
To: geert+renesas@glider.be, niklas.soderlund+renesas@ragnatech.se,
	conor+dt@kernel.org, krzk+dt@kernel.org, robh@kernel.org,
	rafael@kernel.org, daniel.lezcano@linaro.org
Cc: magnus.damm@gmail.com, claudiu.beznea.uj@bp.renesas.com,
	devicetree@vger.kernel.org, john.madieu@gmail.com,
	rui.zhang@intel.com, linux-kernel@vger.kernel.org,
	linux-renesas-soc@vger.kernel.org, biju.das.jz@bp.renesas.com,
	linux-pm@vger.kernel.org,
	John Madieu <john.madieu.xa@bp.renesas.com>
Subject: [RFC PATCH 1/3] thermal/cpuplog_cooling: Add CPU hotplug cooling driver
Date: Sun,  9 Mar 2025 13:13:21 +0100	[thread overview]
Message-ID: <20250309121324.29633-2-john.madieu.xa@bp.renesas.com> (raw)
In-Reply-To: <20250309121324.29633-1-john.madieu.xa@bp.renesas.com>

Add thermal cooling mechanism that dynamically manages CPU online/offline
states to prevent overheating. It registers  per-CPU cooling devices that can
take CPUs offline when thermal thresholds are excee and that integrates with
the Linux thermal framework as a cooling devices.

Signed-off-by: John Madieu <john.madieu.xa@bp.renesas.com>
---
 drivers/thermal/Kconfig           |  12 +
 drivers/thermal/Makefile          |   1 +
 drivers/thermal/cpuplug_cooling.c | 363 ++++++++++++++++++++++++++++++
 drivers/thermal/thermal_of.c      |   1 +
 drivers/thermal/thermal_trace.h   |   2 +
 drivers/thermal/thermal_trip.c    |   1 +
 include/uapi/linux/thermal.h      |   1 +
 7 files changed, 381 insertions(+)
 create mode 100644 drivers/thermal/cpuplug_cooling.c

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index d3f9686e26e7..6b0687f0d635 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -202,6 +202,18 @@ config CPU_IDLE_THERMAL
 	  This implements the CPU cooling mechanism through
 	  idle injection. This will throttle the CPU by injecting
 	  idle cycle.
+
+config CPU_HOTPLUG_THERMAL
+	bool "CPU hotplug cooling device"
+	depends on THERMAL
+	depends on HOTPLUG_CPU
+	help
+	  Enable this to manage platform thermals using CPU hotplug.
+	  This can offline CPUs when a temperature threshold is exceeded and
+	  bring them back online when it drops below the reset temperature.
+	  The boot CPU is never offlined.
+
+	  If in doubt, say N.
 endif
 
 config DEVFREQ_THERMAL
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 9abf43a74f2b..7b3648daabd2 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -28,6 +28,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR)	+= gov_power_allocator.o
 # cpufreq cooling
 thermal_sys-$(CONFIG_CPU_FREQ_THERMAL)	+= cpufreq_cooling.o
 thermal_sys-$(CONFIG_CPU_IDLE_THERMAL)	+= cpuidle_cooling.o
+thermal_sys-$(CONFIG_CPU_HOTPLUG_THERMAL)	+= cpuplug_cooling.o
 
 # devfreq cooling
 thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
diff --git a/drivers/thermal/cpuplug_cooling.c b/drivers/thermal/cpuplug_cooling.c
new file mode 100644
index 000000000000..1f62325f0665
--- /dev/null
+++ b/drivers/thermal/cpuplug_cooling.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CPU Hotplug Thermal Cooling Device
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+#define pr_fmt(fmt) "cpu-hotplug-thermal: " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+#define COOLING_DEVICE_NAME "cpu-hotplug"
+
+/* CPU Hotplug cooling device private data structure */
+struct cpu_hotplug_cooling_device {
+	struct thermal_cooling_device *cdev;
+	int cpu_id;
+	unsigned long cur_state;
+	bool cpu_online;
+	struct list_head node;
+};
+
+static LIST_HEAD(cooling_devices);
+static DEFINE_MUTEX(cooling_list_lock);
+
+/* Track which CPUs already have cooling devices */
+static DECLARE_BITMAP(cpu_cooling_registered, NR_CPUS);
+
+static inline bool is_boot_cpu(unsigned int cpu)
+{
+	return cpu == cpumask_first(cpu_online_mask);
+}
+
+static int cpu_hotplug_get_max_state(struct thermal_cooling_device *cdev,
+				     unsigned long *state)
+{
+	*state = 1; /* We only have two states: on/off */
+	return 0;
+}
+
+/* Get current cooling state */
+static int cpu_hotplug_get_cur_state(struct thermal_cooling_device *cdev,
+				     unsigned long *state)
+{
+	struct cpu_hotplug_cooling_device *hotplug_cdev = cdev->devdata;
+
+	*state = hotplug_cdev->cur_state;
+	return 0;
+}
+
+static int cpu_hotplug_set_cur_state(struct thermal_cooling_device *cdev,
+				     unsigned long state)
+{
+	struct cpu_hotplug_cooling_device *hotplug_cdev = cdev->devdata;
+	int cpu, ret = 0;
+
+	/* Only take action if state has changed */
+	if (hotplug_cdev->cur_state == state)
+		return 0;
+
+	/* Store the current state */
+	hotplug_cdev->cur_state = state;
+	cpu = hotplug_cdev->cpu_id;
+
+	/* Skip if it's the boot CPU */
+	if (is_boot_cpu(cpu))
+		return 0;
+
+	if (state == 0) {
+		/* Cooling off - bring CPU online if it's offline */
+		if (!cpu_online(cpu)) {
+			pr_info("CPU%d coming back online\n", cpu);
+			ret = add_cpu(cpu);
+			if (ret)
+				pr_err("Failed to bring CPU%d online: %d\n", cpu, ret);
+			else
+				hotplug_cdev->cpu_online = true;
+		}
+	} else {
+		/* Cooling on - take CPU offline if it's online */
+		if (cpu_online(cpu)) {
+			pr_info("CPU%d going offline due to overheating\n", cpu);
+			ret = remove_cpu(cpu);
+			if (ret)
+				pr_err("Failed to offline CPU%d: %d\n", cpu, ret);
+			else
+				hotplug_cdev->cpu_online = false;
+		}
+	}
+
+	return 0;
+}
+
+static const struct thermal_cooling_device_ops cpu_hotplug_cooling_ops = {
+	.get_max_state = cpu_hotplug_get_max_state,
+	.get_cur_state = cpu_hotplug_get_cur_state,
+	.set_cur_state = cpu_hotplug_set_cur_state,
+};
+
+static int register_cpu_hotplug_cooling(struct device_node *cpu_node,
+					int cpu_id)
+{
+	struct cpu_hotplug_cooling_device *hotplug_cdev;
+	struct thermal_cooling_device *cdev;
+	char cooling_name[32];
+
+	/* Check if we already registered this CPU */
+	if (test_bit(cpu_id, cpu_cooling_registered)) {
+		pr_info("Cooling device already registered for CPU%d\n", cpu_id);
+		return 0;
+	}
+
+	/* Skip the boot CPU */
+	if (is_boot_cpu(cpu_id)) {
+		pr_info("Skipping boot CPU%d for hotplug cooling\n", cpu_id);
+		return 0;
+	}
+
+	hotplug_cdev = kzalloc(sizeof(*hotplug_cdev), GFP_KERNEL);
+	if (!hotplug_cdev) {
+		pr_err("Failed to allocate memory for cooling device\n");
+		return -ENOMEM;
+	}
+
+	/* Initialize cooling device */
+	hotplug_cdev->cpu_id = cpu_id;
+	hotplug_cdev->cur_state = 0;
+	hotplug_cdev->cpu_online = cpu_online(cpu_id);
+
+	/* Unique cooling device name */
+	snprintf(cooling_name, sizeof(cooling_name), "%s-%d",
+		 COOLING_DEVICE_NAME, hotplug_cdev->cpu_id);
+
+	/* Register cooling device with a unique name - using CPU node */
+	cdev = thermal_of_cooling_device_register(
+		cpu_node, cooling_name, hotplug_cdev, &cpu_hotplug_cooling_ops);
+	if (IS_ERR(cdev)) {
+		pr_err("Failed to register %s: %ld\n", cooling_name,
+		       PTR_ERR(cdev));
+		kfree(hotplug_cdev);
+		return PTR_ERR(cdev);
+	}
+
+	hotplug_cdev->cdev = cdev;
+
+	/* Mark this CPU as having a registered cooling device */
+	set_bit(cpu_id, cpu_cooling_registered);
+
+	/* Add to our list for cleanup later */
+	mutex_lock(&cooling_list_lock);
+	list_add(&hotplug_cdev->node, &cooling_devices);
+	mutex_unlock(&cooling_list_lock);
+
+	pr_info("Successfully registered %s for CPU%d\n", cooling_name,
+		hotplug_cdev->cpu_id);
+
+	return 0;
+}
+
+/* Cleanup all cooling devices */
+static void cleanup_cooling_devices(void)
+{
+	struct cpu_hotplug_cooling_device *hotplug_cdev, *next;
+
+	mutex_lock(&cooling_list_lock);
+	list_for_each_entry_safe(hotplug_cdev, next, &cooling_devices, node) {
+		pr_info("Unregistering cooling device for CPU%d\n",
+			hotplug_cdev->cpu_id);
+
+		/* Clear the registration bit */
+		clear_bit(hotplug_cdev->cpu_id, cpu_cooling_registered);
+
+		/* Remove from list */
+		list_del(&hotplug_cdev->node);
+
+		/* Unregister cooling device */
+		thermal_cooling_device_unregister(hotplug_cdev->cdev);
+
+		/* Make sure CPU is back online */
+		if (!hotplug_cdev->cpu_online) {
+			int cpu = hotplug_cdev->cpu_id;
+			if (!is_boot_cpu(cpu) && !cpu_online(cpu)) {
+				pr_info("Bringing CPU%d back online during module unload\n", cpu);
+				if (add_cpu(cpu))
+					pr_err("Failed to bring CPU%d online\n", cpu);
+			}
+		}
+
+		/* Free memory */
+		kfree(hotplug_cdev);
+	}
+	mutex_unlock(&cooling_list_lock);
+}
+
+/* Check if a trip point is of type "plug" */
+static bool is_plug_trip_point(struct device_node *trip_node)
+{
+	const char *trip_type_str;
+
+	if (!trip_node) {
+		pr_err("Trip node is NULL\n");
+		return false;
+	}
+
+	if (of_property_read_string(trip_node, "type", &trip_type_str)) {
+		pr_err("Trip node missing 'type' property\n");
+		return false;
+	}
+
+	pr_info("Trip type: '%s'\n", trip_type_str);
+
+	if (strcmp(trip_type_str, "plug") != 0) {
+		pr_debug("Trip type is '%s', not 'plug' - skipping\n",
+			 trip_type_str);
+		return false;
+	}
+
+	return true;
+}
+
+/* Init function */
+static int __init cpu_hotplug_cooling_init(void)
+{
+	struct device_node *thermal_zones, *thermal_zone;
+	int ret = 0;
+	int count = 0;
+
+	bitmap_zero(cpu_cooling_registered, NR_CPUS);
+
+	thermal_zones = of_find_node_by_name(NULL, "thermal-zones");
+	if (!thermal_zones) {
+		pr_err("Missing thermal-zones node\n");
+		return -EINVAL;
+	}
+
+	/* Process each thermal zone */
+	for_each_child_of_node(thermal_zones, thermal_zone) {
+		struct device_node *trips, *trip;
+		struct device_node *maps, *map;
+		bool found_plug = false;
+
+		/* First find trips and get a specific plug trip */
+		trips = of_find_node_by_name(thermal_zone, "trips");
+		if (!trips)
+			continue;
+
+		/* Find the emergency trip with type="plug" */
+		for_each_child_of_node(trips, trip) {
+			if (is_plug_trip_point(trip)) {
+				found_plug = true;
+				break;
+			}
+		}
+
+		/* If we didn't find a plug trip, no need to process this zone */
+		if (!found_plug) {
+			of_node_put(trips);
+			continue;
+		}
+
+		maps = of_find_node_by_name(thermal_zone, "cooling-maps");
+		if (!maps) {
+			of_node_put(trip);
+			of_node_put(trips);
+			continue;
+		}
+
+		pr_info("Found 'plug' trip point, processing cooling devices\n");
+
+		/* Find the specific cooling map that references our plug trip */
+		for_each_child_of_node(maps, map) {
+			struct device_node *trip_ref;
+			struct of_phandle_args cooling_spec;
+			int idx = 0;
+
+			trip_ref = of_parse_phandle(map, "trip", 0);
+			if (!trip_ref || trip_ref != trip) {
+				if (trip_ref)
+					of_node_put(trip_ref);
+				continue;
+			}
+			of_node_put(trip_ref);
+
+			if (!of_find_property(map, "cooling-device", NULL)) {
+				pr_err("Missing cooling-device property\n");
+				continue;
+			}
+
+			/* Iterate through all cooling-device entries */
+			while (of_parse_phandle_with_args(
+				       map, "cooling-device",
+				       "#cooling-cells", idx++,
+				       &cooling_spec) == 0) {
+				struct device_node *cpu_node = cooling_spec.np;
+				int cpu;
+
+				if (!cpu_node) {
+					pr_err("CPU node at index %d is NULL\n",
+					       idx - 1);
+					continue;
+				}
+
+				cpu = of_cpu_node_to_id(cpu_node);
+				if (cpu < 0) {
+					pr_err("Failed to map CPU node %pOF to logical ID\n",
+					       cpu_node);
+					of_node_put(cpu_node);
+					continue;
+				}
+
+				if (cpu >= num_possible_cpus()) {
+					pr_err("Invalid CPU ID %d (max %d)\n",
+					       cpu, num_possible_cpus() - 1);
+					of_node_put(cpu_node);
+					continue;
+				}
+
+				pr_info("Processing cooling device for CPU%d\n", cpu);
+				ret = register_cpu_hotplug_cooling(cpu_node, cpu);
+				if (ret == 0)
+					count++;
+
+				of_node_put(cpu_node);
+			}
+			break; /* Only process the first map that references our trip */
+		}
+		of_node_put(maps);
+		of_node_put(trip);
+		of_node_put(trips);
+	}
+	of_node_put(thermal_zones);
+
+	if (count == 0) {
+		pr_err("No cooling devices registered\n");
+		return -ENODEV;
+	}
+
+	pr_info("CPU hotplug cooling driver initialized with %d devices\n", count);
+	return 0;
+}
+
+/* Exit function */
+static void __exit cpu_hotplug_cooling_exit(void)
+{
+	cleanup_cooling_devices();
+	pr_info("CPU hotplug cooling driver removed\n");
+}
+
+module_init(cpu_hotplug_cooling_init);
+module_exit(cpu_hotplug_cooling_exit);
+
+MODULE_AUTHOR("John Madieu <john.madieu.xa@bp.renesas.com>");
+MODULE_DESCRIPTION("CPU Hotplug Thermal Cooling Device");
+MODULE_LICENSE("GPL");
\ No newline at end of file
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
index 0eb92d57a1e2..41655af1e419 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -28,6 +28,7 @@ static const char * const trip_types[] = {
 	[THERMAL_TRIP_ACTIVE]	= "active",
 	[THERMAL_TRIP_PASSIVE]	= "passive",
 	[THERMAL_TRIP_HOT]	= "hot",
+	[THERMAL_TRIP_PLUG]	= "plug",
 	[THERMAL_TRIP_CRITICAL]	= "critical",
 };
 
diff --git a/drivers/thermal/thermal_trace.h b/drivers/thermal/thermal_trace.h
index df8f4edd6068..c26a3aa7de5f 100644
--- a/drivers/thermal/thermal_trace.h
+++ b/drivers/thermal/thermal_trace.h
@@ -12,6 +12,7 @@
 #include "thermal_core.h"
 
 TRACE_DEFINE_ENUM(THERMAL_TRIP_CRITICAL);
+TRACE_DEFINE_ENUM(THERMAL_TRIP_PLUG);
 TRACE_DEFINE_ENUM(THERMAL_TRIP_HOT);
 TRACE_DEFINE_ENUM(THERMAL_TRIP_PASSIVE);
 TRACE_DEFINE_ENUM(THERMAL_TRIP_ACTIVE);
@@ -19,6 +20,7 @@ TRACE_DEFINE_ENUM(THERMAL_TRIP_ACTIVE);
 #define show_tzt_type(type)					\
 	__print_symbolic(type,					\
 			 { THERMAL_TRIP_CRITICAL, "CRITICAL"},	\
+			 { THERMAL_TRIP_PLUG,     "PLUG"},	\
 			 { THERMAL_TRIP_HOT,      "HOT"},	\
 			 { THERMAL_TRIP_PASSIVE,  "PASSIVE"},	\
 			 { THERMAL_TRIP_ACTIVE,   "ACTIVE"})
diff --git a/drivers/thermal/thermal_trip.c b/drivers/thermal/thermal_trip.c
index 4b8238468b53..373f6aaaf0da 100644
--- a/drivers/thermal/thermal_trip.c
+++ b/drivers/thermal/thermal_trip.c
@@ -13,6 +13,7 @@ static const char *trip_type_names[] = {
 	[THERMAL_TRIP_ACTIVE] = "active",
 	[THERMAL_TRIP_PASSIVE] = "passive",
 	[THERMAL_TRIP_HOT] = "hot",
+	[THERMAL_TRIP_PLUG]	= "plug",
 	[THERMAL_TRIP_CRITICAL] = "critical",
 };
 
diff --git a/include/uapi/linux/thermal.h b/include/uapi/linux/thermal.h
index 46a2633d33aa..5f76360c6f69 100644
--- a/include/uapi/linux/thermal.h
+++ b/include/uapi/linux/thermal.h
@@ -15,6 +15,7 @@ enum thermal_trip_type {
 	THERMAL_TRIP_ACTIVE = 0,
 	THERMAL_TRIP_PASSIVE,
 	THERMAL_TRIP_HOT,
+	THERMAL_TRIP_PLUG,
 	THERMAL_TRIP_CRITICAL,
 };
 
-- 
2.25.1


  reply	other threads:[~2025-03-09 12:13 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-03-09 12:13 [RFC PATCH 0/3] thermal: " John Madieu
2025-03-09 12:13 ` John Madieu [this message]
2025-03-11  7:30   ` [RFC PATCH 1/3] thermal/cpuplog_cooling: " Krzysztof Kozlowski
2025-03-11 11:38     ` John Madieu
2025-03-11  8:28   ` Geert Uytterhoeven
2025-03-11 11:41     ` John Madieu
2025-03-09 12:13 ` [RFC PATCH 2/3] tmon: Add support for THERMAL_TRIP_PLUG type John Madieu
2025-03-09 12:13 ` [RFC PATCH 3/3] arm64: dts: renesas: r9a09g047: Add thermal hotplug trip point John Madieu
2025-03-11 10:53   ` Christian Loehle
2025-03-11 11:57     ` John Madieu
2025-03-11 15:29       ` Christian Loehle
2025-03-10 10:17 ` [RFC PATCH 0/3] thermal: Add CPU hotplug cooling driver Biju Das
2025-03-11 11:33   ` John Madieu
2025-03-15 14:40 ` Rafael J. Wysocki

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250309121324.29633-2-john.madieu.xa@bp.renesas.com \
    --to=john.madieu.xa@bp.renesas.com \
    --cc=biju.das.jz@bp.renesas.com \
    --cc=claudiu.beznea.uj@bp.renesas.com \
    --cc=conor+dt@kernel.org \
    --cc=daniel.lezcano@linaro.org \
    --cc=devicetree@vger.kernel.org \
    --cc=geert+renesas@glider.be \
    --cc=john.madieu@gmail.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-renesas-soc@vger.kernel.org \
    --cc=magnus.damm@gmail.com \
    --cc=niklas.soderlund+renesas@ragnatech.se \
    --cc=rafael@kernel.org \
    --cc=robh@kernel.org \
    --cc=rui.zhang@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®