summaryrefslogtreecommitdiffstats
path: root/drivers/i2c/i2c-slave-eeprom.c
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/i2c/i2c-slave-eeprom.c
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/i2c/i2c-slave-eeprom.c')
-rw-r--r--drivers/i2c/i2c-slave-eeprom.c218
1 files changed, 218 insertions, 0 deletions
diff --git a/drivers/i2c/i2c-slave-eeprom.c b/drivers/i2c/i2c-slave-eeprom.c
new file mode 100644
index 000000000..226d722af
--- /dev/null
+++ b/drivers/i2c/i2c-slave-eeprom.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C slave mode EEPROM simulator
+ *
+ * Copyright (C) 2014 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
+ * Copyright (C) 2014 by Renesas Electronics Corporation
+ *
+ * Because most slave IP cores can only detect one I2C slave address anyhow,
+ * this driver does not support simulating EEPROM types which take more than
+ * one address.
+ */
+
+/*
+ * FIXME: What to do if only 8 bits of a 16 bit address are sent?
+ * The ST-M24C64 sends only 0xff then. Needs verification with other
+ * EEPROMs, though. We currently use the 8 bit as a valid address.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+
+struct eeprom_data {
+ struct bin_attribute bin;
+ spinlock_t buffer_lock;
+ u16 buffer_idx;
+ u16 address_mask;
+ u8 num_address_bytes;
+ u8 idx_write_cnt;
+ bool read_only;
+ u8 buffer[];
+};
+
+#define I2C_SLAVE_BYTELEN GENMASK(15, 0)
+#define I2C_SLAVE_FLAG_ADDR16 BIT(16)
+#define I2C_SLAVE_FLAG_RO BIT(17)
+#define I2C_SLAVE_DEVICE_MAGIC(_len, _flags) ((_flags) | ((_len) - 1))
+
+static int i2c_slave_eeprom_slave_cb(struct i2c_client *client,
+ enum i2c_slave_event event, u8 *val)
+{
+ struct eeprom_data *eeprom = i2c_get_clientdata(client);
+
+ switch (event) {
+ case I2C_SLAVE_WRITE_RECEIVED:
+ if (eeprom->idx_write_cnt < eeprom->num_address_bytes) {
+ if (eeprom->idx_write_cnt == 0)
+ eeprom->buffer_idx = 0;
+ eeprom->buffer_idx = *val | (eeprom->buffer_idx << 8);
+ eeprom->idx_write_cnt++;
+ } else {
+ if (!eeprom->read_only) {
+ spin_lock(&eeprom->buffer_lock);
+ eeprom->buffer[eeprom->buffer_idx++ & eeprom->address_mask] = *val;
+ spin_unlock(&eeprom->buffer_lock);
+ }
+ }
+ break;
+
+ case I2C_SLAVE_READ_PROCESSED:
+ /* The previous byte made it to the bus, get next one */
+ eeprom->buffer_idx++;
+ fallthrough;
+ case I2C_SLAVE_READ_REQUESTED:
+ spin_lock(&eeprom->buffer_lock);
+ *val = eeprom->buffer[eeprom->buffer_idx & eeprom->address_mask];
+ spin_unlock(&eeprom->buffer_lock);
+ /*
+ * Do not increment buffer_idx here, because we don't know if
+ * this byte will be actually used. Read Linux I2C slave docs
+ * for details.
+ */
+ break;
+
+ case I2C_SLAVE_STOP:
+ case I2C_SLAVE_WRITE_REQUESTED:
+ eeprom->idx_write_cnt = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static ssize_t i2c_slave_eeprom_bin_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf, loff_t off, size_t count)
+{
+ struct eeprom_data *eeprom;
+ unsigned long flags;
+
+ eeprom = dev_get_drvdata(kobj_to_dev(kobj));
+
+ spin_lock_irqsave(&eeprom->buffer_lock, flags);
+ memcpy(buf, &eeprom->buffer[off], count);
+ spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
+
+ return count;
+}
+
+static ssize_t i2c_slave_eeprom_bin_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf, loff_t off, size_t count)
+{
+ struct eeprom_data *eeprom;
+ unsigned long flags;
+
+ eeprom = dev_get_drvdata(kobj_to_dev(kobj));
+
+ spin_lock_irqsave(&eeprom->buffer_lock, flags);
+ memcpy(&eeprom->buffer[off], buf, count);
+ spin_unlock_irqrestore(&eeprom->buffer_lock, flags);
+
+ return count;
+}
+
+static int i2c_slave_init_eeprom_data(struct eeprom_data *eeprom, struct i2c_client *client,
+ unsigned int size)
+{
+ const struct firmware *fw;
+ const char *eeprom_data;
+ int ret = device_property_read_string(&client->dev, "firmware-name", &eeprom_data);
+
+ if (!ret) {
+ ret = request_firmware_into_buf(&fw, eeprom_data, &client->dev,
+ eeprom->buffer, size);
+ if (ret)
+ return ret;
+ release_firmware(fw);
+ } else {
+ /* An empty eeprom typically has all bits set to 1 */
+ memset(eeprom->buffer, 0xff, size);
+ }
+ return 0;
+}
+
+static int i2c_slave_eeprom_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct eeprom_data *eeprom;
+ int ret;
+ unsigned int size = FIELD_GET(I2C_SLAVE_BYTELEN, id->driver_data) + 1;
+ unsigned int flag_addr16 = FIELD_GET(I2C_SLAVE_FLAG_ADDR16, id->driver_data);
+
+ eeprom = devm_kzalloc(&client->dev, sizeof(struct eeprom_data) + size, GFP_KERNEL);
+ if (!eeprom)
+ return -ENOMEM;
+
+ eeprom->num_address_bytes = flag_addr16 ? 2 : 1;
+ eeprom->address_mask = size - 1;
+ eeprom->read_only = FIELD_GET(I2C_SLAVE_FLAG_RO, id->driver_data);
+ spin_lock_init(&eeprom->buffer_lock);
+ i2c_set_clientdata(client, eeprom);
+
+ ret = i2c_slave_init_eeprom_data(eeprom, client, size);
+ if (ret)
+ return ret;
+
+ sysfs_bin_attr_init(&eeprom->bin);
+ eeprom->bin.attr.name = "slave-eeprom";
+ eeprom->bin.attr.mode = S_IRUSR | S_IWUSR;
+ eeprom->bin.read = i2c_slave_eeprom_bin_read;
+ eeprom->bin.write = i2c_slave_eeprom_bin_write;
+ eeprom->bin.size = size;
+
+ ret = sysfs_create_bin_file(&client->dev.kobj, &eeprom->bin);
+ if (ret)
+ return ret;
+
+ ret = i2c_slave_register(client, i2c_slave_eeprom_slave_cb);
+ if (ret) {
+ sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
+ return ret;
+ }
+
+ return 0;
+};
+
+static void i2c_slave_eeprom_remove(struct i2c_client *client)
+{
+ struct eeprom_data *eeprom = i2c_get_clientdata(client);
+
+ i2c_slave_unregister(client);
+ sysfs_remove_bin_file(&client->dev.kobj, &eeprom->bin);
+}
+
+static const struct i2c_device_id i2c_slave_eeprom_id[] = {
+ { .name = "slave-24c02", .driver_data = I2C_SLAVE_DEVICE_MAGIC(2048 / 8, 0) },
+ { .name = "slave-24c02ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(2048 / 8, I2C_SLAVE_FLAG_RO) },
+ { .name = "slave-24c32", .driver_data = I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16) },
+ { .name = "slave-24c32ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(32768 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) },
+ { .name = "slave-24c64", .driver_data = I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16) },
+ { .name = "slave-24c64ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(65536 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) },
+ { .name = "slave-24c512", .driver_data = I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16) },
+ { .name = "slave-24c512ro", .driver_data = I2C_SLAVE_DEVICE_MAGIC(524288 / 8, I2C_SLAVE_FLAG_ADDR16 | I2C_SLAVE_FLAG_RO) },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, i2c_slave_eeprom_id);
+
+static struct i2c_driver i2c_slave_eeprom_driver = {
+ .driver = {
+ .name = "i2c-slave-eeprom",
+ },
+ .probe = i2c_slave_eeprom_probe,
+ .remove = i2c_slave_eeprom_remove,
+ .id_table = i2c_slave_eeprom_id,
+};
+module_i2c_driver(i2c_slave_eeprom_driver);
+
+MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");
+MODULE_DESCRIPTION("I2C slave mode EEPROM simulator");
+MODULE_LICENSE("GPL v2");