【问题标题】:PS/2 keyboard won't send keypress interrupts, but does respond to commandsPS/2 键盘不会发送按键中断,但会响应命令
【发布时间】:2019-11-11 16:56:55
【问题描述】:

我对操作系统开发还很陌生,最近我开始了一个爱好项目,即创建一个尽可能简单的纯文本操作系统。它是在汇编的帮助下用 C 语言编写的,并使用 GRUB 进行引导,我一直在 VirtualBox 中对其进行测试,并且偶尔将其放在闪存驱动器上以便在一台古老的(~2009 年)笔记本电脑上进行测试。到目前为止,我已经实现了一些基本的文本输出功能,并且我认为我的 GDT 和 IDT 实现还可以,因为最近没有崩溃。目前我正在尝试让中断驱动的键盘驱动程序正常工作。

我认为我已经正确设置了 PIC,而且我似乎很幸运能够向 PS/2 控制器和键盘发出命令并通过中断处理程序捕获响应。例如,下面是给键盘一个识别命令时的调试输出:

Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF2
Keyboard interrupt: 0xFA
Keyboard interrupt: 0xAB
Keyboard interrupt: 0x83

返回的数据似乎是正确的,这证明我的中断处理程序能够连续多次工作而不会崩溃或任何事情,所以我不太担心我的 IDT 或 ISR 实现。现在这是我向键盘发送 0xF4 命令以开始扫描按键时的输出:

Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF4
Keyboard interrupt: 0xFA

带有“确认”状态码 0xFA 的中断看起来很有希望,但之后当我按下按键时什么也没有发生。对于这两个示例,我在 VirtualBox 和我一直使用的笔记本电脑上运行时都得到了相同的结果。

以下是来自键盘驱动程序的一些相关代码:

#define KEYBD_DATA 0x60
#define KEYBD_CMD 0x64

// wrapper for interrupt service routine written in assembly
extern void keyboard_interrupt();

// called from assembly ISR
void keyboard_handler() {
    u8 data = read_port(KEYBD_DATA);
    print("Keyboard interrupt: 0x");
    printx(data);
    putc('\n');
    pic_eoi();
}

// functions to print command before sending it to the port
void keyboard_command(u8 cmd) {
    print("Sending keyboard command: 0x");
    printx(cmd);
    putc('\n');
    write_port(KEYBD_DATA, cmd);
}

void controller_command(u8 cmd) {
    print("Sending controller command: 0x");
    printx(cmd);
    putc('\n');
    write_port(KEYBD_CMD, cmd);
}

void setup_keyboard() {

    // flush keyboard output
    while(read_port(KEYBD_CMD) & 1)
        read_port(KEYBD_DATA);

    // set interrupt descriptor table entry (default code segment and access flags)
    set_idt_entry(0x21, &keyboard_interrupt);

    // activate device
    write_port(KEYBD_CMD, 0xAE);
    wait();

    // get status
    write_port(KEYBD_CMD, 0x20);
    wait();
    u8 status = (read_port(KEYBD_DATA) | 1) & 0x05;
    print("Setting PS/2 controller status: 0x");
    printx(status);
    putc('\n');
    wait();

    // set status
    write_port(KEYBD_CMD, 0x60);
    wait();
    write_port(KEYBD_DATA, status);
    wait();

    // enable keyboard scanning
    keyboard_command(0xf4);
}

不是我认为这是问题的根源,而是为了以防万一(在 GNU 汇编中),这是中断处理程序的汇编部分:

.extern keyboard_handler
.global keyboard_interrupt

keyboard_interrupt:
    cli
    pusha
    cld
    call keyboard_handler
    popa
    sti
    iret

这是预先设置 PIC 的代码:

#define MASTER_CMD 0x20
#define MASTER_DATA 0x21
#define SLAVE_CMD 0xA0
#define SLAVE_DATA 0xA1
#define PIC_EOI 0x20

// hopefully this gives a long enough delay
void wait() {
    for (u8 i = 0; i < 255; i++);
}

// alert the PICs that the interrupt handling is done
// (later I'll check whether the slave PIC needs to be sent the EOI, but for now it doesn't seem to hurt to give it anyway)
void pic_eoi() {
    write_port(MASTER_CMD, PIC_EOI);
    write_port(SLAVE_CMD, PIC_EOI);
    wait();
}

void setup_pic() {
    write_port(MASTER_CMD, 0x11);
    write_port(SLAVE_CMD, 0x11);
    wait();
    write_port(MASTER_DATA, 0x20);
    write_port(SLAVE_DATA, 0x28);
    wait();
    write_port(MASTER_DATA, 0x4);
    write_port(SLAVE_DATA, 0x2);
    wait();
    write_port(MASTER_DATA, 0x1);
    write_port(SLAVE_DATA, 0x1);
    wait();
    write_port(MASTER_DATA, 0x0);
    write_port(SLAVE_DATA, 0x0);
    wait();
}

这是内核主要部分的初始化顺序:

// initialize global descriptor table and interrupt descriptor table
setup_gdt();
setup_idt();

// setup hardware interrupts
setup_pic();
setup_keyboard();
activate_idt(); // assembly routine with lidt and sti

我也知道键盘实际上正在做它的事情并将扫描代码放在端口 0x60 上,并且我已经能够获得一种让按键工作的轮询方法,但是它很混乱,而且它会使它更难处理诸如键重复和跟踪 shift 键之类的事情。让我知道是否需要更多代码。希望有一些明显的事情我要么忘记了,要么做错了:)

【问题讨论】:

  • 您是否尝试过在 BOCHS 中启动它,这样即使中断被禁用,您也可以使用它的内置调试器单步执行?并且没有时间在来宾机器内流逝。启动到 32 位或 64 位模式后,您还可以将 GDB 与 QEMU 一起使用。 (BOCHS 知道实模式分割,GDB 不知道)。如果 VirtualBox 可以充当 GDB 远程,则 IDK,但我强烈建议使用允许对整个机器进行单步和断点调试的 VM 设置。
  • in pic_eoi 如果删除 write_port(SLAVE_CMD, PIC_EOI); wait(); 会发生什么?发送EOI后也无需延迟。如果中断发生在主机上,您不应该向从机发送 EOI。我知道你的评论,只是不要这样做。发送到 PS/2 控制器时,您应该正确等待输入缓冲区状态位变为 0。读取数据时,您应该等待输出状态位设置为 1。您是否有 github 项目或我们可以在某个地方查看整个项目?
  • 不清楚您是否为所有其他中断正确发送 EOI。
  • 如果在此行上使用优化构建 for (u8 i = 0; i &lt; 255; i++); 可能会完全优化并且不会延迟。
  • 哦,有道理。我在忽略例程中添加了对 pic_eoi() 的调用并删除了 PIC 位掩码,现在它工作正常。至少这是一个简单的修复感谢您的帮助!

标签: c assembly x86 interrupt osdev


【解决方案1】:

特定 IRQ、某些 IRQ 或所有 IRQ 似乎无法正常工作的一般原因:

  • 您尚未在 CPU 上使用 sti(或等效项)启用中断
  • 当您initialise 时,您尚未启用带有发送到主从 PIC 的掩码的中断。
  • 在发生中断时未正确确认 EOI 可能会禁用部分或全部中断,具体取决于中断的优先级。
  • 您有disabled 的图片
  • 除非您发送了PS/2 controller configuration byte 并设置了位 0(位 1 是鼠标中断),否则您不会从 PS/2 键盘获得键盘中断

我会通过屏蔽除您正在测试的外部中断之外的所有外部中断来缩小问题空间。在您的情况下,您对 IRQ1 感兴趣。要屏蔽除 IRQ1 之外的所有外部中断,您可以更改 setup_pic 以便:

write_port(MASTER_DATA, 0x0);
write_port(SLAVE_DATA, 0x0);

变成:

write_port(MASTER_DATA, ~0x2);
write_port(SLAVE_DATA, ~0x0);

设置的位屏蔽中断,零位启用它们。 ~0x2 是位掩码 0b11111101~0x0 是位掩码 0b11111111。这应该禁用除 IRQ1(主 PIC 的第 1 位)之外的所有内容。


您发现问题通过使用上面的建议消失了,然后提到您的默认中断处理程序只是执行IRET。即使在默认情况下什么也不做 IRQ 处理程序,您也需要发送正确的 EOI。不要发送 EOI 进行中断,除非它们来自 PIC。在您的情况下,IDT 条目 0x20 到 0x2f(包括)需要具有发送正确 EOI 的处理程序。有关正确处理意向书的更多详细信息,请访问OSDev Wiki

我猜是发生了什么,在第一个定时器中断 (IRQ0) 上,您没有发送 EOI,这将有效地禁用所有外部中断。在发送 EOI 之前,所有相同或更低优先级的外部中断都将被禁用。 IRQ0(定时器)是最高优先级,因此在发送 EOI 之前不发送 EOI 会有效地禁用所有外部中断。

【讨论】:

    猜你喜欢
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-08
    相关资源
    最近更新 更多