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/input/touchscreen/msg2638.c | 504 ++++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 drivers/input/touchscreen/msg2638.c (limited to 'drivers/input/touchscreen/msg2638.c') diff --git a/drivers/input/touchscreen/msg2638.c b/drivers/input/touchscreen/msg2638.c new file mode 100644 index 000000000..cd40c284d --- /dev/null +++ b/drivers/input/touchscreen/msg2638.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MStar msg2638 touchscreens + * + * Copyright (c) 2021 Vincent Knecht + * + * Checksum and IRQ handler based on mstar_drv_common.c and + * mstar_drv_mutual_fw_control.c + * Copyright (c) 2006-2012 MStar Semiconductor, Inc. + * + * Driver structure based on zinitix.c by Michael Srba + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODE_DATA_RAW 0x5A + +#define MSG2138_MAX_FINGERS 2 +#define MSG2638_MAX_FINGERS 5 + +#define MAX_BUTTONS 4 + +#define CHIP_ON_DELAY_MS 15 +#define FIRMWARE_ON_DELAY_MS 50 +#define RESET_DELAY_MIN_US 10000 +#define RESET_DELAY_MAX_US 11000 + +struct msg_chip_data { + irq_handler_t irq_handler; + unsigned int max_fingers; +}; + +struct msg2138_packet { + u8 xy_hi; /* higher bits of x and y coordinates */ + u8 x_low; + u8 y_low; +}; + +struct msg2138_touch_event { + u8 magic; + struct msg2138_packet pkt[MSG2138_MAX_FINGERS]; + u8 checksum; +}; + +struct msg2638_packet { + u8 xy_hi; /* higher bits of x and y coordinates */ + u8 x_low; + u8 y_low; + u8 pressure; +}; + +struct msg2638_touch_event { + u8 mode; + struct msg2638_packet pkt[MSG2638_MAX_FINGERS]; + u8 proximity; + u8 checksum; +}; + +struct msg2638_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct touchscreen_properties prop; + struct regulator_bulk_data supplies[2]; + struct gpio_desc *reset_gpiod; + int max_fingers; + u32 keycodes[MAX_BUTTONS]; + int num_keycodes; +}; + +static u8 msg2638_checksum(u8 *data, u32 length) +{ + s32 sum = 0; + u32 i; + + for (i = 0; i < length; i++) + sum += data[i]; + + return (u8)((-sum) & 0xFF); +} + +static void msg2138_report_keys(struct msg2638_ts_data *msg2638, u8 keys) +{ + int i; + + /* keys can be 0x00 or 0xff when all keys have been released */ + if (keys == 0xff) + keys = 0; + + for (i = 0; i < msg2638->num_keycodes; ++i) + input_report_key(msg2638->input_dev, msg2638->keycodes[i], + keys & BIT(i)); +} + +static irqreturn_t msg2138_ts_irq_handler(int irq, void *msg2638_handler) +{ + struct msg2638_ts_data *msg2638 = msg2638_handler; + struct i2c_client *client = msg2638->client; + struct input_dev *input = msg2638->input_dev; + struct msg2138_touch_event touch_event; + u32 len = sizeof(touch_event); + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(touch_event), + .buf = (u8 *)&touch_event, + }, + }; + struct msg2138_packet *p0, *p1; + u16 x, y, delta_x, delta_y; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, + "Failed I2C transfer in irq handler: %d\n", + ret < 0 ? ret : -EIO); + goto out; + } + + if (msg2638_checksum((u8 *)&touch_event, len - 1) != + touch_event.checksum) { + dev_err(&client->dev, "Failed checksum!\n"); + goto out; + } + + p0 = &touch_event.pkt[0]; + p1 = &touch_event.pkt[1]; + + /* Ignore non-pressed finger data, but check for key code */ + if (p0->xy_hi == 0xFF && p0->x_low == 0xFF && p0->y_low == 0xFF) { + if (p1->xy_hi == 0xFF && p1->y_low == 0xFF) + msg2138_report_keys(msg2638, p1->x_low); + goto report; + } + + x = ((p0->xy_hi & 0xF0) << 4) | p0->x_low; + y = ((p0->xy_hi & 0x0F) << 8) | p0->y_low; + + input_mt_slot(input, 0); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + touchscreen_report_pos(input, &msg2638->prop, x, y, true); + + /* Ignore non-pressed finger data */ + if (p1->xy_hi == 0xFF && p1->x_low == 0xFF && p1->y_low == 0xFF) + goto report; + + /* Second finger is reported as a delta position */ + delta_x = ((p1->xy_hi & 0xF0) << 4) | p1->x_low; + delta_y = ((p1->xy_hi & 0x0F) << 8) | p1->y_low; + + /* Ignore second finger if both deltas equal 0 */ + if (delta_x == 0 && delta_y == 0) + goto report; + + x += delta_x; + y += delta_y; + + input_mt_slot(input, 1); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + touchscreen_report_pos(input, &msg2638->prop, x, y, true); + +report: + input_mt_sync_frame(msg2638->input_dev); + input_sync(msg2638->input_dev); + +out: + return IRQ_HANDLED; +} + +static irqreturn_t msg2638_ts_irq_handler(int irq, void *msg2638_handler) +{ + struct msg2638_ts_data *msg2638 = msg2638_handler; + struct i2c_client *client = msg2638->client; + struct input_dev *input = msg2638->input_dev; + struct msg2638_touch_event touch_event; + u32 len = sizeof(touch_event); + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(touch_event), + .buf = (u8 *)&touch_event, + }, + }; + struct msg2638_packet *p; + u16 x, y; + int ret; + int i; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(&client->dev, + "Failed I2C transfer in irq handler: %d\n", + ret < 0 ? ret : -EIO); + goto out; + } + + if (touch_event.mode != MODE_DATA_RAW) + goto out; + + if (msg2638_checksum((u8 *)&touch_event, len - 1) != + touch_event.checksum) { + dev_err(&client->dev, "Failed checksum!\n"); + goto out; + } + + for (i = 0; i < msg2638->max_fingers; i++) { + p = &touch_event.pkt[i]; + + /* Ignore non-pressed finger data */ + if (p->xy_hi == 0xFF && p->x_low == 0xFF && p->y_low == 0xFF) + continue; + + x = (((p->xy_hi & 0xF0) << 4) | p->x_low); + y = (((p->xy_hi & 0x0F) << 8) | p->y_low); + + input_mt_slot(input, i); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + touchscreen_report_pos(input, &msg2638->prop, x, y, true); + } + + input_mt_sync_frame(msg2638->input_dev); + input_sync(msg2638->input_dev); + +out: + return IRQ_HANDLED; +} + +static void msg2638_reset(struct msg2638_ts_data *msg2638) +{ + gpiod_set_value_cansleep(msg2638->reset_gpiod, 1); + usleep_range(RESET_DELAY_MIN_US, RESET_DELAY_MAX_US); + gpiod_set_value_cansleep(msg2638->reset_gpiod, 0); + msleep(FIRMWARE_ON_DELAY_MS); +} + +static int msg2638_start(struct msg2638_ts_data *msg2638) +{ + int error; + + error = regulator_bulk_enable(ARRAY_SIZE(msg2638->supplies), + msg2638->supplies); + if (error) { + dev_err(&msg2638->client->dev, + "Failed to enable regulators: %d\n", error); + return error; + } + + msleep(CHIP_ON_DELAY_MS); + + msg2638_reset(msg2638); + + enable_irq(msg2638->client->irq); + + return 0; +} + +static int msg2638_stop(struct msg2638_ts_data *msg2638) +{ + int error; + + disable_irq(msg2638->client->irq); + + error = regulator_bulk_disable(ARRAY_SIZE(msg2638->supplies), + msg2638->supplies); + if (error) { + dev_err(&msg2638->client->dev, + "Failed to disable regulators: %d\n", error); + return error; + } + + return 0; +} + +static int msg2638_input_open(struct input_dev *dev) +{ + struct msg2638_ts_data *msg2638 = input_get_drvdata(dev); + + return msg2638_start(msg2638); +} + +static void msg2638_input_close(struct input_dev *dev) +{ + struct msg2638_ts_data *msg2638 = input_get_drvdata(dev); + + msg2638_stop(msg2638); +} + +static int msg2638_init_input_dev(struct msg2638_ts_data *msg2638) +{ + struct device *dev = &msg2638->client->dev; + struct input_dev *input_dev; + int error; + int i; + + input_dev = devm_input_allocate_device(dev); + if (!input_dev) { + dev_err(dev, "Failed to allocate input device.\n"); + return -ENOMEM; + } + + input_set_drvdata(input_dev, msg2638); + msg2638->input_dev = input_dev; + + input_dev->name = "MStar TouchScreen"; + input_dev->phys = "input/ts"; + input_dev->id.bustype = BUS_I2C; + input_dev->open = msg2638_input_open; + input_dev->close = msg2638_input_close; + + if (msg2638->num_keycodes) { + input_dev->keycode = msg2638->keycodes; + input_dev->keycodemax = msg2638->num_keycodes; + input_dev->keycodesize = sizeof(msg2638->keycodes[0]); + for (i = 0; i < msg2638->num_keycodes; i++) + input_set_capability(input_dev, + EV_KEY, msg2638->keycodes[i]); + } + + input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y); + + touchscreen_parse_properties(input_dev, true, &msg2638->prop); + if (!msg2638->prop.max_x || !msg2638->prop.max_y) { + dev_err(dev, "touchscreen-size-x and/or touchscreen-size-y not set in properties\n"); + return -EINVAL; + } + + error = input_mt_init_slots(input_dev, msg2638->max_fingers, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) { + dev_err(dev, "Failed to initialize MT slots: %d\n", error); + return error; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(dev, "Failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +static int msg2638_ts_probe(struct i2c_client *client) +{ + const struct msg_chip_data *chip_data; + struct device *dev = &client->dev; + struct msg2638_ts_data *msg2638; + int error; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "Failed to assert adapter's support for plain I2C.\n"); + return -ENXIO; + } + + msg2638 = devm_kzalloc(dev, sizeof(*msg2638), GFP_KERNEL); + if (!msg2638) + return -ENOMEM; + + msg2638->client = client; + i2c_set_clientdata(client, msg2638); + + chip_data = device_get_match_data(&client->dev); + if (!chip_data || !chip_data->max_fingers) { + dev_err(dev, "Invalid or missing chip data\n"); + return -EINVAL; + } + + msg2638->max_fingers = chip_data->max_fingers; + + msg2638->supplies[0].supply = "vdd"; + msg2638->supplies[1].supply = "vddio"; + error = devm_regulator_bulk_get(dev, ARRAY_SIZE(msg2638->supplies), + msg2638->supplies); + if (error) { + dev_err(dev, "Failed to get regulators: %d\n", error); + return error; + } + + msg2638->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(msg2638->reset_gpiod)) { + error = PTR_ERR(msg2638->reset_gpiod); + dev_err(dev, "Failed to request reset GPIO: %d\n", error); + return error; + } + + msg2638->num_keycodes = device_property_count_u32(dev, + "linux,keycodes"); + if (msg2638->num_keycodes == -EINVAL) { + msg2638->num_keycodes = 0; + } else if (msg2638->num_keycodes < 0) { + dev_err(dev, "Unable to parse linux,keycodes property: %d\n", + msg2638->num_keycodes); + return msg2638->num_keycodes; + } else if (msg2638->num_keycodes > ARRAY_SIZE(msg2638->keycodes)) { + dev_warn(dev, "Found %d linux,keycodes but max is %zd, ignoring the rest\n", + msg2638->num_keycodes, ARRAY_SIZE(msg2638->keycodes)); + msg2638->num_keycodes = ARRAY_SIZE(msg2638->keycodes); + } + + if (msg2638->num_keycodes > 0) { + error = device_property_read_u32_array(dev, "linux,keycodes", + msg2638->keycodes, + msg2638->num_keycodes); + if (error) { + dev_err(dev, "Unable to read linux,keycodes values: %d\n", + error); + return error; + } + } + + error = devm_request_threaded_irq(dev, client->irq, + NULL, chip_data->irq_handler, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + client->name, msg2638); + if (error) { + dev_err(dev, "Failed to request IRQ: %d\n", error); + return error; + } + + error = msg2638_init_input_dev(msg2638); + if (error) { + dev_err(dev, "Failed to initialize input device: %d\n", error); + return error; + } + + return 0; +} + +static int msg2638_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct msg2638_ts_data *msg2638 = i2c_get_clientdata(client); + + guard(mutex)(&msg2638->input_dev->mutex); + + if (input_device_enabled(msg2638->input_dev)) + msg2638_stop(msg2638); + + return 0; +} + +static int msg2638_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct msg2638_ts_data *msg2638 = i2c_get_clientdata(client); + int error; + + guard(mutex)(&msg2638->input_dev->mutex); + + if (input_device_enabled(msg2638->input_dev)) { + error = msg2638_start(msg2638); + if (error) + return error; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(msg2638_pm_ops, msg2638_suspend, msg2638_resume); + +static const struct msg_chip_data msg2138_data = { + .irq_handler = msg2138_ts_irq_handler, + .max_fingers = MSG2138_MAX_FINGERS, +}; + +static const struct msg_chip_data msg2638_data = { + .irq_handler = msg2638_ts_irq_handler, + .max_fingers = MSG2638_MAX_FINGERS, +}; + +static const struct of_device_id msg2638_of_match[] = { + { .compatible = "mstar,msg2138", .data = &msg2138_data }, + { .compatible = "mstar,msg2638", .data = &msg2638_data }, + { } +}; +MODULE_DEVICE_TABLE(of, msg2638_of_match); + +static struct i2c_driver msg2638_ts_driver = { + .probe = msg2638_ts_probe, + .driver = { + .name = "MStar-TS", + .pm = pm_sleep_ptr(&msg2638_pm_ops), + .of_match_table = msg2638_of_match, + }, +}; +module_i2c_driver(msg2638_ts_driver); + +MODULE_AUTHOR("Vincent Knecht "); +MODULE_DESCRIPTION("MStar MSG2638 touchscreen driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.3.1