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/hwmon/ltc2991.c | |
| 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/hwmon/ltc2991.c')
| -rw-r--r-- | drivers/hwmon/ltc2991.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/drivers/hwmon/ltc2991.c b/drivers/hwmon/ltc2991.c new file mode 100644 index 000000000..bc8d803e8 --- /dev/null +++ b/drivers/hwmon/ltc2991.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Analog Devices, Inc. + * Author: Antoniu Miclaus <antoniu.miclaus@analog.com> + */ + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define LTC2991_STATUS_LOW 0x00 +#define LTC2991_CH_EN_TRIGGER 0x01 +#define LTC2991_V1_V4_CTRL 0x06 +#define LTC2991_V5_V8_CTRL 0x07 +#define LTC2991_PWM_TH_LSB_T_INT 0x08 +#define LTC2991_PWM_TH_MSB 0x09 +#define LTC2991_CHANNEL_V_MSB(x) (0x0A + ((x) * 2)) +#define LTC2991_CHANNEL_T_MSB(x) (0x0A + ((x) * 4)) +#define LTC2991_CHANNEL_C_MSB(x) (0x0C + ((x) * 4)) +#define LTC2991_T_INT_MSB 0x1A +#define LTC2991_VCC_MSB 0x1C + +#define LTC2991_V7_V8_EN BIT(7) +#define LTC2991_V5_V6_EN BIT(6) +#define LTC2991_V3_V4_EN BIT(5) +#define LTC2991_V1_V2_EN BIT(4) +#define LTC2991_T_INT_VCC_EN BIT(3) + +#define LTC2991_V3_V4_FILT_EN BIT(7) +#define LTC2991_V3_V4_TEMP_EN BIT(5) +#define LTC2991_V3_V4_DIFF_EN BIT(4) +#define LTC2991_V1_V2_FILT_EN BIT(3) +#define LTC2991_V1_V2_TEMP_EN BIT(1) +#define LTC2991_V1_V2_DIFF_EN BIT(0) + +#define LTC2991_V7_V8_FILT_EN BIT(7) +#define LTC2991_V7_V8_TEMP_EN BIT(5) +#define LTC2991_V7_V8_DIFF_EN BIT(4) +#define LTC2991_V5_V6_FILT_EN BIT(3) +#define LTC2991_V5_V6_TEMP_EN BIT(1) +#define LTC2991_V5_V6_DIFF_EN BIT(0) + +#define LTC2991_REPEAT_ACQ_EN BIT(4) +#define LTC2991_T_INT_FILT_EN BIT(3) + +#define LTC2991_MAX_CHANNEL 4 +#define LTC2991_T_INT_CH_NR 4 +#define LTC2991_VCC_CH_NR 0 + +struct ltc2991_state { + struct regmap *regmap; + u32 r_sense_uohm[LTC2991_MAX_CHANNEL]; + bool temp_en[LTC2991_MAX_CHANNEL]; +}; + +static int ltc2991_read_reg(struct ltc2991_state *st, u8 addr, u8 reg_len, + int *val) +{ + __be16 regvals; + int ret; + + if (reg_len < 2) + return regmap_read(st->regmap, addr, val); + + ret = regmap_bulk_read(st->regmap, addr, ®vals, reg_len); + if (ret) + return ret; + + *val = be16_to_cpu(regvals); + + return 0; +} + +static int ltc2991_get_voltage(struct ltc2991_state *st, u32 reg, long *val) +{ + int reg_val, ret, offset = 0; + + ret = ltc2991_read_reg(st, reg, 2, ®_val); + if (ret) + return ret; + + if (reg == LTC2991_VCC_MSB) + /* Vcc 2.5V offset */ + offset = 2500; + + /* Vx, 305.18uV/LSB */ + *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 14) * 30518, + 1000 * 100) + offset; + + return 0; +} + +static int ltc2991_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2991_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_in_input: + if (channel == LTC2991_VCC_CH_NR) + reg = LTC2991_VCC_MSB; + else + reg = LTC2991_CHANNEL_V_MSB(channel - 1); + + return ltc2991_get_voltage(st, reg, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2991_get_curr(struct ltc2991_state *st, u32 reg, int channel, + long *val) +{ + int reg_val, ret; + + ret = ltc2991_read_reg(st, reg, 2, ®_val); + if (ret) + return ret; + + /* Vx-Vy, 19.075uV/LSB */ + *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 14) * 19075, + (s32)st->r_sense_uohm[channel]); + + return 0; +} + +static int ltc2991_read_curr(struct device *dev, u32 attr, int channel, + long *val) +{ + struct ltc2991_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_curr_input: + reg = LTC2991_CHANNEL_C_MSB(channel); + return ltc2991_get_curr(st, reg, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2991_get_temp(struct ltc2991_state *st, u32 reg, int channel, + long *val) +{ + int reg_val, ret; + + ret = ltc2991_read_reg(st, reg, 2, ®_val); + if (ret) + return ret; + + /* Temp LSB = 0.0625 Degrees */ + *val = DIV_ROUND_CLOSEST(sign_extend32(reg_val, 12) * 1000, 16); + + return 0; +} + +static int ltc2991_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct ltc2991_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_temp_input: + if (channel == LTC2991_T_INT_CH_NR) + reg = LTC2991_T_INT_MSB; + else + reg = LTC2991_CHANNEL_T_MSB(channel); + + return ltc2991_get_temp(st, reg, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2991_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return ltc2991_read_in(dev, attr, channel, val); + case hwmon_curr: + return ltc2991_read_curr(dev, attr, channel, val); + case hwmon_temp: + return ltc2991_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t ltc2991_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct ltc2991_state *st = data; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + if (channel == LTC2991_VCC_CH_NR) + return 0444; + if (st->temp_en[(channel - 1) / 2]) + break; + if (channel % 2) + return 0444; + if (!st->r_sense_uohm[(channel - 1) / 2]) + return 0444; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + if (st->r_sense_uohm[channel]) + return 0444; + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + if (channel == LTC2991_T_INT_CH_NR || + st->temp_en[channel]) + return 0444; + break; + } + break; + default: + break; + } + + return 0; +} + +static const struct hwmon_ops ltc2991_hwmon_ops = { + .is_visible = ltc2991_is_visible, + .read = ltc2991_read, +}; + +static const struct hwmon_channel_info *ltc2991_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT, + HWMON_T_INPUT + ), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT + ), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT + ), + NULL +}; + +static const struct hwmon_chip_info ltc2991_chip_info = { + .ops = <c2991_hwmon_ops, + .info = ltc2991_info, +}; + +static const struct regmap_config ltc2991_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x1D, +}; + +static int ltc2991_init(struct ltc2991_state *st, struct device *dev) +{ + int ret; + u32 val, addr; + u8 v5_v8_reg_data = 0, v1_v4_reg_data = 0; + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable regulator\n"); + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) + return ret; + + if (addr > 3) + return -EINVAL; + + ret = fwnode_property_read_u32(child, + "shunt-resistor-micro-ohms", + &val); + if (!ret) { + if (!val) + return dev_err_probe(dev, -EINVAL, + "shunt resistor value cannot be zero\n"); + + st->r_sense_uohm[addr] = val; + + switch (addr) { + case 0: + v1_v4_reg_data |= LTC2991_V1_V2_DIFF_EN; + break; + case 1: + v1_v4_reg_data |= LTC2991_V3_V4_DIFF_EN; + break; + case 2: + v5_v8_reg_data |= LTC2991_V5_V6_DIFF_EN; + break; + case 3: + v5_v8_reg_data |= LTC2991_V7_V8_DIFF_EN; + break; + default: + break; + } + } + + ret = fwnode_property_read_bool(child, + "adi,temperature-enable"); + if (ret) { + st->temp_en[addr] = ret; + + switch (addr) { + case 0: + v1_v4_reg_data |= LTC2991_V1_V2_TEMP_EN; + break; + case 1: + v1_v4_reg_data |= LTC2991_V3_V4_TEMP_EN; + break; + case 2: + v5_v8_reg_data |= LTC2991_V5_V6_TEMP_EN; + break; + case 3: + v5_v8_reg_data |= LTC2991_V7_V8_TEMP_EN; + break; + default: + break; + } + } + } + + ret = regmap_write(st->regmap, LTC2991_V5_V8_CTRL, v5_v8_reg_data); + if (ret) + return dev_err_probe(dev, ret, + "Error: Failed to set V5-V8 CTRL reg.\n"); + + ret = regmap_write(st->regmap, LTC2991_V1_V4_CTRL, v1_v4_reg_data); + if (ret) + return dev_err_probe(dev, ret, + "Error: Failed to set V1-V4 CTRL reg.\n"); + + ret = regmap_write(st->regmap, LTC2991_PWM_TH_LSB_T_INT, + LTC2991_REPEAT_ACQ_EN); + if (ret) + return dev_err_probe(dev, ret, + "Error: Failed to set continuous mode.\n"); + + /* Enable all channels and trigger conversions */ + return regmap_write(st->regmap, LTC2991_CH_EN_TRIGGER, + LTC2991_V7_V8_EN | LTC2991_V5_V6_EN | + LTC2991_V3_V4_EN | LTC2991_V1_V2_EN | + LTC2991_T_INT_VCC_EN); +} + +static int ltc2991_i2c_probe(struct i2c_client *client) +{ + int ret; + struct device *hwmon_dev; + struct ltc2991_state *st; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->regmap = devm_regmap_init_i2c(client, <c2991_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ltc2991_init(st, &client->dev); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, + client->name, st, + <c2991_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id ltc2991_of_match[] = { + { .compatible = "adi,ltc2991" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2991_of_match); + +static const struct i2c_device_id ltc2991_i2c_id[] = { + { .name = "ltc2991" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2991_i2c_id); + +static struct i2c_driver ltc2991_i2c_driver = { + .driver = { + .name = "ltc2991", + .of_match_table = ltc2991_of_match, + }, + .probe = ltc2991_i2c_probe, + .id_table = ltc2991_i2c_id, +}; + +module_i2c_driver(ltc2991_i2c_driver); + +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>"); +MODULE_DESCRIPTION("Analog Devices LTC2991 HWMON Driver"); +MODULE_LICENSE("GPL"); |
