Короткий ответ — скорее всего 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.