diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/reset/sti | |
| download | linux-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/reset/sti')
| -rw-r--r-- | drivers/reset/sti/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/reset/sti/Makefile | 2 | ||||
| -rw-r--r-- | drivers/reset/sti/reset-stih407.c | 159 | ||||
| -rw-r--r-- | drivers/reset/sti/reset-syscfg.c | 192 | ||||
| -rw-r--r-- | drivers/reset/sti/reset-syscfg.h | 65 |
5 files changed, 425 insertions, 0 deletions
diff --git a/drivers/reset/sti/Kconfig b/drivers/reset/sti/Kconfig new file mode 100644 index 000000000..0b599f7cf --- /dev/null +++ b/drivers/reset/sti/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +if ARCH_STI || COMPILE_TEST + +config STIH407_RESET + bool "STIH407 Reset Driver" if COMPILE_TEST + +endif diff --git a/drivers/reset/sti/Makefile b/drivers/reset/sti/Makefile new file mode 100644 index 000000000..5e833496c --- /dev/null +++ b/drivers/reset/sti/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_STIH407_RESET) += reset-stih407.o reset-syscfg.o diff --git a/drivers/reset/sti/reset-stih407.c b/drivers/reset/sti/reset-stih407.c new file mode 100644 index 000000000..c3bf9a918 --- /dev/null +++ b/drivers/reset/sti/reset-stih407.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014 STMicroelectronics (R&D) Limited + * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <dt-bindings/reset/stih407-resets.h> +#include "reset-syscfg.h" + +/* STiH407 Peripheral powerdown definitions. */ +static const char stih407_core[] = "st,stih407-core-syscfg"; +static const char stih407_sbc_reg[] = "st,stih407-sbc-reg-syscfg"; +static const char stih407_lpm[] = "st,stih407-lpm-syscfg"; + +#define STIH407_PDN_0(_bit) \ + _SYSCFG_RST_CH(stih407_core, SYSCFG_5000, _bit, SYSSTAT_5500, _bit) +#define STIH407_PDN_1(_bit) \ + _SYSCFG_RST_CH(stih407_core, SYSCFG_5001, _bit, SYSSTAT_5501, _bit) +#define STIH407_PDN_ETH(_bit, _stat) \ + _SYSCFG_RST_CH(stih407_sbc_reg, SYSCFG_4032, _bit, SYSSTAT_4520, _stat) + +/* Powerdown requests control 0 */ +#define SYSCFG_5000 0x0 +#define SYSSTAT_5500 0x7d0 +/* Powerdown requests control 1 (High Speed Links) */ +#define SYSCFG_5001 0x4 +#define SYSSTAT_5501 0x7d4 + +/* Ethernet powerdown/status/reset */ +#define SYSCFG_4032 0x80 +#define SYSSTAT_4520 0x820 +#define SYSCFG_4002 0x8 + +static const struct syscfg_reset_channel_data stih407_powerdowns[] = { + [STIH407_EMISS_POWERDOWN] = STIH407_PDN_0(1), + [STIH407_NAND_POWERDOWN] = STIH407_PDN_0(0), + [STIH407_USB3_POWERDOWN] = STIH407_PDN_1(6), + [STIH407_USB2_PORT1_POWERDOWN] = STIH407_PDN_1(5), + [STIH407_USB2_PORT0_POWERDOWN] = STIH407_PDN_1(4), + [STIH407_PCIE1_POWERDOWN] = STIH407_PDN_1(3), + [STIH407_PCIE0_POWERDOWN] = STIH407_PDN_1(2), + [STIH407_SATA1_POWERDOWN] = STIH407_PDN_1(1), + [STIH407_SATA0_POWERDOWN] = STIH407_PDN_1(0), + [STIH407_ETH1_POWERDOWN] = STIH407_PDN_ETH(0, 2), +}; + +/* Reset Generator control 0/1 */ +#define SYSCFG_5128 0x200 +#define SYSCFG_5131 0x20c +#define SYSCFG_5132 0x210 + +#define LPM_SYSCFG_1 0x4 /* Softreset IRB & SBC UART */ + +#define STIH407_SRST_CORE(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_core, _reg, _bit) + +#define STIH407_SRST_SBC(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_sbc_reg, _reg, _bit) + +#define STIH407_SRST_LPM(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_lpm, _reg, _bit) + +static const struct syscfg_reset_channel_data stih407_softresets[] = { + [STIH407_ETH1_SOFTRESET] = STIH407_SRST_SBC(SYSCFG_4002, 4), + [STIH407_MMC1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 3), + [STIH407_USB2_PORT0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 28), + [STIH407_USB2_PORT1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 29), + [STIH407_PICOPHY_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 30), + [STIH407_IRB_SOFTRESET] = STIH407_SRST_LPM(LPM_SYSCFG_1, 6), + [STIH407_PCIE0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 6), + [STIH407_PCIE1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 15), + [STIH407_SATA0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 7), + [STIH407_SATA1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 16), + [STIH407_MIPHY0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 4), + [STIH407_MIPHY1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 13), + [STIH407_MIPHY2_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 22), + [STIH407_SATA0_PWR_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 5), + [STIH407_SATA1_PWR_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 14), + [STIH407_DELTA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 3), + [STIH407_BLITTER_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 10), + [STIH407_HDTVOUT_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 11), + [STIH407_HDQVDP_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 12), + [STIH407_VDP_AUX_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 14), + [STIH407_COMPO_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 15), + [STIH407_HDMI_TX_PHY_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 21), + [STIH407_JPEG_DEC_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 23), + [STIH407_VP8_DEC_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 24), + [STIH407_GPU_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 30), + [STIH407_HVA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 0), + [STIH407_ERAM_HVA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 1), + [STIH407_LPM_SOFTRESET] = STIH407_SRST_SBC(SYSCFG_4002, 2), + [STIH407_KEYSCAN_SOFTRESET] = STIH407_SRST_LPM(LPM_SYSCFG_1, 8), + [STIH407_ST231_AUD_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 26), + [STIH407_ST231_DMU_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 27), + [STIH407_ST231_GP0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 28), + [STIH407_ST231_GP1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5128, 2), +}; + +/* PicoPHY reset/control */ +#define SYSCFG_5061 0x0f4 + +static const struct syscfg_reset_channel_data stih407_picophyresets[] = { + [STIH407_PICOPHY0_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 5), + [STIH407_PICOPHY1_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 6), + [STIH407_PICOPHY2_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 7), +}; + +static const struct syscfg_reset_controller_data stih407_powerdown_controller = { + .wait_for_ack = true, + .nr_channels = ARRAY_SIZE(stih407_powerdowns), + .channels = stih407_powerdowns, +}; + +static const struct syscfg_reset_controller_data stih407_softreset_controller = { + .wait_for_ack = false, + .active_low = true, + .nr_channels = ARRAY_SIZE(stih407_softresets), + .channels = stih407_softresets, +}; + +static const struct syscfg_reset_controller_data stih407_picophyreset_controller = { + .wait_for_ack = false, + .nr_channels = ARRAY_SIZE(stih407_picophyresets), + .channels = stih407_picophyresets, +}; + +static const struct of_device_id stih407_reset_match[] = { + { + .compatible = "st,stih407-powerdown", + .data = &stih407_powerdown_controller, + }, + { + .compatible = "st,stih407-softreset", + .data = &stih407_softreset_controller, + }, + { + .compatible = "st,stih407-picophyreset", + .data = &stih407_picophyreset_controller, + }, + { /* sentinel */ }, +}; + +static struct platform_driver stih407_reset_driver = { + .probe = syscfg_reset_probe, + .driver = { + .name = "reset-stih407", + .of_match_table = stih407_reset_match, + }, +}; + +static int __init stih407_reset_init(void) +{ + return platform_driver_register(&stih407_reset_driver); +} + +arch_initcall(stih407_reset_init); diff --git a/drivers/reset/sti/reset-syscfg.c b/drivers/reset/sti/reset-syscfg.c new file mode 100644 index 000000000..38f78d78f --- /dev/null +++ b/drivers/reset/sti/reset-syscfg.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2013 STMicroelectronics Limited + * Author: Stephen Gallimore <stephen.gallimore@st.com> + * + * Inspired by mach-imx/src.c + */ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include "reset-syscfg.h" + +/** + * struct syscfg_reset_channel - Reset channel regmap configuration + * + * @reset: regmap field for the channel's reset bit. + * @ack: regmap field for the channel's ack bit (optional). + */ +struct syscfg_reset_channel { + struct regmap_field *reset; + struct regmap_field *ack; +}; + +/** + * struct syscfg_reset_controller - A reset controller which groups together + * a set of related reset bits, which may be located in different system + * configuration registers. + * + * @rst: base reset controller structure. + * @active_low: are the resets in this controller active low, i.e. clearing + * the reset bit puts the hardware into reset. + * @channels: An array of reset channels for this controller. + */ +struct syscfg_reset_controller { + struct reset_controller_dev rst; + bool active_low; + struct syscfg_reset_channel channels[]; +}; + +#define to_syscfg_reset_controller(_rst) \ + container_of(_rst, struct syscfg_reset_controller, rst) + +static int syscfg_reset_program_hw(struct reset_controller_dev *rcdev, + unsigned long idx, int assert) +{ + struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev); + const struct syscfg_reset_channel *ch; + u32 ctrl_val = rst->active_low ? !assert : !!assert; + int err; + + if (idx >= rcdev->nr_resets) + return -EINVAL; + + ch = &rst->channels[idx]; + + err = regmap_field_write(ch->reset, ctrl_val); + if (err) + return err; + + if (ch->ack) { + u32 ack_val; + + err = regmap_field_read_poll_timeout(ch->ack, ack_val, (ack_val == ctrl_val), + 100, USEC_PER_SEC); + if (err) + return err; + } + + return 0; +} + +static int syscfg_reset_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, true); +} + +static int syscfg_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, false); +} + +static int syscfg_reset_dev(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + int err; + + err = syscfg_reset_assert(rcdev, idx); + if (err) + return err; + + return syscfg_reset_deassert(rcdev, idx); +} + +static int syscfg_reset_status(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev); + const struct syscfg_reset_channel *ch; + u32 ret_val = 0; + int err; + + if (idx >= rcdev->nr_resets) + return -EINVAL; + + ch = &rst->channels[idx]; + if (ch->ack) + err = regmap_field_read(ch->ack, &ret_val); + else + err = regmap_field_read(ch->reset, &ret_val); + if (err) + return err; + + return rst->active_low ? !ret_val : !!ret_val; +} + +static const struct reset_control_ops syscfg_reset_ops = { + .reset = syscfg_reset_dev, + .assert = syscfg_reset_assert, + .deassert = syscfg_reset_deassert, + .status = syscfg_reset_status, +}; + +static int syscfg_reset_controller_register(struct device *dev, + const struct syscfg_reset_controller_data *data) +{ + struct syscfg_reset_controller *rc; + int i, err; + + rc = devm_kzalloc(dev, struct_size(rc, channels, data->nr_channels), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + rc->rst.ops = &syscfg_reset_ops; + rc->rst.of_node = dev->of_node; + rc->rst.nr_resets = data->nr_channels; + rc->active_low = data->active_low; + + for (i = 0; i < data->nr_channels; i++) { + struct regmap *map; + struct regmap_field *f; + const char *compatible = data->channels[i].compatible; + + map = syscon_regmap_lookup_by_compatible(compatible); + if (IS_ERR(map)) + return PTR_ERR(map); + + f = devm_regmap_field_alloc(dev, map, data->channels[i].reset); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].reset = f; + + if (!data->wait_for_ack) + continue; + + f = devm_regmap_field_alloc(dev, map, data->channels[i].ack); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].ack = f; + } + + err = reset_controller_register(&rc->rst); + if (!err) + dev_info(dev, "registered\n"); + + return err; +} + +int syscfg_reset_probe(struct platform_device *pdev) +{ + struct device *dev = pdev ? &pdev->dev : NULL; + const void *data; + + if (!dev || !dev->driver) + return -ENODEV; + + data = device_get_match_data(&pdev->dev); + if (!data) + return -EINVAL; + + return syscfg_reset_controller_register(dev, data); +} diff --git a/drivers/reset/sti/reset-syscfg.h b/drivers/reset/sti/reset-syscfg.h new file mode 100644 index 000000000..4e3e69de1 --- /dev/null +++ b/drivers/reset/sti/reset-syscfg.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 STMicroelectronics (R&D) Limited + * Author: Stephen Gallimore <stephen.gallimore@st.com> + */ +#ifndef __STI_RESET_SYSCFG_H +#define __STI_RESET_SYSCFG_H + +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +/** + * Reset channel description for a system configuration register based + * reset controller. + * + * @compatible: Compatible string of the syscon regmap containing this + * channel's control and ack (status) bits. + * @reset: Regmap field description of the channel's reset bit. + * @ack: Regmap field description of the channel's acknowledge bit. + */ +struct syscfg_reset_channel_data { + const char *compatible; + struct reg_field reset; + struct reg_field ack; +}; + +#define _SYSCFG_RST_CH(_c, _rr, _rb, _ar, _ab) \ + { .compatible = _c, \ + .reset = REG_FIELD(_rr, _rb, _rb), \ + .ack = REG_FIELD(_ar, _ab, _ab), } + +#define _SYSCFG_RST_CH_NO_ACK(_c, _rr, _rb) \ + { .compatible = _c, \ + .reset = REG_FIELD(_rr, _rb, _rb), } + +/** + * Description of a system configuration register based reset controller. + * + * @wait_for_ack: The controller will wait for reset assert and de-assert to + * be "ack'd" in a channel's ack field. + * @active_low: Are the resets in this controller active low, i.e. clearing + * the reset bit puts the hardware into reset. + * @nr_channels: The number of reset channels in this controller. + * @channels: An array of reset channel descriptions. + */ +struct syscfg_reset_controller_data { + bool wait_for_ack; + bool active_low; + int nr_channels; + const struct syscfg_reset_channel_data *channels; +}; + +/** + * syscfg_reset_probe(): platform device probe function used by syscfg + * reset controller drivers. This registers a reset + * controller configured by the OF match data for + * the compatible device which should be of type + * "struct syscfg_reset_controller_data". + * + * @pdev: platform device + */ +int syscfg_reset_probe(struct platform_device *pdev); + +#endif /* __STI_RESET_SYSCFG_H */ |
