Почему в моей самописной ОС для x86_64 прерывания клавиатуры (IRQ1) перестают срабатывать после первого нажатия при переходе в режим UEFI Graphics Output Protocol (GOP)?

Пишу ОС (загрузка через UEFI, переход в Long Mode, вывод графики через GOP). Столкнулся с проблемой при настройке прерываний (IDT). <br/> <br/> <b>Симптомы:</b> <br/> При старте экран по задумке чёрный, и клавиатура корректно реагирует на ESC — соответствующий код выполняется. Однако при нажатии любой другой клавиши система переходит в графический режим, после чего клавиатура полностью перестаёт отвечать на любые нажатия (включая ESC). Прерывания выглядят так, будто блокируются после первого–второго вызова. <br/> <br/> <b>Что уже проверил:</b> <br/> 1. Базовый обработчик IDT вызывается. <br/> 2. Стек при выходе из прерывания (iretq) восстанавливается — явных Triple Fault нет. <br/> <br/> <b>Предположение:</b> <br/> Либо я неправильно отправляю EOI (End of Interrupt) в PIC/APIC и контроллер удерживает/блокирует линию, либо где-то в ISR затираются регистры общего назначения. <br/> <br/> Ссылка на репозиторий с кодом обработки IDT: <a href="https://github.com/larcenkokirill91-max/AnalOS.git" rel="nofollow">https://github.com/larcenkokirill91-max/AnalOS.git</a> <br/> <br/> Подскажите, пожалуйста, на какие места в коде ISR-заглушек и в инициализации контроллера прерываний стоит обратить внимание, чтобы предотвратить блокировку ввода?
  • 12 июля 2026 г. 12:01
Ответы на вопрос 1
Короткий ответ — скорее всего IRQ1 «зависает» из‑за неправильной обработки двух вещей: либо вы не снимаете/не читаете данные с контроллера клавиатуры (и линия остаётся в состоянии asserted), либо не посылаете правильный EOI тому контроллеру, который реально доставляет прерывание (8259 PIC vs IOAPIC+Local APIC), либо у вас неправильно оформлен/сбрасывается контекст в ISR (или тип/атрибут в IDT). Переключение в GOP/UEFI часто пересоздаёт маршрутизацию прерываний (IOAPIC), меняет режим контроллера клавиатуры и/или конфигурацию триггеров — поэтому баги в EOI/IOAPIC/ISR начинают проявляться именно после включения графики.

На что смотреть и что проверить (практический чеклист)

1) Читаете scancode из порта 0x60
- В обработчике клавиатуры обязательно выполните inb(0x64) (статус) и inb(0x60) (scancode). Если вы не читаете выходной буфер контроллера, линия IRQ может оставаться удержанной и контроллер будет больше прерываний не посылать (или будет посылать, но аппарат будет держать линию и контроллер не позволит дальнейшие).
- Пример (C/с inline asm или портовые функции):
  - status = inb(0x64); if (status & 1) sc = inb(0x60);

2) Правильный EOI — кому отправлять
- Если вы используете старый 8259 PIC, отправляйте EOI на мастер PIC: outb(0x20, 0x20). Если IRQ происходит на слейве (IRQ8..15), отправляйте также на слейв (0xA0).
- Если у вас включён APIC/IOAPIC, EOI должен отправляться в Local APIC, а не в 0x20 порт. Local APIC EOI — запись в регистр EOI (MMIO LAPIC_BASE+0xB0), обычно запись любого 32‑битного значения, например write32(lapic_base + 0xB0, 0).
- Если вы неправильно посылаете EOI (посылаете только PIC, но прерывание идёт через IOAPIC/LAPIC, или наоборот), то контроллер будет считать IRQ не обработанным и далее блокировать/удерживать линию.
- Проверьте, какой контроллер реально маршрутизирует IRQ1 на вашей машине после включения GOP (UEFI может переключить на IOAPIC).

3) IOAPIC: триггерный режим и маски
- IOAPIC redirection entry: бит 15 — Trigger Mode (0=edge, 1=level). Клавиатура — edge triggered. Если IOAPIC запись случайно выставила level trigger, линия может оставаться asserted пока не будет снят OBF, и EOI/remote IRR не будут сниматься.
- Убедитесь, что в redirection entry для IRQ1: trigger mode = edge, polarity = (обычно high), mask = 0 (размасковано), и вектор корректен.
- Если вы перенастраивали IOAPIC вручную, проверьте эти битовые поля.

4) IDT и ISR wrapper (контекст)
- IDT‑запись для IRQ должна быть Interrupt Gate (тип 0x8E для 64‑бит), а не Trap Gate. Interrupt gate автоматически сбрасывает IF при входе.
- В x86_64 нет pusha/popa — правильно сохраняйте все регистры, которые используете в ISR: RAX, RBX, RCX, RDX, RSI, RDI, RBP, R8‑R15. Неполное сохранение/восстановление может уронить состояния и прервать дальнейшие прерывания.
- Стек должен быть правильно выровнен (16‑байт) при вызове C‑функций из обработчика. Если вход из CPL0, iretq должен корректно восстановить RIP/RFLAGS/SS:RSP (особенно если был переход с CPL3 — тогда нужен TSS/IST).
- Проверьте, что вы возвращаетесь через iretq, а не через ret.

5) PIC remap / маски
- Убедитесь, что PIC ремапирован в вектора, которые вы обрабатываете.
- Убедитесь, что IRQ1 не замаскирован: maskA = inb(0x21); maskA & ~(1<<1). Для slave inb(0xA1) аналогично.
- Если вы используете APIC, обычно следует маскировать PIC (или полностью отключить), чтобы не было двойной маршрутизации.

6) Аппаратные особенности UEFI/GOP
- UEFI при включении GOP/графики может переинициализировать контроллеры (особенно в современном оборудовании), переключить IRQ маршрутизацию на IOAPIC, включить MSI, поменять настройки PCI устройств (USB хабы => клавиатура может стать USB и пойти через XHCI с другими IRQ/механизмом). Убедитесь, что клавиатура реально PS/2 IRQ1, а не USB (в случае USB — вам нужны драйверы для USB и они не пройдут через порт 0x60).
- Проверьте, работает ли клавиатура после GOP вообще (например, смотря на состояние port 0x64 и буфер 0x60).

Диагностика — что и как читать
- Сразу после «залипания» клавиатуры выполните:
  - inb(0x64) & 1 — есть ли байт в OBF?
  - inb(0x21) и inb(0xA1) — маски PIC.
  - если есть LAPIC/MMIO регистры доступны — читать LAPIC ISR/IRR/EOI.
  - прочитать IOAPIC redirection table (через регистры IOREGSEL/IOWIN) для IRQ1 — проверить trigger/mask/vector.
- Логируйте в ISR: вход/выход, чтение scancode, посылаемый EOI. Если лог не проходит (т.к. нет консоли), можно миганием экрана (GOP) или записью в заранее выделенный буфер.

Примеры (кратко)

- EOI для PIC:
  outb(0x20, 0x20); // master EOI
  // if IRQ from slave (>=8):
  outb(0xA0, 0x20);

- EOI для Local APIC (C-like):
  volatile uint32_t *lapic = (uint32_t *)LAPIC_BASE;
  lapic[0x0B0/4] = 0; // запись в регистр EOI

- Размаскировать IRQ1 в PIC:
  uint8_t mask = inb(0x21);
  mask &= ~(1 << 1);
  outb(0x21, mask);

- Проверка OBF и чтение scancode:
  uint8_t status = inb(0x64);
  if (status & 1) {
    uint8_t sc = inb(0x60);
    // обработка...
  }

Куда смотреть в вашем репозитории
- Файлы, связанные с IDT/ISR (ассемблерные заглушки ISR, регистрация IDT), код настройки PIC и APIC, и код обработки клавиатуры. Ищите в репе файлы с названиями типа idt, isr, pic, apic, keyboard, kbd.
- Особое внимание на ассемблерные обёртки ISR: push/restore регистров, выставление стека, iretq.

Если хотите — могу:
- Быстро просмотреть конкретные файлы из вашего репозитория (укажите путь к файлам idt/isr/pic/apic/keyboard), или
- Прислать фрагменты ваших ISR‑заглушек и кода инициализации PIC/APIC — я укажу точные ошибки.

Резюмируя: сначала проверьте чтение scancode и отправку EOI тому контроллеру, который реально использует система после включения GOP (часто это IOAPIC + LAPIC), и проверьте trigger mode (edge vs level) в IOAPIC. Также удостоверьтесь, что ваш ISR корректно сохраняет/восстанавливает регистры и что IRQ не замаскированы в PIC.
Похожие вопросы