Skip to the content.

i8042 : 键盘

i8042

TODO

整理

应该是,首先发送一个中断,然后驱动读去吧。

这个注册只能说是相当的直白了:

static void i8042_realizefn(DeviceState *dev, Error **errp)
{
    ISADevice *isadev = ISA_DEVICE(dev);
    ISAKBDState *isa_s = I8042(dev);
    KBDState *s = &isa_s->kbd;

    isa_init_irq(isadev, &s->irq_kbd, 1);
    isa_init_irq(isadev, &s->irq_mouse, 12);

    isa_register_ioport(isadev, isa_s->io + 0, 0x60);
    isa_register_ioport(isadev, isa_s->io + 1, 0x64);

    s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s);
    s->mouse = ps2_mouse_init(kbd_update_aux_irq, s);
    qemu_register_reset(kbd_reset, s);
}

info mtree -f

  0000000000000060-0000000000000060 (prio 0, i/o): i8042-data
  0000000000000061-0000000000000061 (prio 0, i/o): pcspk
  0000000000000062-0000000000000063 (prio 0, i/o): io @0000000000000062
  0000000000000064-0000000000000064 (prio 0, i/o): i8042-cmd

info qtree

dev: i8042, id ""
  gpio-out "a20" 1
  isa irqs 1,12

观测了一下 guest 的 /proc/interrupts, isa 注册的 1 和 12 就是 linux irq

isa_get_irq [4]
isa_get_irq [7]
isa_get_irq [6]
isa_get_irq [1]
isa_get_irq [12]
isa_get_irq [14]
isa_get_irq [15]

isa 的中断就是直接和 X86MachineState 的 gsi 相同的:

pc_init1 ==> isa_bus_irqs(isa_bus, x86ms->gsi);

从 host 如何捕获数据

通过 gtk 从图形程序获取

- main
  - qemu_main_loop
    - main_loop_wait
      - os_host_main_loop_wait
        - glib_pollfds_poll
          - g_main_context_dispatch
            - gtk_main_do_event
              - g_signal_emit
                - g_signal_emit_valist
                  - gtk_window_propagate_key_event
                    - g_signal_emit
                      - g_signal_emit_valist
                        - g_closure_invoke
                          - gd_key_event
                            - qkbd_state_key_event
                              - qkbd_state_key_event
                                - qemu_input_event_send_key_qcode
                                  - qemu_input_event_send_key
                                    - qemu_input_event_send_impl
                                      - ps2_keyboard_event
                                        - ps2_put_keycode
                                          - ps2_queue
                                            - ps2_queue_noirq

ps2_queue_noirq 的调用者可以从 guest ,也可以从 Host 到来,具体没有分析

host 如何注入中断的

isa_bus 和 gsi 就是直接连接到一起的:

    isa_bus_irqs(isa_bus, x86ms->gsi);

当想要注入中断的时候,调用函数 kbd_update_irq, 然后 gsi 转入到 pic / ioapic 中

guest 如何接受数据

#0  i8042_probe (dev=0xffff88810128f800) at drivers/input/serio/i8042.c:1539
#1  0xffffffff8168215a in platform_probe (_dev=0xffff88810128f810) at drivers/base/platform.c:1447
#2  0xffffffff8167ff86 in really_probe (dev=dev@entry=0xffff88810128f810, drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>) at drivers/base/dd.c:576
#3  0xffffffff816801c1 in driver_probe_device (drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>, dev=dev@entry=0xffff88810128f810) at drivers/base/dd.c:763
#4  0xffffffff8168048e in device_driver_attach (drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>, dev=dev@entry=0xffff88810128f810) at drivers/base/dd.c:1039
#5  0xffffffff816804f0 in __driver_attach (dev=0xffff88810128f810, data=0xffffffff825e6668 <i8042_driver+40>) at drivers/base/dd.c:1117
#6  0xffffffff8167e193 in bus_for_each_dev (bus=<optimized out>, start=start@entry=0x0 <fixed_percpu_data>, data=data@entry=0xffffffff825e6668 <i8042_driver+40>, fn=fn@
entry=0xffffffff816804a0 <__driver_attach>) at drivers/base/bus.c:305
#7  0xffffffff8167f975 in driver_attach (drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>) at drivers/base/dd.c:1133
#8  0xffffffff8167f342 in bus_add_driver (drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>) at drivers/base/bus.c:622
#9  0xffffffff81680d87 in driver_register (drv=drv@entry=0xffffffff825e6668 <i8042_driver+40>) at drivers/base/driver.c:171
#10 0xffffffff8168270e in __platform_driver_register (owner=<optimized out>, drv=0xffffffff825e6640 <i8042_driver>) at drivers/base/platform.c:894
#11 __platform_driver_probe (drv=0xffffffff825e6640 <i8042_driver>, probe=<optimized out>, module=<optimized out>) at drivers/base/platform.c:962
#12 0xffffffff81682baa in __platform_create_bundle (driver=driver@entry=0xffffffff825e6640 <i8042_driver>, probe=probe@entry=0xffffffff82b7b8f2 <i8042_probe>, res=res@e
ntry=0x0 <fixed_percpu_data>, n_res=n_res@entry=0, data=data@entry=0x0 <fixed_percpu_data>, size=size@entry=0, module=0x0 <fixed_percpu_data>) at drivers/base/platform.
c:1026
#13 0xffffffff82b7c3be in i8042_init () at drivers/input/serio/i8042.c:1629
#14 0xffffffff81000def in do_one_initcall (fn=0xffffffff82b7bf9b <i8042_init>) at init/main.c:1249
#15 0xffffffff82b3d26b in do_initcall_level (command_line=0xffff888100123400 "root", level=6) at ./include/linux/compiler.h:234
#16 do_initcalls () at init/main.c:1338
#17 do_basic_setup () at init/main.c:1358
#18 kernel_init_freeable () at init/main.c:1560
#19 0xffffffff81b7c249 in kernel_init (unused=<optimized out>) at init/main.c:1447
#20 0xffffffff810019b2 in ret_from_fork () at arch/x86/entry/entry_64.S:294
static struct platform_driver i8042_driver = {
	.driver		= {
		.name	= "i8042",
#ifdef CONFIG_PM
		.pm	= &i8042_pm_ops,
#endif
	},
	.remove		= i8042_remove,
	.shutdown	= i8042_shutdown,
};
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.probe		= platform_probe,
	.remove		= platform_remove,
	.shutdown	= platform_shutdown,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};

大多数设备都是挂载到总线上的,但是,并不是所有的,比如 i8042 之类1, 所以构建出来 platform 这种虚假的总线。 实际上,还是非常的让人迷惑,无论是物理设计 还是 qemu,i8042 都是接入到总线上才对啊。

初始化之类的东西就实在是太多了,具体内容就从 : i8042_interrupt 如何将数据到上层吧!

使用 strace bash 来分析 pselect 到底在做什么?

pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "e", 1)                         = 1
select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(2, "e", 1e)                        = 1
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "x", 1)                         = 1
select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(2, "x", 1x)                        = 1
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "i", 1)                         = 1
select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(2, "i", 1i)                        = 1
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "t", 1)                         = 1
select(1, [0], NULL, [0], {tv_sec=0, tv_usec=0}) = 0 (Timeout)
write(2, "t", 1t)                        = 1
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0, "\r", 1)                        = 1
write(2, "\n", 1)

在内核显示出来 pollwake 的机制

[   36.794135] Call Trace:
[   36.794140]  <IRQ>
[   36.794143]  dump_stack+0x64/0x7c
[   36.794150]  pollwake+0x2a/0x90
[   36.794157]  ? check_preempt_curr+0x3a/0x70
[   36.794162]  ? ttwu_do_wakeup.isra.0+0xd/0xd0
[   36.794167]  __wake_up_common+0x75/0x140
[   36.794172]  __wake_up_common_lock+0x77/0xb0
[   36.794178]  evdev_events+0x7c/0xa0
[   36.794184]  input_to_handler+0x90/0xf0
[   36.794190]  input_pass_values.part.0+0x119/0x140
[   36.794196]  input_handle_event+0x20e/0x5f0
[   36.794203]  input_event+0x4a/0x70
[   36.794208]  atkbd_interrupt+0x47f/0x640
[   36.794213]  serio_interrupt+0x42/0x90
[   36.794218]  i8042_interrupt+0x146/0x250
[   36.794223]  __handle_irq_event_percpu+0x38/0x150
[   36.794229]  handle_irq_event_percpu+0x2c/0x80
[   36.794234]  handle_irq_event+0x23/0x50
[   36.794252]  handle_edge_irq+0x79/0x190
[   36.794257]  __common_interrupt+0x39/0x90
[   36.794277]  common_interrupt+0x76/0xa0
[   36.794283]  </IRQ>
[   36.794285]  asm_common_interrupt+0x1e/0x40

在 evdev_events 的处理中间,其挂载到 evdev->client_list evdev_pass_values

#0  evdev_open (inode=0xffff888101283da8, file=0xffff8881015bad00) at drivers/input/evdev.c:472
#1  0xffffffff811e7378 in chrdev_open (inode=inode@entry=0xffff888101283da8, filp=filp@entry=0xffff8881015bad00) at fs/char_dev.c:414
#2  0xffffffff811de5c6 in do_dentry_open (f=f@entry=0xffff8881015bad00, inode=0xffff888101283da8, open=0xffffffff811e72e0 <chrdev_open>, open@entry=0x0 <fixed_percpu_da
ta>) at fs/open.c:826
#3  0xffffffff811dffb9 in vfs_open (path=path@entry=0xffffc90000507db0, file=file@entry=0xffff8881015bad00) at ./include/linux/dcache.h:555
#4  0xffffffff811f2c41 in do_open (op=0xffffc90000507ed4, op=0xffffc90000507ed4, file=0xffff8881015bad00, nd=0xffffc90000507db0) at fs/namei.c:3361
#5  path_openat (nd=nd@entry=0xffffc90000507db0, op=op@entry=0xffffc90000507ed4, flags=flags@entry=65) at fs/namei.c:3494
#6  0xffffffff811f461d in do_filp_open (dfd=dfd@entry=-100, pathname=pathname@entry=0xffff8881001f1000, op=op@entry=0xffffc90000507ed4) at fs/namei.c:3521
#7  0xffffffff811def2d in do_sys_openat2 (dfd=-100, filename=<optimized out>, how=how@entry=0xffffc90000507f18) at fs/open.c:1187
#8  0xffffffff811e03ff in do_sys_open (dfd=<optimized out>, filename=<optimized out>, flags=<optimized out>, mode=<optimized out>) at fs/open.c:1203
#9  0xffffffff81b79150 in do_syscall_64 (nr=<optimized out>, regs=0xffffc90000507f58) at arch/x86/entry/common.c:47
#10 0xffffffff81c0007c in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:112
#11 0x0000000000000000 in ?? ()
 */

这说明,其实当前打开设备是: evdev_open, 说明 evdev 也是存在一个 cdev

分析 input device 的注册过程

分析一下 evdev_connect

static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);

读取相当简单

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。

  1. https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Introduction-to-Linux-Kernel-Driver-Programming-Michael-Opdenacker-Bootlin-.pdf ↩