summaryrefslogtreecommitdiffstats
path: root/drivers/thermal/spacemit
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/thermal/spacemit
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/thermal/spacemit')
-rw-r--r--drivers/thermal/spacemit/Kconfig19
-rw-r--r--drivers/thermal/spacemit/Makefile3
-rw-r--r--drivers/thermal/spacemit/k1_tsensor.c326
3 files changed, 348 insertions, 0 deletions
diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig
new file mode 100644
index 000000000..de7b5ece5
--- /dev/null
+++ b/drivers/thermal/spacemit/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SpacemiT thermal drivers"
+depends on ARCH_SPACEMIT || COMPILE_TEST
+
+config SPACEMIT_K1_TSENSOR
+ tristate "SpacemiT K1 thermal sensor driver"
+ depends on THERMAL_OF
+ help
+ This driver provides support for the thermal sensor
+ integrated in the SpacemiT K1 SoC.
+
+ The thermal sensor monitors temperatures for five thermal zones:
+ soc, package, gpu, cluster0, and cluster1. It supports reporting
+ temperature values and handling high/low threshold interrupts.
+
+ Say Y here if you want to enable thermal monitoring on SpacemiT K1.
+ If compiled as a module, it will be called k1_tsensor.
+
+endmenu
diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile
new file mode 100644
index 000000000..82b30741e
--- /dev/null
+++ b/drivers/thermal/spacemit/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_SPACEMIT_K1_TSENSOR) += k1_tsensor.o
diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c
new file mode 100644
index 000000000..ef91453e5
--- /dev/null
+++ b/drivers/thermal/spacemit/k1_tsensor.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thermal sensor driver for SpacemiT K1 SoC
+ *
+ * Copyright (C) 2026 Shuwei Wu <shuwei.wu@mailbox.org>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#include "../thermal_hwmon.h"
+
+#define K1_TSENSOR_PCTRL_REG 0x00
+#define K1_TSENSOR_PCTRL_ENABLE BIT(0)
+#define K1_TSENSOR_PCTRL_TEMP_MODE BIT(3)
+#define K1_TSENSOR_PCTRL_RAW_SEL BIT(7)
+
+#define K1_TSENSOR_PCTRL_CTUNE GENMASK(11, 8)
+#define K1_TSENSOR_PCTRL_SW_CTRL GENMASK(21, 18)
+#define K1_TSENSOR_PCTRL_HW_AUTO_MODE BIT(23)
+
+#define K1_TSENSOR_EN_REG 0x08
+#define K1_TSENSOR_EN_ALL GENMASK(MAX_SENSOR_NUMBER - 1, 0)
+
+#define K1_TSENSOR_TIME_REG 0x0C
+#define K1_TSENSOR_TIME_WAIT_REF_CNT GENMASK(3, 0)
+#define K1_TSENSOR_TIME_ADC_CNT_RST GENMASK(7, 4)
+#define K1_TSENSOR_TIME_FILTER_PERIOD GENMASK(21, 20)
+#define K1_TSENSOR_TIME_MASK GENMASK(23, 0)
+
+#define K1_TSENSOR_INT_CLR_REG 0x10
+#define K1_TSENSOR_INT_EN_REG 0x14
+#define K1_TSENSOR_INT_STA_REG 0x18
+
+#define K1_TSENSOR_INT_EN_MASK BIT(0)
+#define K1_TSENSOR_INT_MASK(x) (GENMASK(2, 1) << ((x) * 2))
+
+#define K1_TSENSOR_DATA_BASE_REG 0x20
+#define K1_TSENSOR_DATA_REG(x) (K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4)
+#define K1_TSENSOR_DATA_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_DATA_HIGH_MASK GENMASK(31, 16)
+
+#define K1_TSENSOR_THRSH_BASE_REG 0x40
+#define K1_TSENSOR_THRSH_REG(x) (K1_TSENSOR_THRSH_BASE_REG + ((x) * 4))
+#define K1_TSENSOR_THRSH_LOW_MASK GENMASK(15, 0)
+#define K1_TSENSOR_THRSH_HIGH_MASK GENMASK(31, 16)
+
+#define MAX_SENSOR_NUMBER 5
+
+/* Hardware offset value required for temperature calculation */
+#define TEMPERATURE_OFFSET 278
+
+struct k1_tsensor_channel {
+ struct k1_tsensor *ts;
+ struct thermal_zone_device *tzd;
+ int id;
+};
+
+struct k1_tsensor {
+ void __iomem *base;
+ struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER];
+};
+
+static void k1_tsensor_init(struct k1_tsensor *ts)
+{
+ u32 val;
+
+ /* Disable all the interrupts */
+ writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Configure ADC sampling time and filter period */
+ val = readl(ts->base + K1_TSENSOR_TIME_REG);
+ val &= ~K1_TSENSOR_TIME_MASK;
+ val |= K1_TSENSOR_TIME_FILTER_PERIOD |
+ K1_TSENSOR_TIME_ADC_CNT_RST |
+ K1_TSENSOR_TIME_WAIT_REF_CNT;
+ writel(val, ts->base + K1_TSENSOR_TIME_REG);
+
+ /*
+ * Enable all sensors' auto mode, enable dither control,
+ * consecutive mode, and power up sensor.
+ */
+ val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
+ val &= ~K1_TSENSOR_PCTRL_SW_CTRL;
+ val &= ~K1_TSENSOR_PCTRL_CTUNE;
+ val |= K1_TSENSOR_PCTRL_RAW_SEL |
+ K1_TSENSOR_PCTRL_TEMP_MODE |
+ K1_TSENSOR_PCTRL_HW_AUTO_MODE |
+ K1_TSENSOR_PCTRL_ENABLE;
+ writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
+
+ /* Enable each sensor */
+ val = readl(ts->base + K1_TSENSOR_EN_REG);
+ val |= K1_TSENSOR_EN_ALL;
+ writel(val, ts->base + K1_TSENSOR_EN_REG);
+}
+
+static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch)
+{
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ val |= K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_CLR_REG);
+
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val &= ~K1_TSENSOR_INT_MASK(ch->id);
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Enable thermal interrupt */
+ val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
+ val |= K1_TSENSOR_INT_EN_MASK;
+ writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
+}
+
+/*
+ * The conversion formula used is:
+ * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000
+ */
+static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id));
+ if (ch->id % 2)
+ *temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val);
+ else
+ *temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val);
+
+ *temp -= TEMPERATURE_OFFSET;
+ *temp *= 1000;
+
+ return 0;
+}
+
+/*
+ * For each sensor, the hardware threshold register is 32 bits:
+ * - Lower 16 bits [15:0] configure the low threshold temperature.
+ * - Upper 16 bits [31:16] configure the high threshold temperature.
+ */
+static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
+ struct k1_tsensor *ts = ch->ts;
+ u32 val;
+
+ low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK));
+ high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET,
+ FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK));
+ if (low >= high)
+ return -EINVAL;
+
+ val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ val &= ~(K1_TSENSOR_THRSH_LOW_MASK | K1_TSENSOR_THRSH_HIGH_MASK);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low);
+ val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high);
+
+ writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id));
+
+ return 0;
+}
+
+static const struct thermal_zone_device_ops k1_tsensor_ops = {
+ .get_temp = k1_tsensor_get_temp,
+ .set_trips = k1_tsensor_set_trips,
+};
+
+static irqreturn_t k1_tsensor_irq_thread(int irq, void *data)
+{
+ struct k1_tsensor *ts = (struct k1_tsensor *)data;
+ int mask, status, i;
+
+ status = readl(ts->base + K1_TSENSOR_INT_STA_REG);
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; i++) {
+ if (status & K1_TSENSOR_INT_MASK(i)) {
+ mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
+ mask |= K1_TSENSOR_INT_MASK(i);
+ writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG);
+ thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void k1_tsensor_shutdown(struct k1_tsensor *ts)
+{
+ u32 val;
+
+ /* Disable all interrupts */
+ writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
+
+ /* Disable all sensors */
+ val = readl(ts->base + K1_TSENSOR_EN_REG);
+ val &= ~K1_TSENSOR_EN_ALL;
+ writel(val, ts->base + K1_TSENSOR_EN_REG);
+
+ /* Clear the sampling configuration set by k1_tsensor_init() */
+ val = readl(ts->base + K1_TSENSOR_TIME_REG);
+ val &= ~(K1_TSENSOR_TIME_FILTER_PERIOD |
+ K1_TSENSOR_TIME_ADC_CNT_RST |
+ K1_TSENSOR_TIME_WAIT_REF_CNT);
+ writel(val, ts->base + K1_TSENSOR_TIME_REG);
+
+ /* Clear the control bits configured by k1_tsensor_init() */
+ val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
+ val &= ~(K1_TSENSOR_PCTRL_RAW_SEL |
+ K1_TSENSOR_PCTRL_TEMP_MODE |
+ K1_TSENSOR_PCTRL_HW_AUTO_MODE |
+ K1_TSENSOR_PCTRL_ENABLE);
+ writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
+}
+
+static void k1_tsensor_shutdown_action(void *data)
+{
+ k1_tsensor_shutdown(data);
+}
+
+static int k1_tsensor_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct k1_tsensor *ts;
+ struct reset_control *reset;
+ struct clk *clk;
+ int i, irq, ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ ts->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ts->base))
+ return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n");
+
+ reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n");
+
+ clk = devm_clk_get_enabled(dev, "core");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n");
+
+ clk = devm_clk_get_enabled(dev, "bus");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n");
+
+ k1_tsensor_init(ts);
+
+ for (i = 0; i < MAX_SENSOR_NUMBER; ++i) {
+ ts->ch[i].id = i;
+ ts->ch[i].ts = ts;
+ ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops);
+ if (IS_ERR(ts->ch[i].tzd)) {
+ ret = PTR_ERR(ts->ch[i].tzd);
+ goto err_shutdown;
+ }
+
+ /* Attach sysfs hwmon attributes for userspace monitoring */
+ ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd);
+ if (ret)
+ dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto err_shutdown;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ k1_tsensor_irq_thread,
+ IRQF_ONESHOT, "k1_tsensor", ts);
+ if (ret < 0)
+ goto err_shutdown;
+
+ ret = devm_add_action_or_reset(dev, k1_tsensor_shutdown_action, ts);
+ if (ret)
+ return ret;
+
+ /* Enable interrupts only after all zones and the handler are ready */
+ for (i = 0; i < MAX_SENSOR_NUMBER; ++i)
+ k1_tsensor_enable_irq(ts->ch + i);
+
+ platform_set_drvdata(pdev, ts);
+
+ return 0;
+
+err_shutdown:
+ k1_tsensor_shutdown(ts);
+ return ret;
+}
+
+static const struct of_device_id k1_tsensor_dt_ids[] = {
+ { .compatible = "spacemit,k1-tsensor" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids);
+
+static struct platform_driver k1_tsensor_driver = {
+ .driver = {
+ .name = "k1_tsensor",
+ .of_match_table = k1_tsensor_dt_ids,
+ },
+ .probe = k1_tsensor_probe,
+};
+module_platform_driver(k1_tsensor_driver);
+
+MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver");
+MODULE_AUTHOR("Shuwei Wu <shuwei.wu@mailbox.org>");
+MODULE_LICENSE("GPL");