Клавиатура PS / 2 не отправляет прерывания нажатия клавиш, но реагирует на команды - PullRequest
8 голосов
/ 01 июля 2019

Я довольно новичок в разработке ОС, и недавно я начал хобби-проект создания простой текстовой операционной системы. Он написан на 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. Дайте мне знать, если нужно больше кода. Надеюсь, есть что-то очевидное, что я либо забыл, либо делаю неправильно :) 10 *

1 Ответ

8 голосов
/ 01 июля 2019

Общие причины, по которым определенный IRQ, некоторые IRQ или все IRQ могут не работать:

  • Вы не включили прерывания в ЦП с sti (или эквивалентным)
  • Вы не включили прерывания с маской, отправленной на главный и подчиненный PIC, когда вы инициализировали их.
  • Неправильное подтверждение EOI при возникновении прерывания может заблокировать некоторые или все прерывания в зависимости от приоритета прерывания.
  • У вас отключено PICs
  • Вы не получите прерывание клавиатуры от клавиатуры PS / 2, если не отправите байт конфигурации контроллера PS / 2 с установленным битом 0 (бит 1 является прерыванием для мыши)

Я бы сузил проблемное пространство, маскируя все внешние прерывания, кроме того, которое вы тестируете.В вашем случае вы заинтересованы в 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 (бит 1 главного PIC).


Вы обнаружили, что проблема исчезла, используя приведенное выше предложение, а затем упомянули, что ваш обработчик прерываний по умолчанию просто выполняет IRET.Вам нужно отправить правильный EOI, даже если по умолчанию обработчики IRQ ничего не делают.Не посылайте EOI для прерываний, если они не получены от PIC.В вашем случае запись IDT от 0x20 до 0x2f (включительно) должна иметь обработчики, которые отправляют надлежащие EOI.Более подробную информацию о правильной обработке EOI можно найти в OSDev Wiki

. Я предполагаю, что при первом прерывании по таймеру (IRQ0) вы не отправляете EOI, иэто эффективно отключило бы все внешние прерывания.До отправки EOI все внешние прерывания равного или более низкого приоритета будут отключены.IRQ0 (таймер) является наивысшим приоритетом, поэтому не отправка EOI эффективно отключает все внешние прерывания, пока не будет отправлен EOI.

...