Коротко — то, что вы описываете, почти классический набор симптомов «криво настроенного IDT / ISR / PIC»: прерывания попадают не туда, где вы ждёте, стёртые/неправильно сохранённые регистры разрушают состояние, EOI не отправляется (или отправляется неправильно), и поэтому поведение (особенно видео/клавиатура) выглядит очень странно. Точного диагноза без просмотра кода дать нельзя, но ниже — практические шаги для отладки и правка «из коробки», которую можно вставить вместо ваших текущих обработчиков/инициализации прерываний. Если не поможет — пришлите сюда (или скопируйте) файлы с реализацией IDT, ISR-ста́бов, PIC-инициализации и обработчиков клавиатуры.
Что проверить сразу (быстро):
- Правильно ли вы ремапите PIC (чтобы IRQ0..IRQ15 не пересекались с исключениями CPU).
- Правильно ли заполнены записи IDT (селектор сегмента кода — тот же, что в GDT, обычно 0x08; тип — interrupt gate 0x8E).
- Используется ли iretq в x86_64 (в 64-битном режиме), а не iret.
- Сохраняются ли регистры в ISR и соблюдается ли выравнивание стека перед вызовом C-кода (лучше использовать атрибуты компилятора для ISR).
- Отправляете ли вы EOI на PIC (и на мастер, если IRQ >= 8).
- Не вызываются ли прерывания до того, как настроен корректный стек/TSS/IST (если используете их).
Рекомендую временно добавить выводы в serial (COM1) или QEMU -serial stdio, и включить лог прерываний в QEMU (qemu -d int) — это очень помогает понять, какие векторы приходят.
Готовая «замена», которую можно попробовать вставить (предполагается x86_64, компилятор GCC/clang). Это минимальный, корректный набор: ремап PIC, таблица IDT, корректная запись для IRQ клавиатуры с использованием атрибута interrupt (компилятор сам сгенерирует переходы и iretq):
1) Низкоуровневые порты (inb/outb)
```c
#include <stdint.h>
static inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
```
2) Ремап PIC и EOI
```c
#define PIC1_CMD 0x20
#define PIC1_DATA 0x21
#define PIC2_CMD 0xA0
#define PIC2_DATA 0xA1
#define PIC_EOI 0x20
void pic_remap(int offset1, int offset2) {
uint8_t a1 = inb(PIC1_DATA);
uint8_t a2 = inb(PIC2_DATA);
outb(PIC1_CMD, 0x11);
outb(PIC2_CMD, 0x11);
outb(PIC1_DATA, offset1);
outb(PIC2_DATA, offset2);
outb(PIC1_DATA, 0x04);
outb(PIC2_DATA, 0x02);
outb(PIC1_DATA, 0x01);
outb(PIC2_DATA, 0x01);
outb(PIC1_DATA, a1);
outb(PIC2_DATA, a2);
}
static inline void pic_send_eoi(unsigned irq) {
if (irq >= 8) outb(PIC2_CMD, PIC_EOI);
outb(PIC1_CMD, PIC_EOI);
}
```
Вызывайте pic_remap(0x20, 0x28) (тогда IRQ0 -> 0x20, IRQ1 -> 0x21 и т.д.).
3) IDT (x86_64) — структура, установка записи, загрузка IDT
```c
typedef struct {
uint16_t offset_low;
uint16_t selector;
uint8_t ist;
uint8_t type_attr;
uint16_t offset_mid;
uint32_t offset_high;
uint32_t zero;
} __attribute__((packed)) idt_entry_t;
idt_entry_t idt[256];
struct {
uint16_t limit;
uint64_t base;
} __attribute__((packed)) idt_ptr;
void set_idt_gate(int n, void* handler, uint8_t type_attr) {
uint64_t h = (uint64_t)handler;
idt[n].offset_low = h & 0xFFFF;
idt[n].selector = 0x08; // ваш селектор кодового сегмента в GDT
idt[n].ist = 0;
idt[n].type_attr = type_attr; // 0x8E = present, DPL=0, 32/64-bit interrupt gate
idt[n].offset_mid = (h >> 16) & 0xFFFF;
idt[n].offset_high = (h >> 32) & 0xFFFFFFFF;
idt[n].zero = 0;
}
void load_idt(void) {
idt_ptr.limit = sizeof(idt) - 1;
idt_ptr.base = (uint64_t)&idt;
__asm__ volatile ("lidt %0" : : "m"(idt_ptr));
}
```
4) Обработчик клавиатуры — используя атрибут interrupt (GCC/clang создаст корректный пролог/эпилог с iretq)
```c
struct interrupt_frame { uint64_t rip, cs, rflags, rsp, ss; };
void keyboard_handler_logic(uint8_t scancode) {
// Здесь ваша логика обработки scancode: перевод в символ, обновление буфера, рисование и т.д.
}
__attribute__((interrupt))
void keyboard_isr(struct interrupt_frame *frame) {
uint8_t sc = inb(0x60);
keyboard_handler_logic(sc);
pic_send_eoi(1); // IRQ1 = клавиатура
}
```
5) Инициализация (примерный порядок)
```c
void irq_init(void) {
// remap PIC
pic_remap(0x20, 0x28);
// замена записи в IDT для IRQ1 (0x21)
set_idt_gate(0x21, keyboard_isr, 0x8E);
// разглушиваем только клавиатуру (пример) — снимаем маску для IRQ1
uint8_t mask = inb(PIC1_DATA);
mask &= ~(1 << 1); // снять маску с IRQ1
outb(PIC1_DATA, mask);
load_idt();
// включаем прерывания
__asm__ volatile ("sti");
}
```
Почему это часто решает проблему
- PIC ремапит IRQ-номера так, чтобы они не попадали в область исключений 0..31.
- Правильная структура IDT и корректный селектор предотвращают «прыжки» в неправильный сегмент/код.
- Использование атрибута interrupt избавляет от ошибок в ручных ASM-ста́бах (неправильный iretq / потеря регистров / выравнивание стека).
- Отправка EOI на PIC предотвращает «залипание» IRQ и последующие странные побочные эффекты.
Что ещё проверить/если не поможет
- Убедитесь, что GDT и селектор кода (0x08) действительно установлены до загрузки IDT. Неверный селектор приводит к переходу в произвольный CS.
- Если вы используете APIC (Local/APIC), не используйте PIC-ремапинг — требуется иная инициализация.
- Если вы оказываете доступ к графике через UEFI/GOP, но теперь запускаете в BIOS-подобном окружении — убедитесь, что вы не рассчитываете на UEFI-стек/сервисы. Иногда переход из UEFI в чистый режим ломает окружение.
- Включите лог прерываний в QEMU: qemu-system-x86_64 -d int — это покажет, какие векторы приходят и когда.
- Добавьте вывод в serial (COM1) во всех ключевых точках: init start/end, при входе в ISR, при чтении scancode — это даст картину.
Если хотите, я могу:
- Просмотреть ваш код, если вы скинете сюда файлы (например: файл с заполнением IDT, asm-ста́бы, pic.c, keyboard.c).
- Подправить прямо в вашем репозитории конкретные куски — пришлите ссылки на файлы/пути в репозитории, которые нужно заменить, или скопируйте сюда содержимое текущих реализаций.
Если согласны — пришлите (в этом чате) содержимое ваших файлов:
- src/idt.c (или где у вас IDT),
- src/interrupts.S (или asm-ста́бы),
- src/pic.c,
- src/keyboard.c
и я подготовлю «патч» (файлы для замены).