summaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard/hilkbd.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/input/keyboard/hilkbd.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/input/keyboard/hilkbd.c')
-rw-r--r--drivers/input/keyboard/hilkbd.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/drivers/input/keyboard/hilkbd.c b/drivers/input/keyboard/hilkbd.c
new file mode 100644
index 000000000..c8d8d0ea3
--- /dev/null
+++ b/drivers/input/keyboard/hilkbd.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/hil/hilkbd.c
+ *
+ * Copyright (C) 1998 Philip Blundell <philb@gnu.org>
+ * Copyright (C) 1999 Matthew Wilcox <willy@infradead.org>
+ * Copyright (C) 1999-2007 Helge Deller <deller@gmx.de>
+ *
+ * Very basic HP Human Interface Loop (HIL) driver.
+ * This driver handles the keyboard on HP300 (m68k) and on some
+ * HP700 (parisc) series machines.
+ */
+
+#include <linux/pci_ids.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/hil.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <asm/irq.h>
+#ifdef CONFIG_HP300
+#include <asm/hwtest.h>
+#endif
+
+
+MODULE_AUTHOR("Philip Blundell, Matthew Wilcox, Helge Deller");
+MODULE_DESCRIPTION("HIL keyboard driver (basic functionality)");
+MODULE_LICENSE("GPL v2");
+
+
+#if defined(CONFIG_PARISC)
+
+ #include <asm/io.h>
+ #include <asm/hardware.h>
+ #include <asm/parisc-device.h>
+ static unsigned long hil_base; /* HPA for the HIL device */
+ static unsigned int hil_irq;
+ #define HILBASE hil_base /* HPPA (parisc) port address */
+ #define HIL_DATA 0x800
+ #define HIL_CMD 0x801
+ #define HIL_IRQ hil_irq
+ #define hil_readb(p) gsc_readb(p)
+ #define hil_writeb(v,p) gsc_writeb((v),(p))
+
+#elif defined(CONFIG_HP300)
+
+ #define HILBASE 0xf0428000UL /* HP300 (m68k) port address */
+ #define HIL_DATA 0x1
+ #define HIL_CMD 0x3
+ #define HIL_IRQ 2
+ #define hil_readb(p) readb((const volatile void __iomem *)(p))
+ #define hil_writeb(v, p) writeb((v), (volatile void __iomem *)(p))
+
+#else
+#error "HIL is not supported on this platform"
+#endif
+
+
+
+/* HIL helper functions */
+
+#define hil_busy() (hil_readb(HILBASE + HIL_CMD) & HIL_BUSY)
+#define hil_data_available() (hil_readb(HILBASE + HIL_CMD) & HIL_DATA_RDY)
+#define hil_status() (hil_readb(HILBASE + HIL_CMD))
+#define hil_command(x) do { hil_writeb((x), HILBASE + HIL_CMD); } while (0)
+#define hil_read_data() (hil_readb(HILBASE + HIL_DATA))
+#define hil_write_data(x) do { hil_writeb((x), HILBASE + HIL_DATA); } while (0)
+
+/* HIL constants */
+
+#define HIL_BUSY 0x02
+#define HIL_DATA_RDY 0x01
+
+#define HIL_SETARD 0xA0 /* set auto-repeat delay */
+#define HIL_SETARR 0xA2 /* set auto-repeat rate */
+#define HIL_SETTONE 0xA3 /* set tone generator */
+#define HIL_CNMT 0xB2 /* clear nmi */
+#define HIL_INTON 0x5C /* Turn on interrupts. */
+#define HIL_INTOFF 0x5D /* Turn off interrupts. */
+
+#define HIL_READKBDSADR 0xF9
+#define HIL_WRITEKBDSADR 0xE9
+
+static unsigned int hphilkeyb_keycode[HIL_KEYCODES_SET1_TBLSIZE] __read_mostly =
+ { HIL_KEYCODES_SET1 };
+
+/* HIL structure */
+static struct {
+ struct input_dev *dev;
+
+ unsigned int curdev;
+
+ unsigned char s;
+ unsigned char c;
+ int valid;
+
+ unsigned char data[16];
+ unsigned int ptr;
+ spinlock_t lock;
+
+ void *dev_id; /* native bus device */
+} hil_dev;
+
+
+static void poll_finished(void)
+{
+ int down;
+ int key;
+ unsigned char scode;
+
+ switch (hil_dev.data[0]) {
+ case 0x40:
+ down = (hil_dev.data[1] & 1) == 0;
+ scode = hil_dev.data[1] >> 1;
+ key = hphilkeyb_keycode[scode];
+ input_report_key(hil_dev.dev, key, down);
+ break;
+ }
+ hil_dev.curdev = 0;
+}
+
+
+static inline void handle_status(unsigned char s, unsigned char c)
+{
+ if (c & 0x8) {
+ /* End of block */
+ if (c & 0x10)
+ poll_finished();
+ } else {
+ if (c & 0x10) {
+ if (hil_dev.curdev)
+ poll_finished(); /* just in case */
+ hil_dev.curdev = c & 7;
+ hil_dev.ptr = 0;
+ }
+ }
+}
+
+
+static inline void handle_data(unsigned char s, unsigned char c)
+{
+ if (hil_dev.curdev) {
+ hil_dev.data[hil_dev.ptr++] = c;
+ hil_dev.ptr &= 15;
+ }
+}
+
+
+/* handle HIL interrupts */
+static irqreturn_t hil_interrupt(int irq, void *handle)
+{
+ unsigned char s, c;
+
+ s = hil_status();
+ c = hil_read_data();
+
+ switch (s >> 4) {
+ case 0x5:
+ handle_status(s, c);
+ break;
+ case 0x6:
+ handle_data(s, c);
+ break;
+ case 0x4:
+ hil_dev.s = s;
+ hil_dev.c = c;
+ mb();
+ hil_dev.valid = 1;
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+
+/* send a command to the HIL */
+static void hil_do(unsigned char cmd, unsigned char *data, unsigned int len)
+{
+ guard(spinlock_irqsave)(&hil_dev.lock);
+
+ while (hil_busy())
+ /* wait */;
+ hil_command(cmd);
+ while (len--) {
+ while (hil_busy())
+ /* wait */;
+ hil_write_data(*(data++));
+ }
+}
+
+
+/* initialize HIL */
+static int hil_keyb_init(void)
+{
+ unsigned char c;
+ unsigned int i, kbid;
+ wait_queue_head_t hil_wait;
+ int err;
+
+ if (hil_dev.dev)
+ return -ENODEV; /* already initialized */
+
+ init_waitqueue_head(&hil_wait);
+ spin_lock_init(&hil_dev.lock);
+
+ hil_dev.dev = input_allocate_device();
+ if (!hil_dev.dev)
+ return -ENOMEM;
+
+ err = request_irq(HIL_IRQ, hil_interrupt, 0, "hil", hil_dev.dev_id);
+ if (err) {
+ printk(KERN_ERR "HIL: Can't get IRQ\n");
+ goto err1;
+ }
+
+ /* Turn on interrupts */
+ hil_do(HIL_INTON, NULL, 0);
+
+ /* Look for keyboards */
+ hil_dev.valid = 0; /* clear any pending data */
+ hil_do(HIL_READKBDSADR, NULL, 0);
+
+ wait_event_interruptible_timeout(hil_wait, hil_dev.valid, 3 * HZ);
+ if (!hil_dev.valid)
+ printk(KERN_WARNING "HIL: timed out, assuming no keyboard present\n");
+
+ c = hil_dev.c;
+ hil_dev.valid = 0;
+ if (c == 0) {
+ kbid = -1;
+ printk(KERN_WARNING "HIL: no keyboard present\n");
+ } else {
+ kbid = ffz(~c);
+ printk(KERN_INFO "HIL: keyboard found at id %d\n", kbid);
+ }
+
+ /* set it to raw mode */
+ c = 0;
+ hil_do(HIL_WRITEKBDSADR, &c, 1);
+
+ for (i = 0; i < HIL_KEYCODES_SET1_TBLSIZE; i++)
+ if (hphilkeyb_keycode[i] != KEY_RESERVED)
+ __set_bit(hphilkeyb_keycode[i], hil_dev.dev->keybit);
+
+ hil_dev.dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+ hil_dev.dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
+ BIT_MASK(LED_SCROLLL);
+ hil_dev.dev->keycodemax = HIL_KEYCODES_SET1_TBLSIZE;
+ hil_dev.dev->keycodesize= sizeof(hphilkeyb_keycode[0]);
+ hil_dev.dev->keycode = hphilkeyb_keycode;
+ hil_dev.dev->name = "HIL keyboard";
+ hil_dev.dev->phys = "hpkbd/input0";
+
+ hil_dev.dev->id.bustype = BUS_HIL;
+ hil_dev.dev->id.vendor = PCI_VENDOR_ID_HP;
+ hil_dev.dev->id.product = 0x0001;
+ hil_dev.dev->id.version = 0x0010;
+
+ err = input_register_device(hil_dev.dev);
+ if (err) {
+ printk(KERN_ERR "HIL: Can't register device\n");
+ goto err2;
+ }
+
+ printk(KERN_INFO "input: %s, ID %d at 0x%08lx (irq %d) found and attached\n",
+ hil_dev.dev->name, kbid, HILBASE, HIL_IRQ);
+
+ return 0;
+
+err2:
+ hil_do(HIL_INTOFF, NULL, 0);
+ free_irq(HIL_IRQ, hil_dev.dev_id);
+err1:
+ input_free_device(hil_dev.dev);
+ hil_dev.dev = NULL;
+ return err;
+}
+
+static void hil_keyb_exit(void)
+{
+ if (HIL_IRQ)
+ free_irq(HIL_IRQ, hil_dev.dev_id);
+
+ /* Turn off interrupts */
+ hil_do(HIL_INTOFF, NULL, 0);
+
+ input_unregister_device(hil_dev.dev);
+ hil_dev.dev = NULL;
+}
+
+#if defined(CONFIG_PARISC)
+static int __init hil_probe_chip(struct parisc_device *dev)
+{
+ /* Only allow one HIL keyboard */
+ if (hil_dev.dev)
+ return -ENODEV;
+
+ if (!dev->irq) {
+ printk(KERN_WARNING "HIL: IRQ not found for HIL bus at 0x%p\n",
+ (void *)dev->hpa.start);
+ return -ENODEV;
+ }
+
+ hil_base = dev->hpa.start;
+ hil_irq = dev->irq;
+ hil_dev.dev_id = dev;
+
+ printk(KERN_INFO "Found HIL bus at 0x%08lx, IRQ %d\n", hil_base, hil_irq);
+
+ return hil_keyb_init();
+}
+
+static void __exit hil_remove_chip(struct parisc_device *dev)
+{
+ hil_keyb_exit();
+}
+
+static const struct parisc_device_id hil_tbl[] __initconst = {
+ { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00073 },
+ { 0, }
+};
+
+#if 0
+/* Disabled to avoid conflicts with the HP SDC HIL drivers */
+MODULE_DEVICE_TABLE(parisc, hil_tbl);
+#endif
+
+static struct parisc_driver hil_driver __refdata = {
+ .name = "hil",
+ .id_table = hil_tbl,
+ .probe = hil_probe_chip,
+ .remove = __exit_p(hil_remove_chip),
+};
+
+static int __init hil_init(void)
+{
+ return register_parisc_driver(&hil_driver);
+}
+
+static void __exit hil_exit(void)
+{
+ unregister_parisc_driver(&hil_driver);
+}
+
+#else /* !CONFIG_PARISC */
+
+static int __init hil_init(void)
+{
+ int error;
+
+ /* Only allow one HIL keyboard */
+ if (hil_dev.dev)
+ return -EBUSY;
+
+ if (!MACH_IS_HP300)
+ return -ENODEV;
+
+ if (!hwreg_present((void *)(HILBASE + HIL_DATA))) {
+ printk(KERN_ERR "HIL: hardware register was not found\n");
+ return -ENODEV;
+ }
+
+ if (!request_region(HILBASE + HIL_DATA, 2, "hil")) {
+ printk(KERN_ERR "HIL: IOPORT region already used\n");
+ return -EIO;
+ }
+
+ error = hil_keyb_init();
+ if (error) {
+ release_region(HILBASE + HIL_DATA, 2);
+ return error;
+ }
+
+ return 0;
+}
+
+static void __exit hil_exit(void)
+{
+ hil_keyb_exit();
+ release_region(HILBASE + HIL_DATA, 2);
+}
+
+#endif /* CONFIG_PARISC */
+
+module_init(hil_init);
+module_exit(hil_exit);