diff --git a/.gitignore b/.gitignore index 9265a08..348e4a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **.o +**.bin **.iso diff --git a/Makefile b/Makefile index 7663fce..6e0b811 100644 --- a/Makefile +++ b/Makefile @@ -1,57 +1,73 @@ -NASM = nasm -GCC = gcc +CC = gcc +CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -Iinclude + +AS = nasm +ASFLAGS = -f elf32 + LD = ld -GRUB_RESCUE = grub-mkrescue +LDFLAGS = -m elf_i386 -T linker.ld -# Исходники -BOOT_SRC = boot/boot.asm -KERNEL_SRCS = kernel/kmain.c -DRIVER_SRCS = drivers/vga.c +TARGET = iso/boot/kernel.bin +ISO_IMAGE = peredOS.iso -# Автоматически собираем список всех объектных файлов -BOOT_OBJ = boot/boot.o -OBJS = kernel/kmain.o drivers/vga.o +# Жесткий список всех объектов, идущих в линкер +OBJ = boot/boot.o \ + kernel/kmain.o \ + kernel/gdt.o \ + kernel/gdt_asm.o \ + kernel/idt.o \ + kernel/interrupt.o \ + kernel/logger.o \ + kernel/io.o \ + drivers/serial.o \ + drivers/vga.o -ISO_DIR = iso -KERNEL_BIN = $(ISO_DIR)/boot/kernel.bin -ISO_OUT = peredOS.iso - -NASM_FLAGS = -f elf32 -GCC_FLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -c -LD_FLAGS = -m elf_i386 -T linker.ld -GRUB_FLAGS = - -all: $(ISO_OUT) - -# Сборка ISO -$(ISO_OUT): $(KERNEL_BIN) - @echo "[+] Крафтим ISO..." - $(GRUB_RESCUE) $(GRUB_FLAGS) -o $(ISO_OUT) $(ISO_DIR) +# Дефолтное правило (просто сборка бинарника) +all: $(TARGET) + @echo "[+] Сборка бинарника успешно завершена!" # Линковка ядра -$(KERNEL_BIN): $(BOOT_OBJ) $(OBJS) +$(TARGET): $(OBJ) @echo "[+] Линкуем ядро..." - @mkdir -p $(ISO_DIR)/boot - $(LD) $(LD_FLAGS) $(BOOT_OBJ) $(OBJS) -o $(KERNEL_BIN) + $(LD) $(LDFLAGS) $(OBJ) -o $(TARGET) -# Компиляция ассемблера -$(BOOT_OBJ): $(BOOT_SRC) - $(NASM) $(NASM_FLAGS) $(BOOT_SRC) -o $(BOOT_OBJ) - -# Универсальное правило для компиляции Си-файлов в kernel/ +# Компиляция Си-файлов ядра kernel/%.o: kernel/%.c - @echo "[+] Компилируем ядро: $<" - $(GCC) $(GCC_FLAGS) $< -o $@ + @echo "[+] Компилируем ядро (C): $<" + $(CC) $(CFLAGS) -c $< -o $@ -# Универсальное правило для компиляции Си-файлов в drivers/ +# Компиляция ассемблера GDT +kernel/gdt_asm.o: kernel/gdt.asm + @echo "[+] Компилируем gdt.asm..." + $(AS) $(ASFLAGS) kernel/gdt.asm -o kernel/gdt_asm.o + +# Компиляция ассемблера IDT +kernel/interrupt.o: kernel/interrupt.asm + @echo "[+] Компилируем interrupt.asm..." + $(AS) $(ASFLAGS) kernel/interrupt.asm -o kernel/interrupt.o +# Компиляция драйверов drivers/%.o: drivers/%.c @echo "[+] Компилируем драйвер: $<" - $(GCC) $(GCC_FLAGS) $< -o $@ + $(CC) $(CFLAGS) -c $< -o $@ -run: all - qemu-system-i386 -cdrom $(ISO_OUT) +# Компиляция загрузочного сектора +boot/boot.o: boot/boot.asm + @echo "[+] Компилируем бутлоадер..." + $(AS) $(ASFLAGS) boot/boot.asm -o boot/boot.o +# Сборка полноценного ISO-образа через GRUB +iso: $(TARGET) + @echo "[+] Собираем загрузочный ISO-образ..." + grub-mkrescue -o $(ISO_IMAGE) iso/ + +# Честный запуск через ISO в QEMU +run: iso + @echo "[+] Запускаем peredOS в QEMU через CD-ROM..." + qemu-system-i386 -cdrom $(ISO_IMAGE) -serial stdio + +# Очистка мусора clean: - rm -f boot/*.o kernel/*.o drivers/*.o $(KERNEL_BIN) $(ISO_OUT) + @echo "[-] Очистка объектных файлов и образов..." + rm -f boot/*.o kernel/*.o drivers/*.o $(TARGET) $(ISO_IMAGE) -.PHONY: all run clean +.PHONY: all clean run iso diff --git a/README.md b/README.md index ce5b631..1706726 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ - [x] Оно работает - [x] Драйвер VGA -- [ ] Прерывания (GDT/IDT) +- [x] Прерывания (GDT/IDT) +- [x] UART отладка - [ ] Драйвер таймера - [ ] Драйвер клавиатуры - [ ] Нормальный драйвер VGA (скролл итд) diff --git a/drivers/serial.c b/drivers/serial.c new file mode 100644 index 0000000..eb2186d --- /dev/null +++ b/drivers/serial.c @@ -0,0 +1,27 @@ +#include "../kernel/io.h" // Подключаем наши порты + +#define COM1 0x3F8 + +void serial_init(void) { + outb(COM1 + 1, 0x00); + outb(COM1 + 3, 0x80); + outb(COM1 + 0, 0x03); + outb(COM1 + 1, 0x00); + outb(COM1 + 3, 0x03); + outb(COM1 + 2, 0xC7); +} + +int is_transmit_empty(void) { + return inb(COM1 + 5) & 0x20; +} + +void serial_putc(char c) { + while (is_transmit_empty() == 0); + outb(COM1, c); +} + +void kprint_serial(const char* str) { + for (int i = 0; str[i] != '\0'; i++) { + serial_putc(str[i]); + } +} diff --git a/drivers/serial.h b/drivers/serial.h new file mode 100644 index 0000000..946f56a --- /dev/null +++ b/drivers/serial.h @@ -0,0 +1,16 @@ +#ifndef SERIAL_H +#define SERIAL_H + +// Инициализация COM1 порта на скорость 38400 бод, 8N1 +void serial_init(void); + +// Проверка, пуст ли передающий буфер порта +int is_transmit_empty(void); + +// Отправка одного символа в COM1 +void serial_putc(char c); + +// Вывод строки (логов) в последовательный порт +void kprint_serial(const char* str); + +#endif diff --git a/kernel/gdt.asm b/kernel/gdt.asm new file mode 100644 index 0000000..3d05439 --- /dev/null +++ b/kernel/gdt.asm @@ -0,0 +1,24 @@ +bits 32 + +global gdt_flush + +gdt_flush: + ; Берем адрес gdtp, переданный как аргумент функции (лежит в стеке) + mov eax, [esp + 4] + lgdt [eax] ; Загружаем новую GDT + + ; Обновляем сегменты данных. 0x10 — это смещение Kernel Data в нашей GDT (индекс 2 * 8 = 16 = 0x10) + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Делаем дальний прыжок, чтобы обновить регистр CS. + ; 0x08 — это смещение Kernel Code (индекс 1 * 8 = 8 = 0x08). + ; Процессор прыгнет на метку .flush, но уже с новым селектором CS. + jmp 0x08:.flush + +.flush: + ret diff --git a/kernel/gdt.c b/kernel/gdt.c new file mode 100644 index 0000000..06439fb --- /dev/null +++ b/kernel/gdt.c @@ -0,0 +1,46 @@ +#include "gdt.h" + +// Наша таблица GDT и указатель на неё +struct gdt_entry gdt[5]; +struct gdt_ptr gdtp; + +// Ассемблерная функция для загрузки регистра GDTR и обновления сегментов +extern void gdt_flush(uint32_t); + +// Внутренняя функция для сборки дескриптора из человеческих параметров +static void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) { + gdt[num].base_low = (base & 0xFFFF); + gdt[num].base_middle = (base >> 16) & 0xFF; + gdt[num].base_high = (base >> 24) & 0xFF; + + gdt[num].limit_low = (limit & 0xFFFF); + gdt[num].granularity = (limit >> 16) & 0x0F; + + gdt[num].granularity |= gran & 0xF0; + gdt[num].access = access; +} + +void gdt_init(void) { + // Настраиваем указатель GDT + gdtp.limit = (sizeof(struct gdt_entry) * 5) - 1; + gdtp.base = (uint32_t)&gdt; + + // 1. Нулевой дескриптор (обязателен, всегда нули) + gdt_set_gate(0, 0, 0, 0, 0); + + // 2. Сегмент кода ядра (Kernel Code) + // База = 0, Лимит = 0xFFFFF, Доступ = 0x9A, Гранулярность = 0xCF (страничная разметка, 4ГБ) + gdt_set_gate(1, 0, 0xFFFFF, 0x9A, 0xCF); + + // 3. Сегмент данных ядра (Kernel Data) + // Всё то же самое, но Доступ = 0x92 (разрешена запись, это сегмент данных) + gdt_set_gate(2, 0, 0xFFFFF, 0x92, 0xCF); + + // 4. Юзерский код (Ring 3) — пока заглушка, заюзаем в Части 2 + gdt_set_gate(3, 0, 0, 0, 0); + // 5. Юзерские данные (Ring 3) — пока заглушка, заюзаем в Части 2 + gdt_set_gate(4, 0, 0, 0, 0); + + // Загружаем GDT в процессор + gdt_flush((uint32_t)&gdtp); +} diff --git a/kernel/gdt.h b/kernel/gdt.h new file mode 100644 index 0000000..b165ef0 --- /dev/null +++ b/kernel/gdt.h @@ -0,0 +1,25 @@ +#ifndef GDT_H +#define GDT_H + +#include + +// Структура одного дескриптора сегмента (8 байт) +struct gdt_entry { + uint16_t limit_low; // Младшие 16 бит лимита + uint16_t base_low; // Младшие 16 бит базы + uint8_t base_middle; // Следующие 8 бит базы + uint8_t access; // Байт доступа (права, кольцо защиты) + uint8_t granularity; // Гранулярность и старшие 4 бита лимита + uint8_t base_high; // Старшие 8 бит базы +} __attribute__((packed)); + +// Структура указателя для инструкции lgdt (6 байт) +struct gdt_ptr { + uint16_t limit; // Размер всей GDT в байтах минус 1 + uint32_t base; // Физический адрес начала массива gdt_entry +} __attribute__((packed)); + +// Функции для вызова из ядра +void gdt_init(void); + +#endif diff --git a/kernel/idt.c b/kernel/idt.c new file mode 100644 index 0000000..38cb3ac --- /dev/null +++ b/kernel/idt.c @@ -0,0 +1,82 @@ +#include "idt.h" + +// Сам массив IDT на 256 векторов и указатель на него +struct idt_entry idt[256]; +struct idt_ptr idtp; + +// Процессорные исключения (0-21) +extern void isr0(); extern void isr1(); extern void isr2(); extern void isr3(); +extern void isr4(); extern void isr5(); extern void isr6(); extern void isr7(); +extern void isr8(); extern void isr9(); extern void isr10(); extern void isr11(); +extern void isr12(); extern void isr13(); extern void isr14(); extern void isr15(); +extern void isr16(); extern void isr17(); extern void isr18(); extern void isr19(); +extern void isr20(); extern void isr21(); + +// Аппаратные прерывания железа (IRQ) +extern void irq0(); extern void irq1(); + +// Внешняя функция на ассемблере для выполнения 'lidt' +extern void idt_load(); + +// Заполнение полей одного дескриптора +void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { + idt[num].base_low = (base & 0xFFFF); + idt[num].base_high = ((base >> 16) & 0xFFFF); + idt[num].sel = sel; + idt[num].always0 = 0; + idt[num].flags = flags; +} + +// Главная функция инициализации IDT +void idt_init(void) { + // Настраиваем указатель IDTR + idtp.limit = (sizeof(struct idt_entry) * 256) - 1; + idtp.base = (uint32_t)&idt; + + // Зануляем всю таблицу перед использованием + for (int i = 0; i < 256; i++) { + idt_set_gate(i, 0, 0, 0); + } + + // 0x08 — Селектор сегмента кода ядра + // 0x8E — Флаги: Present, Ring 0, 32-bit Interrupt Gate + + // Пихаем исключения процессора в IDT + idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E); idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E); + idt_set_gate(2, (uint32_t)isr2, 0x08, 0x8E); idt_set_gate(3, (uint32_t)isr3, 0x08, 0x8E); + idt_set_gate(4, (uint32_t)isr4, 0x08, 0x8E); idt_set_gate(5, (uint32_t)isr5, 0x08, 0x8E); + idt_set_gate(6, (uint32_t)isr6, 0x08, 0x8E); idt_set_gate(7, (uint32_t)isr7, 0x08, 0x8E); + idt_set_gate(8, (uint32_t)isr8, 0x08, 0x8E); idt_set_gate(9, (uint32_t)isr9, 0x08, 0x8E); + idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E); idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E); + idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E); idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E); + idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E); idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E); + idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E); idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E); + idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E); idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E); + idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E); idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E); + + // Вешаем аппаратные прерывания (после перемапливания PIC они будут тут) + idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E); // Таймер PIT + idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E); // Клавиатура + + // Загружаем IDTR в процессор + idt_load(); +} + +// Сюда сходятся все исключения процессора (0-31) +void isr_handler(struct registers regs) { + // Пока у нас нет printf, тут будет пусто или заглушка для отладки. + // Когда процессор словит ошибку (например деление на 0), он вызовет эту функцию. + for(;;); +} + +// Сюда сходятся все прерывания от железа (32-47) +void irq_handler(struct registers regs) { + // Если прерывание пришло от чипа PIC2 (IRQ 8-15, векторы 40-47), шлем ему EOI (End of Interrupt) + if (regs.int_no >= 40) { + __asm__ __volatile__("outb %%al, %%dx" :: "a"(0x20), "d"(0xA0)); + } + // В любом случае шлем EOI главному чипу PIC1 (порты 0x20/0x20) + __asm__ __volatile__("outb %%al, %%dx" :: "a"(0x20), "d"(0x20)); + + // Сюда в будущем добавим вызовы конкретных драйверов таймера и клавы +} diff --git a/kernel/idt.h b/kernel/idt.h new file mode 100644 index 0000000..ba5272a --- /dev/null +++ b/kernel/idt.h @@ -0,0 +1,33 @@ +#ifndef IDT_H +#define IDT_H + +#include + +// Структура одного элемента таблицы IDT (8 байт) +struct idt_entry { + uint16_t base_low; // Младшие 16 бит адреса функции-обработчика + uint16_t sel; // Сегментный селектор кода (0x08 — Kernel Code в GDT) + uint8_t always0; // Всегда 0, зарезервировано процессором + uint8_t flags; // Флаги доступа (0x8E для Ring 0 прерываний) + uint16_t base_high; // Старшие 16 бит адреса функции-обработчика +} __attribute__((packed)); + +// Структура «указателя» для инструкции lidt (6 байт) +struct idt_ptr { + uint16_t limit; // Размер таблицы в байтах минус 1 (256 * 8 - 1) + uint32_t base; // Физический адрес начала массива структур idt_entry +} __attribute__((packed)); + +// Структура, передаваемая в Си-обработчик, содержащая состояние всех регистров +struct registers { + uint32_t ds; // Сегмент данных + uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Напушено инструкцией pushad + uint32_t int_no, err_code; // Номер прерывания и код ошибки (если есть) + uint32_t eip, cs, eflags, useresp, ss; // Автоматически пушится процессором +}; + +// Объявляем функции для вызова извне +void idt_init(void); +void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags); + +#endif diff --git a/kernel/interrupt.asm b/kernel/interrupt.asm new file mode 100644 index 0000000..fa24462 --- /dev/null +++ b/kernel/interrupt.asm @@ -0,0 +1,145 @@ +bits 32 + +; Экспортируем функции для Си +global idt_load +global isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7, isr8, isr9, isr10 +global isr11, isr12, isr13, isr14, isr15, isr16, isr17, isr18, isr19, isr20, isr21 +global irq0, irq1 + +; Импортируем Си-обработчики +extern idtp +extern isr_handler +extern irq_handler + +; Инструкция загрузки регистра таблицы прерываний +idt_load: + lidt [idtp] + ret + +; Макрос для исключений, КОТОРЫЕ НЕ ПУШАТ код ошибки (подставляем ложный 0) +%macro ISR_NOERRCODE 1 + idt_stub_isr_%1: + push byte 0 ; Ложный код ошибки + push byte %1 ; Номер прерывания + jmp isr_common_stub +%endmacro + +; Макрос для исключений, КОТОРЫЕ САМИ ПУШАТ код ошибки +%macro ISR_ERRCODE 1 + idt_stub_isr_%1: + ; Процессор уже напушил код ошибки сам + push byte %1 ; Номер прерывания + jmp isr_common_stub +%endmacro + +; Макрос для прерываний от железа (IRQ) +%macro IRQ 2 + idt_stub_irq_%2: + push byte 0 ; Ложный код ошибки + push byte %1 ; Номер прерывания (вектор в IDT, например 32) + jmp irq_common_stub +%endmacro + +; Генерируем заглушки с помощью макросов +ISR_NOERRCODE 0 +ISR_NOERRCODE 1 +ISR_NOERRCODE 2 +ISR_NOERRCODE 3 +ISR_NOERRCODE 4 +ISR_NOERRCODE 5 +ISR_NOERRCODE 6 +ISR_NOERRCODE 7 +ISR_ERRCODE 8 +ISR_NOERRCODE 9 +ISR_ERRCODE 10 +ISR_ERRCODE 11 +ISR_ERRCODE 12 +ISR_ERRCODE 13 +ISR_ERRCODE 14 +ISR_NOERRCODE 15 +ISR_NOERRCODE 16 +ISR_ERRCODE 17 +ISR_NOERRCODE 18 +ISR_NOERRCODE 19 +ISR_NOERRCODE 20 +ISR_ERRCODE 21 + +IRQ 32, 0 ; IRQ0 - Таймер +IRQ 33, 1 ; IRQ1 - Клавиатура + +; Перенаправляем глобальные имена на сгенерированные макросами точки +isr0: jmp idt_stub_isr_0 +isr1: jmp idt_stub_isr_1 +isr2: jmp idt_stub_isr_2 +isr3: jmp idt_stub_isr_3 +isr4: jmp idt_stub_isr_4 +isr5: jmp idt_stub_isr_5 +isr6: jmp idt_stub_isr_6 +isr7: jmp idt_stub_isr_7 +isr8: jmp idt_stub_isr_8 +isr9: jmp idt_stub_isr_9 +isr10: jmp idt_stub_isr_10 +isr11: jmp idt_stub_isr_11 +isr12: jmp idt_stub_isr_12 +isr13: jmp idt_stub_isr_13 +isr14: jmp idt_stub_isr_14 +isr15: jmp idt_stub_isr_15 +isr16: jmp idt_stub_isr_16 +isr17: jmp idt_stub_isr_17 +isr18: jmp idt_stub_isr_18 +isr19: jmp idt_stub_isr_19 +isr20: jmp idt_stub_isr_20 +isr21: jmp idt_stub_isr_21 + +irq0: jmp idt_stub_irq_0 +irq1: jmp idt_stub_irq_1 + +; Общая точка сборки для Исключений Процессора +isr_common_stub: + pushad ; Пушит edi, esi, ebp, esp, ebx, edx, ecx, eax + + mov ax, ds ; Сохраняем сегмент данных + push eax + + mov ax, 0x10 ; Загружаем ядерный сегмент данных в регистры + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + call isr_handler + + pop eax ; Восстанавливаем оригинальный сегмент данных + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + popad ; Восстанавливаем регистры общего назначения + add esp, 8 ; Вычищаем из стека код ошибки и номер прерывания + iretd ; Возвращаемся из прерывания + +; Общая точка сборки для Прерываний Железа (IRQ) +irq_common_stub: + pushad + + mov ax, ds + push eax + + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + call irq_handler + + pop eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + popad + add esp, 8 + iretd diff --git a/kernel/io.c b/kernel/io.c new file mode 100644 index 0000000..ebd1971 --- /dev/null +++ b/kernel/io.c @@ -0,0 +1,48 @@ +#include + +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; +} + +/* --- ВОРД (16 бит) --- */ + +// Запись ворда в порт (понадобится для звука AC97 и диска) +static inline void outw(uint16_t port, uint16_t val) { + __asm__ __volatile__("outw %0, %1" : : "a"(val), "Nd"(port)); +} + +// Чтение ворда из порта +static inline uint16_t inw(uint16_t port) { + uint16_t ret; + __asm__ __volatile__("inw %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +/* --- ДВОЙНОЙ ВОРД (32 бита) --- */ + +// Запись двойного ворда в порт (для PCI сканера в Части 4) +static inline void outl(uint16_t port, uint32_t val) { + __asm__ __volatile__("outl %0, %1" : : "a"(val), "Nd"(port)); +} + +// Чтение двойного ворда из порта +static inline uint32_t inl(uint16_t port) { + uint32_t ret; + __asm__ __volatile__("inl %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +/* --- ТОРМОЗ ДЛЯ СТАРЫХ МАТЕРИНОК --- */ + +// Короткая задержка (io_wait). Нужна при общении с контроллером прерываний PIC, +// чтобы старый чип успел переварить команду. Сбрасываем мусорный байт в неиспользуемый порт 0x80. +static inline void io_wait(void) { + __asm__ __volatile__("outb %%al, $0x80" : : "a"(0)); +} diff --git a/kernel/io.h b/kernel/io.h new file mode 100644 index 0000000..36e8fcf --- /dev/null +++ b/kernel/io.h @@ -0,0 +1,56 @@ +#ifndef IO_H +#define IO_H + +#include + +/* --- БАЙТ (8 бит) --- */ + +// Запись байта в порт +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; +} + +/* --- ВОРД (16 бит) --- */ + +// Запись ворда в порт (понадобится для звука AC97 и диска) +static inline void outw(uint16_t port, uint16_t val) { + __asm__ __volatile__("outw %0, %1" : : "a"(val), "Nd"(port)); +} + +// Чтение ворда из порта +static inline uint16_t inw(uint16_t port) { + uint16_t ret; + __asm__ __volatile__("inw %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +/* --- ДВОЙНОЙ ВОРД (32 бита) --- */ + +// Запись двойного ворда в порт (для PCI сканера в Части 4) +static inline void outl(uint16_t port, uint32_t val) { + __asm__ __volatile__("outl %0, %1" : : "a"(val), "Nd"(port)); +} + +// Чтение двойного ворда из порта +static inline uint32_t inl(uint16_t port) { + uint32_t ret; + __asm__ __volatile__("inl %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +/* --- ТОРМОЗ ДЛЯ СТАРЫХ МАТЕРИНОК --- */ + +// Короткая задержка (io_wait). Нужна при общении с контроллером прерываний PIC, +// чтобы старый чип успел переварить команду. Сбрасываем мусорный байт в неиспользуемый порт 0x80. +static inline void io_wait(void) { + __asm__ __volatile__("outb %%al, $0x80" : : "a"(0)); +} + +#endif diff --git a/kernel/kmain.c b/kernel/kmain.c index 0a8cc0c..7d33482 100644 --- a/kernel/kmain.c +++ b/kernel/kmain.c @@ -1,9 +1,19 @@ #include "../drivers/vga.h" +#include "../drivers/serial.h" +#include "gdt.h" +#include "idt.h" +#include "logger.h" void kernel_main(void) { + log_info(LOG_NORMAL, "Initializing serial..."); + serial_init(); + log_info(LOG_NORMAL, "Initializing VGA..."); vga_init(); - vga_set_color(VGA_COLOR_WHITE, VGA_COLOR_LIGHT_GREEN); - vga_println("--- BOOT PEREDOS ---"); - vga_println("---END BOOT PEREDOS---"); + log_info(LOG_NORMAL, "Setting color white on black"); + vga_set_color(VGA_COLOR_WHITE, VGA_COLOR_BLACK); + log_info(LOG_NORMAL, "Initializing GDT..."); + gdt_init(); + log_info(LOG_NORMAL, "Initializing IDT..."); + idt_init(); while (1); } diff --git a/kernel/logger.c b/kernel/logger.c new file mode 100644 index 0000000..6b2164f --- /dev/null +++ b/kernel/logger.c @@ -0,0 +1,38 @@ +#include "logger.h" +#include "../drivers/serial.h" +#include "../drivers/vga.h" + +void logger_init(void) { + serial_init(); + log_info(LOG_NORMAL, "Logger ready"); +} + +void klog(log_level_t level, const char* prefix, const char* message) { + // 1. Форматируем и отправляем в Serial в любом случае + kprint_serial("["); + kprint_serial(prefix); + kprint_serial("] "); + kprint_serial(message); + kprint_serial("\n"); + + // 2. Если уровень LOG_NORMAL, дублируем вывод на экран VGA + if (level == LOG_NORMAL) { + vga_print("["); + vga_print(prefix); + vga_print("] "); + vga_print(message); + vga_print("\n"); + } +} + +void log_info(log_level_t level, const char* message) { + klog(level, "INFO", message); +} + +void log_warn(log_level_t level, const char* message) { + klog(level, "WARN", message); +} + +void log_error(log_level_t level, const char* message) { + klog(level, "ERROR", message); +} diff --git a/kernel/logger.h b/kernel/logger.h new file mode 100644 index 0000000..e269022 --- /dev/null +++ b/kernel/logger.h @@ -0,0 +1,21 @@ +#ifndef LOGGER_H +#define LOGGER_H + +// Уровни логирования +typedef enum { + LOG_NORMAL, // И в VGA, и в Serial (для важных событий системы) + LOG_DETAIL // Только в Serial (детальная отладка, дампы регистров и т.д.) +} log_level_t; + +// Инициализация подсистемы +void logger_init(void); + +// Универсальная функция логирования +void klog(log_level_t level, const char* prefix, const char* message); + +// Удобные обёртки для повседневного использования +void log_info(log_level_t level, const char* message); +void log_warn(log_level_t level, const char* message); +void log_error(log_level_t level, const char* message); + +#endif