From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions 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) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/pmdomain/tegra/Kconfig | 6 + drivers/pmdomain/tegra/Makefile | 2 + drivers/pmdomain/tegra/powergate-bpmp.c | 369 ++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 drivers/pmdomain/tegra/Kconfig create mode 100644 drivers/pmdomain/tegra/Makefile create mode 100644 drivers/pmdomain/tegra/powergate-bpmp.c (limited to 'drivers/pmdomain/tegra') diff --git a/drivers/pmdomain/tegra/Kconfig b/drivers/pmdomain/tegra/Kconfig new file mode 100644 index 000000000..13ade6d84 --- /dev/null +++ b/drivers/pmdomain/tegra/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config SOC_TEGRA_POWERGATE_BPMP + def_bool y + depends on PM_GENERIC_DOMAINS + depends on TEGRA_BPMP diff --git a/drivers/pmdomain/tegra/Makefile b/drivers/pmdomain/tegra/Makefile new file mode 100644 index 000000000..ec8acfd2c --- /dev/null +++ b/drivers/pmdomain/tegra/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o diff --git a/drivers/pmdomain/tegra/powergate-bpmp.c b/drivers/pmdomain/tegra/powergate-bpmp.c new file mode 100644 index 000000000..e6a7e025b --- /dev/null +++ b/drivers/pmdomain/tegra/powergate-bpmp.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved + */ + +#include +#include +#include +#include + +#include +#include + +struct tegra_powergate_info { + unsigned int id; + char *name; +}; + +struct tegra_powergate { + struct generic_pm_domain genpd; + struct tegra_bpmp *bpmp; + unsigned int id; +}; + +static inline struct tegra_powergate * +to_tegra_powergate(struct generic_pm_domain *genpd) +{ + return container_of(genpd, struct tegra_powergate, genpd); +} + +static int tegra_bpmp_powergate_set_state(struct tegra_bpmp *bpmp, + unsigned int id, u32 state) +{ + struct mrq_pg_request request; + struct tegra_bpmp_message msg; + int err; + + memset(&request, 0, sizeof(request)); + request.cmd = CMD_PG_SET_STATE; + request.id = id; + request.set_state.state = state; + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_PG; + msg.tx.data = &request; + msg.tx.size = sizeof(request); + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + else if (msg.rx.ret < 0) + return -EINVAL; + + return 0; +} + +static int tegra_bpmp_powergate_get_state(struct tegra_bpmp *bpmp, + unsigned int id) +{ + struct mrq_pg_response response; + struct mrq_pg_request request; + struct tegra_bpmp_message msg; + int err; + + memset(&request, 0, sizeof(request)); + request.cmd = CMD_PG_GET_STATE; + request.id = id; + + memset(&response, 0, sizeof(response)); + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_PG; + msg.tx.data = &request; + msg.tx.size = sizeof(request); + msg.rx.data = &response; + msg.rx.size = sizeof(response); + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return PG_STATE_OFF; + else if (msg.rx.ret < 0) + return -EINVAL; + + return response.get_state.state; +} + +static int tegra_bpmp_powergate_get_max_id(struct tegra_bpmp *bpmp) +{ + struct mrq_pg_response response; + struct mrq_pg_request request; + struct tegra_bpmp_message msg; + int err; + + memset(&request, 0, sizeof(request)); + request.cmd = CMD_PG_GET_MAX_ID; + + memset(&response, 0, sizeof(response)); + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_PG; + msg.tx.data = &request; + msg.tx.size = sizeof(request); + msg.rx.data = &response; + msg.rx.size = sizeof(response); + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + else if (msg.rx.ret < 0) + return -EINVAL; + + return response.get_max_id.max_id; +} + +static char *tegra_bpmp_powergate_get_name(struct tegra_bpmp *bpmp, + unsigned int id) +{ + struct mrq_pg_response response; + struct mrq_pg_request request; + struct tegra_bpmp_message msg; + int err; + + memset(&request, 0, sizeof(request)); + request.cmd = CMD_PG_GET_NAME; + request.id = id; + + memset(&response, 0, sizeof(response)); + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_PG; + msg.tx.data = &request; + msg.tx.size = sizeof(request); + msg.rx.data = &response; + msg.rx.size = sizeof(response); + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0 || msg.rx.ret < 0) + return NULL; + + if (response.get_name.name[0] == '\0') + return NULL; + + if (dev_to_node(bpmp->dev) != NUMA_NO_NODE) + return kasprintf(GFP_KERNEL, "%d-%s", dev_to_node(bpmp->dev), + response.get_name.name); + + return kstrdup(response.get_name.name, GFP_KERNEL); +} + +static inline bool tegra_bpmp_powergate_is_powered(struct tegra_bpmp *bpmp, + unsigned int id) +{ + return tegra_bpmp_powergate_get_state(bpmp, id) != PG_STATE_OFF; +} + +static int tegra_powergate_power_on(struct generic_pm_domain *domain) +{ + struct tegra_powergate *powergate = to_tegra_powergate(domain); + struct tegra_bpmp *bpmp = powergate->bpmp; + + return tegra_bpmp_powergate_set_state(bpmp, powergate->id, + PG_STATE_ON); +} + +static int tegra_powergate_power_off(struct generic_pm_domain *domain) +{ + struct tegra_powergate *powergate = to_tegra_powergate(domain); + struct tegra_bpmp *bpmp = powergate->bpmp; + + return tegra_bpmp_powergate_set_state(bpmp, powergate->id, + PG_STATE_OFF); +} + +static struct tegra_powergate * +tegra_powergate_add(struct tegra_bpmp *bpmp, + const struct tegra_powergate_info *info) +{ + struct tegra_powergate *powergate; + bool off; + int err; + + off = !tegra_bpmp_powergate_is_powered(bpmp, info->id); + + powergate = devm_kzalloc(bpmp->dev, sizeof(*powergate), GFP_KERNEL); + if (!powergate) + return ERR_PTR(-ENOMEM); + + powergate->id = info->id; + powergate->bpmp = bpmp; + + powergate->genpd.name = kstrdup(info->name, GFP_KERNEL); + powergate->genpd.power_on = tegra_powergate_power_on; + powergate->genpd.power_off = tegra_powergate_power_off; + powergate->genpd.flags = GENPD_FLAG_NO_STAY_ON; + + err = pm_genpd_init(&powergate->genpd, NULL, off); + if (err < 0) { + kfree(powergate->genpd.name); + return ERR_PTR(err); + } + + return powergate; +} + +static void tegra_powergate_remove(struct tegra_powergate *powergate) +{ + struct generic_pm_domain *genpd = &powergate->genpd; + struct tegra_bpmp *bpmp = powergate->bpmp; + int err; + + err = pm_genpd_remove(genpd); + if (err < 0) + dev_err(bpmp->dev, "failed to remove power domain %s: %d\n", + genpd->name, err); + + kfree(genpd->name); +} + +static int +tegra_bpmp_probe_powergates(struct tegra_bpmp *bpmp, + struct tegra_powergate_info **powergatesp) +{ + struct tegra_powergate_info *powergates; + unsigned int max_id, id, count = 0; + unsigned int num_holes = 0; + int err; + + err = tegra_bpmp_powergate_get_max_id(bpmp); + if (err < 0) + return err; + + max_id = err; + + dev_dbg(bpmp->dev, "maximum powergate ID: %u\n", max_id); + + powergates = kzalloc_objs(*powergates, max_id + 1); + if (!powergates) + return -ENOMEM; + + for (id = 0; id <= max_id; id++) { + struct tegra_powergate_info *info = &powergates[count]; + + info->name = tegra_bpmp_powergate_get_name(bpmp, id); + if (!info->name) { + num_holes++; + continue; + } + + info->id = id; + count++; + } + + dev_dbg(bpmp->dev, "holes: %u\n", num_holes); + + *powergatesp = powergates; + + return count; +} + +static int tegra_bpmp_add_powergates(struct tegra_bpmp *bpmp, + struct tegra_powergate_info *powergates, + unsigned int count) +{ + struct genpd_onecell_data *genpd = &bpmp->genpd; + struct generic_pm_domain **domains; + struct tegra_powergate *powergate; + unsigned int i; + int err; + + domains = kzalloc_objs(*domains, count); + if (!domains) + return -ENOMEM; + + for (i = 0; i < count; i++) { + powergate = tegra_powergate_add(bpmp, &powergates[i]); + if (IS_ERR(powergate)) { + err = PTR_ERR(powergate); + goto remove; + } + + dev_dbg(bpmp->dev, "added power domain %s\n", + powergate->genpd.name); + domains[i] = &powergate->genpd; + } + + genpd->num_domains = count; + genpd->domains = domains; + + return 0; + +remove: + while (i--) { + powergate = to_tegra_powergate(domains[i]); + tegra_powergate_remove(powergate); + } + + kfree(domains); + return err; +} + +static void tegra_bpmp_remove_powergates(struct tegra_bpmp *bpmp) +{ + struct genpd_onecell_data *genpd = &bpmp->genpd; + unsigned int i = genpd->num_domains; + struct tegra_powergate *powergate; + + while (i--) { + dev_dbg(bpmp->dev, "removing power domain %s\n", + genpd->domains[i]->name); + powergate = to_tegra_powergate(genpd->domains[i]); + tegra_powergate_remove(powergate); + } +} + +static struct generic_pm_domain * +tegra_powergate_xlate(const struct of_phandle_args *spec, void *data) +{ + struct generic_pm_domain *domain = ERR_PTR(-ENOENT); + struct genpd_onecell_data *genpd = data; + unsigned int i; + + for (i = 0; i < genpd->num_domains; i++) { + struct tegra_powergate *powergate; + + powergate = to_tegra_powergate(genpd->domains[i]); + if (powergate->id == spec->args[0]) { + domain = &powergate->genpd; + break; + } + } + + return domain; +} + +int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp) +{ + struct device_node *np = bpmp->dev->of_node; + struct tegra_powergate_info *powergates; + struct device *dev = bpmp->dev; + unsigned int count, i; + int err; + + err = tegra_bpmp_probe_powergates(bpmp, &powergates); + if (err < 0) + return err; + + count = err; + + dev_dbg(dev, "%u power domains probed\n", count); + + err = tegra_bpmp_add_powergates(bpmp, powergates, count); + if (err < 0) + goto free; + + bpmp->genpd.xlate = tegra_powergate_xlate; + + err = of_genpd_add_provider_onecell(np, &bpmp->genpd); + if (err < 0) { + dev_err(dev, "failed to add power domain provider: %d\n", err); + tegra_bpmp_remove_powergates(bpmp); + } + +free: + for (i = 0; i < count; i++) + kfree(powergates[i].name); + + kfree(powergates); + return err; +} -- cgit v1.3.1