PS / 2键盘不会发送按键中断,但会响应命令

时间:2019-06-30 23:55:45

标签: c assembly x86 interrupt osdev

我对OS开发还很陌生,最近我开始了一个业余爱好项目,它创建了一个尽可能简单的纯文本操作系统。它是用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键之类的事情。让我知道是否需要更多代码。希望有一些明显的事情我忘记或做错了:)

1 个答案:

答案 0 :(得分:8)

特定IRQ,某些IRQ或所有IRQ似乎不起作用的一般原因:

  • 您尚未使用sti(或同等功能)在CPU上启用中断
  • initialise时,尚未通过发送给主PIC和从PIC的掩码启用中断。
  • 如果确实发生中断,则不能正确地确认EOI会禁用部分或全部中断,具体取决于中断的优先级。
  • 您有disabled个图片
  • 除非您发送的PS/2 controller configuration byte的第0位设置为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。除非中断来自PIC,否则请勿发送EOI。在您的情况下,IDT条目0x20至0x2f(含0x2f)需要具有发送适当EOI的处理程序。有关正确处理EOI的更多详细信息,请访问OSDev Wiki

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