commit 32ced713c310da3b63fd89e87ab7713a3ee87e66 Author: msh356 Date: Sun Jul 19 14:41:16 2026 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..516f275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**.o +**.bin +zalupa.iso diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b3e2ce2 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# Настройки +CC = gcc +AS = nasm +LD = ld + +# Добавь эти флаги в CFLAGS: +CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra \ + -fno-stack-protector -fno-exceptions \ + -fno-pie -fno-stack-protector \ + -fno-asynchronous-unwind-tables \ + -mno-sse -mno-sse2 -mno-sse3 -mno-3dnow -mno-mmx \ + -I./src +ASFLAGS = -f elf32 +LDFLAGS = -m elf_i386 -T linker.ld -nostdlib + +# Файлы +SOURCES = $(wildcard src/*.c) +ASM_SOURCES = $(wildcard src/*.asm) +OBJECTS = src/boot.o $(SOURCES:.c=.o) $(ASM_SOURCES:.asm=.o) +TARGET = kernel.bin + +all: $(TARGET) iso + +# Сборка ядра +$(TARGET): $(OBJECTS) + $(LD) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +%.o: %.asm + $(AS) $(ASFLAGS) $< -o $@ + +# Сборка ISO +iso: $(TARGET) + mkdir -p iso/boot/ + cp $(TARGET) iso/boot/ + grub-mkrescue -o zalupa.iso iso/ + +run: iso + qemu-system-i386 -vga std -cdrom zalupa.iso -serial stdio -monitor none #-d int,cpu_reset -no-reboot -no-shutdown + +clean: + rm -f src/*.o $(TARGET) zalupa.iso + rm -rf iso/boot/$(TARGET) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a91a7de --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ + +

ZalupaCalc 2

+--- + +ZalupaCalc 2 - это инновационная операционная система, превращающая старые ПК в... калькуляторы. Ваш ПК не будет напрягаться вычислениями, т.к. из ОС выкинуто все, что НЕ НУЖНО калькулятору. + +## Роадмап + +### 1. Ядро + +- [x] Оно работает +- [x] IDT, GDT +- [x] Драйвер UART COM (чтение) +- [x] Драйвер клавиатуры PS/2 (базовый) +- [x] Драйвер клавиатуры PS/2 (нормальный, с Shift, Caps Lock и прочим) +- [x] Драйвер мыши +- [x] Драйвер таймера +- [x] Физ. менеджер памяти +- [x] Вирт. менеджер памяти +- [x] Аллокатор памяти +- [x] PC Speaker +- [x] Куча ядра + +### 2. Графика + +- [ ] Вывод на экран попиксельно +- [ ] Вывод на экран прямоугольников и прочих фигур +- [ ] Вывод на экран текста +- [ ] Двойная буферизация +- [ ] Вывод курсора мыши на экран, движение +- [ ] Кнопки (touch zones, массив в памяти где будут координаты областей и действия при нажатии) + +### 3. Математика + +- [ ] Простые операции (число операнд число, базовые операнды) +- [ ] Дроби, float +- [ ] Выражения (более сложные математические операции со скобками и расставлением порядка действий) +- [ ] Больше операндов +- [ ] Инженерные функции (sin, cos и че там еще у крутых математиков) + +### 4. Калькулятор + +- [ ] Окно +- [ ] Рабочий ввод с клавиатуры в калькулятор +- [ ] Рабочий ввод с мыши в калькулятор (кнопки калькулятора) + +### 5. Мелочевка + +- [ ] Часы в углу экрана +- [ ] Логотип загрузки +- [ ] Анимации + +### 6. Сохранение данных + +- [ ] Драйвер ATA PIO (чтение) +- [ ] Драйвер ATA PIO (запись) +- [ ] Парсер MBR +- [ ] Запись в MBR +- [ ] Собственная ФС (скорее хранилище данных) с настройками калькулятора, историей операций итд... +- [ ] Установщик + +### 7. Рофляные фукции + +- [ ] Напоминания о намазе +- [ ] Офлайн казино +- [ ] Офлайн покер +- [ ] Офлайн блекджек +- [ ] Сапер +- [ ] Пасьянс косынка + +## Где что есть что? + +- `assets/` - ассеты для README и системы +- `iso/` - корень ISO +- `src/` - там сурсы diff --git a/assets/logo.bmp b/assets/logo.bmp new file mode 100644 index 0000000..c7f3310 Binary files /dev/null and b/assets/logo.bmp differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..50fdab3 Binary files /dev/null and b/assets/logo.png differ diff --git a/iso/boot/grub/grub.cfg b/iso/boot/grub/grub.cfg new file mode 100644 index 0000000..de4ed8a --- /dev/null +++ b/iso/boot/grub/grub.cfg @@ -0,0 +1,7 @@ +set timeout=0 +set default=0 + +menuentry "ZalupaCalc 2" { + multiboot /boot/kernel.bin + boot +} diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..afcc7ef --- /dev/null +++ b/linker.ld @@ -0,0 +1,32 @@ +ENTRY(_start) + +SECTIONS +{ + /* Начинаем с 1 МБ */ + . = 1M; + + .text : AT(ADDR(.text)) + { + /* Принудительно размещаем заголовок Multiboot на первой позиции */ + KEEP(*(.multiboot)) + *(.text) + } + + .rodata : ALIGN(4K) + { + *(.rodata) + } + + .data : ALIGN(4K) + { + *(.data) + } + + .bss : ALIGN(4K) + { + *(COMMON) + *(.bss) + } + + _kernel_end = .; +} diff --git a/src/boot.asm b/src/boot.asm new file mode 100644 index 0000000..7d8b91e --- /dev/null +++ b/src/boot.asm @@ -0,0 +1,45 @@ +[bits 32] +section .multiboot + +MULTIBOOT_HEADER_MAGIC equ 0x1BADB002 +; Бит 0: выравнивание, Бит 1: meminfo, Бит 2: графический запрос +MULTIBOOT_HEADER_FLAGS equ 0x00000007 +MULTIBOOT_HEADER_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) + +header: + dd MULTIBOOT_HEADER_MAGIC + dd MULTIBOOT_HEADER_FLAGS + dd MULTIBOOT_HEADER_CHECKSUM + ; Эти поля обязательны для флага 0x00000004: + dd 0 ; header_addr (0 для ELF) + dd 0 ; load_addr + dd 0 ; load_end_addr + dd 0 ; bss_end_addr + dd 0 ; entry_addr + ; Теперь поля графики: + dd 0 ; mode_type (0 = линейный графический режим) + dd 1024 ; width + dd 768 ; height + dd 32 ; depth + +section .text +global _start +extern kernel_main + +_start: + mov esp, stack_top + push eax ; magic + push ebx ; mbi (здесь будет указатель на структуру с инфой о видео!) + + call kernel_main + +.hang: + cli + hlt + jmp .hang + +section .bss +align 16 +stack_bottom: + resb 16384 +stack_top: diff --git a/src/gdt.c b/src/gdt.c new file mode 100644 index 0000000..06439fb --- /dev/null +++ b/src/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/src/gdt.h b/src/gdt.h new file mode 100644 index 0000000..b165ef0 --- /dev/null +++ b/src/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/src/gdt_asm.asm b/src/gdt_asm.asm new file mode 100644 index 0000000..3d05439 --- /dev/null +++ b/src/gdt_asm.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/src/heap.c b/src/heap.c new file mode 100644 index 0000000..083f893 --- /dev/null +++ b/src/heap.c @@ -0,0 +1,83 @@ +#include "heap.h" +#include "vmm.h" +#include "pmm.h" +#include "serial.h" + +static heap_chunk_t* heap_start_chunk = NULL; + +void kheap_init(void) { + kprint_serial("HEAP: Initializing Kernel Heap...\n"); + + // 1. Выделяем одну физ. страницу под старт кучи + void* phys_page = pmm_alloc_page(); + + // 2. Мапим её на виртуальный адрес HEAP_START + // Нам нужны флаги Present (1) + Write (2) = 3 + vmm_map_page((uint32_t)phys_page, HEAP_START, 3); + + // 3. Создаем базовый блок на всю эту страницу + heap_start_chunk = (heap_chunk_t*)HEAP_START; + heap_start_chunk->size = PAGE_SIZE - sizeof(heap_chunk_t); + heap_start_chunk->is_free = 1; + heap_start_chunk->next = NULL; + + kprint_serial("HEAP: Kernel Heap initialized successfully at 0x00400000!\n"); +} + +void* kmalloc(size_t size) { + if (size == 0) return NULL; + + // Выравниваем размер по 4 байта для стабильности на x86 + size = (size + 3) & ~3; + + heap_chunk_t* current = heap_start_chunk; + + // Ищем подходящий свободный блок (First Fit) + while (current != NULL) { + if (current->is_free && current->size >= size) { + + // Если блок сильно больше, чем надо, дробим его + // sizeof(heap_chunk_t) + 4 байта минимально на остаток + if (current->size >= size + sizeof(heap_chunk_t) + 4) { + heap_chunk_t* new_chunk = (heap_chunk_t*)((uint32_t)current + sizeof(heap_chunk_t) + size); + new_chunk->size = current->size - size - sizeof(heap_chunk_t); + new_chunk->is_free = 1; + new_chunk->next = current->next; + + current->size = size; + current->next = new_chunk; + } + + current->is_free = 0; + + // Возвращаем указатель на память СРАЗУ ЗА заголовком + return (void*)((uint32_t)current + sizeof(heap_chunk_t)); + } + current = current->next; + } + + // По-хорошему, если памяти не хватило, нужно выделить еще страницу из PMM + // и расширить кучу. Но для первого теста выкинем панику. + kprint_serial("HEAP ERROR: OUT OF KERNEL HEAP MEMORY!\n"); + return NULL; +} + +void kfree(void* ptr) { + if (ptr == NULL) return; + + // Смещаемся назад, чтобы получить доступ к заголовку блока + heap_chunk_t* chunk = (heap_chunk_t*)((uint32_t)ptr - sizeof(heap_chunk_t)); + chunk->is_free = 1; + + // Склеиваем последовательные свободные блоки (Coalescing), чтобы не было фрагментации + heap_chunk_t* current = heap_start_chunk; + while (current != NULL) { + if (current->is_free && current->next && current->next->is_free) { + current->size += sizeof(heap_chunk_t) + current->next->size; + current->next = current->next->next; + // Не шагаем вперед, проверяем текущий блок еще раз с новым next + continue; + } + current = current->next; + } +} diff --git a/src/heap.h b/src/heap.h new file mode 100644 index 0000000..2c7ab77 --- /dev/null +++ b/src/heap.h @@ -0,0 +1,26 @@ +#ifndef HEAP_H +#define HEAP_H + +#include +#include + +// Начальный виртуальный адрес кучи ядра (3 МБ) +#define HEAP_START 0x00300000 + +// Структура заголовка блока памяти +typedef struct heap_chunk { + size_t size; // Размер полезных данных в байтах + uint8_t is_free; // Флаг: 1 - свободен, 0 - занят + struct heap_chunk* next; // Указатель на следующий блок +} heap_chunk_t; + +// Инициализация кучи +void kheap_init(void); + +// Выделение памяти +void* kmalloc(size_t size); + +// Освобождение памяти +void kfree(void* ptr); + +#endif diff --git a/src/idt.c b/src/idt.c new file mode 100644 index 0000000..2fdaeeb --- /dev/null +++ b/src/idt.c @@ -0,0 +1,74 @@ +#include "idt.h" +#include "io.h" +#include "serial.h" +#include "timer.h" +#include "keyboard.h" +#include "mouse.h" + +struct idt_entry idt[256]; +struct idt_ptr idtp; + +extern void* isr_table[]; // Убедись, что это массив указателей на isr0-isr31 +extern void irq0(), irq1(), irq12(), idt_load(); + +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(); extern void isr22(); extern void isr23(); +extern void isr24(); extern void isr25(); extern void isr26(); extern void isr27(); +extern void isr28(); extern void isr29(); extern void isr30(); extern void isr31(); + +void* isr_table[] = { + isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7, + isr8, isr9, isr10, isr11, isr12, isr13, isr14, isr15, + isr16, isr17, isr18, isr19, isr20, isr21, isr22, isr23, + isr24, isr25, isr26, isr27, isr28, isr29, isr30, isr31 +}; + +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; +} + +void idt_init(void) { + idtp.limit = (sizeof(struct idt_entry) * 256) - 1; + idtp.base = (uint32_t)&idt; + + for (int i = 0; i < 32; i++) + idt_set_gate(i, (uint32_t)isr_table[i], 0x08, 0x8E); + + idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E); + idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E); + idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E); + + idt_load(); +} + +void isr_handler(struct registers regs) { + kprint_serial("Exception: "); + kprint_serial_dec(regs.int_no); + kprint_serial("\nSystem Halted.\n"); + while(1) asm("hlt"); +} + +void irq_handler(struct registers regs) { + if (regs.int_no == 32) { + timer_handler(); + } else if (regs.int_no == 33) { + keyboard_handler(); + } else if (regs.int_no == 44) { + mouse_handler(); + } else { + kprint_serial("Unknown IRQ: "); + kprint_serial_dec(regs.int_no); + kprint_serial("\n"); + } + + if (regs.int_no >= 40) outb(0xA0, 0x20); // Slave + outb(0x20, 0x20); // Master +} diff --git a/src/idt.h b/src/idt.h new file mode 100644 index 0000000..1908b41 --- /dev/null +++ b/src/idt.h @@ -0,0 +1,36 @@ +#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; + // Эти два поля ДОЛЖНЫ идти именно в таком порядке, + // чтобы 32 попало в int_no + uint32_t int_no; + uint32_t 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/src/idt_asm.asm b/src/idt_asm.asm new file mode 100644 index 0000000..7207c8e --- /dev/null +++ b/src/idt_asm.asm @@ -0,0 +1,5 @@ +global idt_load +extern idtp +idt_load: + lidt [idtp] + ret diff --git a/src/interrupt.asm b/src/interrupt.asm new file mode 100644 index 0000000..43be5b4 --- /dev/null +++ b/src/interrupt.asm @@ -0,0 +1,86 @@ +; interrupt.asm +[extern isr_handler] +[extern irq_handler] + +%macro ISR_NOERR 1 +global isr%1 +isr%1: + cli + push 0 + push %1 + jmp common_isr_stub +%endmacro + +%macro ISR_ERR 1 +global isr%1 +isr%1: + cli + push %1 + jmp common_isr_stub +%endmacro + +; --- Исключения (0-31) --- +%assign i 0 +%rep 32 + %if i == 8 || (i >= 10 && i <= 14) || i == 17 + ISR_ERR i + %else + ISR_NOERR i + %endif + %assign i i+1 +%endrep + +; --- Аппаратные прерывания (IRQ -> Векторы 32, 33, 44) --- +%macro IRQ 2 +global irq%1 +irq%1: + cli + push 0 + push %2 + jmp common_irq_stub +%endmacro + +IRQ 0, 32 +IRQ 1, 33 +IRQ 12, 44 + +common_isr_stub: + pusha + mov ax, ds + push eax + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + push esp + call isr_handler + pop eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + popa + add esp, 8 + iret + +common_irq_stub: + pusha + mov ax, ds + push eax + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + push esp + call irq_handler + pop eax + pop eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + popa + add esp, 8 + iret diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..ebd1971 --- /dev/null +++ b/src/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/src/io.h b/src/io.h new file mode 100644 index 0000000..36e8fcf --- /dev/null +++ b/src/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/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..69861eb --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,121 @@ +#include "keyboard.h" +#include "serial.h" +#include "io.h" + +// Состояние модификаторов клавиатуры +static int is_e0 = 0; +static int shift_pressed = 0; +static int caps_lock_active = 0; + +// Скан-коды управления (Set 1) +#define SCAN_LSHIFT 0x2A +#define SCAN_RSHIFT 0x36 +#define SCAN_CAPS_LOCK 0x3A + +// Скан-коды стрелок (после 0xE0) +#define SCAN_UP 0x48 +#define SCAN_DOWN 0x50 +#define SCAN_LEFT 0x4B +#define SCAN_RIGHT 0x4D + +// Таблица 1: Обычные символы (без Shift) +static const char kbdus_lower[128] = { + 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', + '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', + 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, + '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, + ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Таблица 2: Символы при зажатом Shift +static const char kbdus_upper[128] = { + 0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', + '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', + 0, 'A', 's', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, + '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, + ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, '_', 0, 0, 0, '+', 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void keyboard_init(void) { + shift_pressed = 0; + caps_lock_active = 0; + is_e0 = 0; + kprint_serial("[INFO] Full keyboard driver with Modifiers initialized.\n"); +} + +void keyboard_handler(void) { + uint8_t status = inb(0x64); + + // Бит 5 равен 1, если данные пришли от мыши (Auxiliary Device) + // Мышь не должна обрабатываться здесь! + if (status & 0x20) { + // Очистим порт, чтобы не висел, но не обрабатываем как клаву + inb(0x60); + return; + } + + // Если мы здесь, значит это точно клавиатура + if (!(status & 0x01)) return; // Нет данных + + uint8_t scancode = inb(0x60); + + // Обработка префикса 0xE0 + if (scancode == 0xE0) { + is_e0 = 1; + return; + } + + // Обработка Break Code (отпускание) + if (scancode & 0x80) { + uint8_t released_code = scancode & 0x7F; + if (released_code == SCAN_LSHIFT || released_code == SCAN_RSHIFT) { + shift_pressed = 0; + } + is_e0 = 0; // Сброс префикса + return; + } + + // Обработка стрелок (расширенные) + if (is_e0) { + is_e0 = 0; + switch (scancode) { + case SCAN_UP: /* ... */ break; + case SCAN_DOWN: /* ... */ break; + case SCAN_LEFT: /* ... */ break; + case SCAN_RIGHT: /* ... */ break; + } + return; + } + + // Обычные модификаторы + if (scancode == SCAN_LSHIFT || scancode == SCAN_RSHIFT) { + shift_pressed = 1; + return; + } + + if (scancode == SCAN_CAPS_LOCK) { + caps_lock_active = !caps_lock_active; + return; + } + + // Печать символов + if (scancode < 128) { + char c = 0; + int use_upper = shift_pressed; + char base_char = kbdus_lower[scancode]; + int is_alpha = (base_char >= 'a' && base_char <= 'z'); + + if (is_alpha && caps_lock_active) use_upper = !use_upper; + + c = (use_upper) ? kbdus_upper[scancode] : kbdus_lower[scancode]; + + if (c != 0) { + char str[2] = {c, 0}; + kprint_serial(str); + } + } +} diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..424cc53 --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,10 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +// Инициализация (пока просто выведем лог, что всё ок) +void keyboard_init(void); + +// Основной обработчик прерывания, который мы вызовем из idt.c +void keyboard_handler(void); + +#endif diff --git a/src/kmain.c b/src/kmain.c new file mode 100644 index 0000000..d9bf1f1 --- /dev/null +++ b/src/kmain.c @@ -0,0 +1,171 @@ +#include "serial.h" +#include "mouse.h" +#include "timer.h" +#include "speaker.h" +#include "gdt.h" +#include "idt.h" +#include "heap.h" +#include "pmm.h" +#include "vmm.h" +#include "pic.h" +#include "logger.h" +#include "string.h" +#include "multiboot.h" + +extern uint32_t _kernel_end; // Символ 'end' прописан в конце любого стандартного linker.ld +uint32_t kernel_end = (uint32_t)&_kernel_end; + +void kernel_main(uint32_t mboot_addr, uint32_t magic) { + log_info(LOG_NORMAL, "Initializing serial..."); + serial_init(); + if (magic != MULTIBOOT_BOOTLOADER_MAGIC) { + log_error(LOG_NORMAL, "fuck, i got invalid multiboot magic. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?"); + while(1); + } + struct multiboot_info* mbi = (struct multiboot_info*)mboot_addr; + if (!(mbi->flags & (1 << 6))) { + log_error(LOG_NORMAL, "fuck, i haven't got a memory map. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?"); + while(1); + } + log_info(LOG_NORMAL, "Initializing GDT..."); + gdt_init(); + log_info(LOG_NORMAL, "Initializing IDT..."); + idt_init(); + log_info(LOG_NORMAL, "Remapping PIC..."); + pic_remap(32, 40); + log_info(LOG_NORMAL, "Unmasking IRQ 0... (timer)"); + pic_clear_mask(0); + log_info(LOG_NORMAL, "Unmasking IRQ 1... (keyboard)"); + pic_clear_mask(1); + log_info(LOG_NORMAL, "Unmasking IRQ 2... (slave pic)"); + pic_clear_mask(2); + log_info(LOG_NORMAL, "Unmasking IRQ 12... (mouse)"); + pic_clear_mask(12); + log_info(LOG_NORMAL, "Registering timer at 100Hz"); + timer_init(100); + log_info(LOG_NORMAL, "Registering mouse"); + mouse_init(); + kprint_serial("--------------------\n"); + log_info(LOG_NORMAL, "Enabling PMM..."); + pmm_init(mbi, (uint32_t)&_kernel_end); + kprint_serial("--- Memory Map From GRUB ---"); + struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr; + + while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) { + char addr_buf[32]; + char len_buf[32]; + + itoa((uint32_t)mmap->addr, addr_buf, 16); + itoa((uint32_t)mmap->len, len_buf, 10); + + kprint_serial("Region: Base=0x"); + kprint_serial(addr_buf); + kprint_serial(" Size="); + kprint_serial(len_buf); + + if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) { + kprint_serial(" [FREE RAM]\n"); + } else { + kprint_serial(" [RESERVED]\n"); + } + + // Переходим к следующей записи (учитываем поле size) + mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size)); + } + kprint_serial("--- trying to allocate some fucking memory... ---\n"); + kprint_serial("page 1... "); + void* p1 = pmm_alloc_page(); + kprint_serial("page 2..."); + void* p2 = pmm_alloc_page(); + kprint_serial("ok!"); + char buf[32]; + kprint_serial("PMM TEST: Page 1 allocated at 0x"); + itoa((uint32_t)p1, buf, 16); + kprint_serial(buf); + kprint_serial("\n"); + + kprint_serial("PMM TEST: Page 2 allocated at 0x"); + itoa((uint32_t)p2, buf, 16); + kprint_serial(buf); + kprint_serial("\n"); + + kprint_serial("freeing the pages back... page 1... "); + pmm_free_page(p1); + kprint_serial("page 2... "); + pmm_free_page(p2); + kprint_serial("it don't died!\n"); + + kprint_serial("\n--- PMM MEMORY STATS ---\n"); + + pmm_get_stats(); // Просто запускаем сбор, она запишет всё в глобальную переменную + + // Берем указатель на глобальную переменную + pmm_stats_t* my_stats = pmm_get_stats_ptr(); + + kprint_serial("Total Memory: "); + itoa(my_stats->total_memory >> 20, buf, 10); + kprint_serial(buf); + kprint_serial(" MB\n"); + + kprint_serial("Used Memory: "); + itoa(my_stats->used_memory >> 10, buf, 10); + kprint_serial(buf); + kprint_serial(" KB\n"); + + kprint_serial("Free Memory: "); + itoa(my_stats->free_memory >> 20, buf, 10); + kprint_serial(buf); + kprint_serial(" MB\n"); + + kprint_serial("------------------------\n\n"); + log_info(LOG_NORMAL, "Enabling VMM..."); + vmm_init(); + log_info(LOG_NORMAL, "Enabling KHeap..."); + kheap_init(); + + kprint_serial("--- HEAP TESTING ---\n"); + + // Выделяем три разных куска памяти + int* arr1 = (int*)kmalloc(10 * sizeof(int)); + char* str = (char*)kmalloc(64); + int* arr2 = (int*)kmalloc(5 * sizeof(int)); + char hbuf[32]; + kprint_serial("Allocated arr1 at virtual address: 0x"); + itoa((uint32_t)arr1, hbuf, 16); + kprint_serial(hbuf); + kprint_serial("\n"); + kprint_serial("Allocated str at virtual address: 0x"); + itoa((uint32_t)str, hbuf, 16); + kprint_serial(hbuf); + kprint_serial("\n"); + // Запишем туда данные, чтобы проверить, что маппинг страниц не падает при записи + arr1[0] = 1337; + str[0] = 'H'; + str[1] = 'A'; + str[2] = 'I'; + str[3] = '\0'; + kprint_serial("Data integrity check: str is "); + kprint_serial(str); + kprint_serial("\n"); + // Освобождаем память обратно + kfree(arr1); + kfree(str); + kfree(arr2); + + kprint_serial("Kfree done. It don't died twice!\n"); + log_info(LOG_NORMAL, "Enabling IRQ..."); + __asm__ __volatile__("sti"); + speaker_beep(100, 10); + speaker_beep(125, 10); + speaker_beep(150, 10); + speaker_beep(175, 10); + speaker_beep(200, 10); + speaker_beep(225, 10); + speaker_beep(250, 10); + speaker_beep(275, 10); + speaker_beep(300, 10); + speaker_beep(325, 10); + speaker_beep(350, 400); + log_info(LOG_NORMAL, "OK!"); + while (1); +} diff --git a/src/logger.c b/src/logger.c new file mode 100644 index 0000000..d73bbe7 --- /dev/null +++ b/src/logger.c @@ -0,0 +1,27 @@ +#include "logger.h" +#include "serial.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(prefix); + kprint_serial(": "); + kprint_serial(message); + kprint_serial("\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/src/logger.h b/src/logger.h new file mode 100644 index 0000000..e269022 --- /dev/null +++ b/src/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 diff --git a/src/mouse.c b/src/mouse.c new file mode 100644 index 0000000..a9bd082 --- /dev/null +++ b/src/mouse.c @@ -0,0 +1,128 @@ +#include "mouse.h" +#include "io.h" +#include "string.h" +#include "serial.h" // Для kprint_serial + +#define PS2_DATA 0x60 +#define PS2_STATUS 0x64 +#define PS2_COMMAND 0x64 + +static void ps2_wait_write(void) { + while (inb(PS2_STATUS) & 0x02); +} + +static void ps2_wait_read(void) { + while (!(inb(PS2_STATUS) & 0x01)); +} + +static void mouse_write(uint8_t data) { + ps2_wait_write(); + outb(PS2_COMMAND, 0xD4); // Сигнал: шлем команду именно мыши + ps2_wait_write(); + outb(PS2_DATA, data); +} + +static uint8_t mouse_read(void) { + ps2_wait_read(); + return inb(PS2_DATA); +} + +void mouse_init(void) { + uint8_t status; + + kprint_serial("Initializing PS/2 Mouse...\n"); + + // Включаем второй порт PS/2 (мышь) + ps2_wait_write(); + outb(PS2_COMMAND, 0xA8); + io_wait(); + + // Читаем Command Byte, чтобы включить прерывания + ps2_wait_write(); + outb(PS2_COMMAND, 0x20); + ps2_wait_read(); + status = inb(PS2_DATA) | 2; // Бит 1 — прерывания IRQ12 от мыши + status &= ~0x20; // Сбрасываем Бит 5 — активируем Mouse Clock + + // Записываем Command Byte обратно + ps2_wait_write(); + outb(PS2_COMMAND, 0x60); + ps2_wait_write(); + outb(PS2_DATA, status); + + // Настраиваем саму мышь + mouse_write(0xF6); // Загрузить дефолтные параметры + mouse_read(); // Читаем ACK (0xFA) + + mouse_write(0xF4); // Включаем передачу пакетов (Data Reporting) + mouse_read(); // Читаем ACK (0xFA) + + kprint_serial("Mouse initialized successfully.\n"); +} + +static uint8_t mouse_cycle = 0; +static uint8_t mouse_packet[3]; +static int mouse_x = 40; // Стартуем по центру текстового экрана 80x25 +static int mouse_y = 12; + +void mouse_handler(void) { + uint8_t status = inb(PS2_STATUS); + + // Проверяем, что в буфере реально есть данные и они от мыши (бит 5 выставлен) + if (!(status & 0x01) || !(status & 0x20)) { + return; + } + + // ДЕБАГ: выводим каждый байт + kprint_serial("M_BYTE: "); + kprint_serial_hex(status); + kprint_serial("\n"); + + mouse_packet[mouse_cycle++] = inb(PS2_DATA); + + if (mouse_cycle == 3) { + mouse_cycle = 0; + + // Бит 3 первого байта всегда должен быть равен 1 (проверка синхронизации) + if (!(mouse_packet[0] & 0x08)) { + return; + } + + uint8_t flags = mouse_packet[0]; + int offset_x = (int)mouse_packet[1]; + int offset_y = (int)mouse_packet[2]; + + // Обработка знаков смещения (8 бит -> 32 бита с сохранением знака) + if (flags & 0x10) offset_x |= 0xFFFFFF00; + if (flags & 0x20) offset_y |= 0xFFFFFF00; + + mouse_x += offset_x; + mouse_y -= offset_y; // Инвертируем Y, так как в VGA координатах 0 верх + + // Ограничиваем рамками консоли 80x25 + if (mouse_x < 0) mouse_x = 0; + if (mouse_x >= 80) mouse_x = 79; + if (mouse_y < 0) mouse_y = 0; + if (mouse_y >= 25) mouse_y = 24; + + // Чек кнопок + int left_click = flags & 0x01; + int right_click = flags & 0x02; + + // Выводим инфу в UART через itoa + char buf[16]; + + kprint_serial("Mouse: X="); + itoa(mouse_x, buf, 10); + kprint_serial(buf); + + kprint_serial(" Y="); + itoa(mouse_y, buf, 10); + kprint_serial(buf); + + if (left_click) kprint_serial(" [LEFT]"); + if (right_click) kprint_serial(" [RIGHT]"); + + kprint_serial("\n"); + } +} diff --git a/src/mouse.h b/src/mouse.h new file mode 100644 index 0000000..f42b004 --- /dev/null +++ b/src/mouse.h @@ -0,0 +1,9 @@ +#ifndef MOUSE_H +#define MOUSE_H + +#include + +void mouse_init(void); +void mouse_handler(void); + +#endif diff --git a/src/multiboot.h b/src/multiboot.h new file mode 100644 index 0000000..bc41b3a --- /dev/null +++ b/src/multiboot.h @@ -0,0 +1,47 @@ +#ifndef MULTIBOOT_H +#define MULTIBOOT_H + +#include + +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 + +struct multiboot_mmap_entry { + uint32_t size; + uint64_t addr; + uint64_t len; +#define MULTIBOOT_MEMORY_AVAILABLE 1 +#define MULTIBOOT_MEMORY_RESERVED 2 +#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 +#define MULTIBOOT_MEMORY_NVS 4 +#define MULTIBOOT_MEMORY_BADRAM 5 + uint32_t type; +} __attribute__((packed)); + +struct multiboot_info { + uint32_t flags; + uint32_t mem_lower; + uint32_t mem_upper; + uint32_t boot_device; + uint32_t cmdline; + uint32_t mods_count; + uint32_t mods_addr; + uint32_t num; + uint32_t size; + uint32_t addr; + uint32_t shndx; + uint32_t mmap_length; + uint32_t mmap_addr; // <-- Вот это нам нужно! Адрес карты памяти + uint32_t drives_length; + uint32_t drives_addr; + uint32_t config_table; + uint32_t boot_loader_name; + uint32_t apm_table; + uint32_t vbe_control_info; + uint32_t vbe_mode_info; + uint16_t vbe_mode; + uint16_t vbe_interface_seg; + uint16_t vbe_interface_off; + uint16_t vbe_interface_len; +} __attribute__((packed)); + +#endif diff --git a/src/pic.c b/src/pic.c new file mode 100644 index 0000000..6199f21 --- /dev/null +++ b/src/pic.c @@ -0,0 +1,76 @@ +#include "pic.h" +#include "io.h" +#include "logger.h" + +#define PIC1_COMMAND 0x20 +#define PIC1_DATA 0x21 +#define PIC2_COMMAND 0xA0 +#define PIC2_DATA 0xA1 + +#define ICW1_INIT 0x10 // Инициализация +#define ICW1_IC4 0x01 // Будет ICW4 +#define ICW4_8086 0x01 // Режим 8086/8186 + +void pic_remap(uint8_t master_offset, uint8_t slave_offset) { + log_info(LOG_DETAIL, "Remapping PIC..."); + + // Сохраняем текущие маски прерываний (какое железо сейчас отключено) + uint8_t a1 = inb(PIC1_DATA); + uint8_t a2 = inb(PIC2_DATA); + + // Свисток к началу инициализации (ICW1) + outb(PIC1_COMMAND, ICW1_INIT | ICW1_IC4); + io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_IC4); + io_wait(); + + // Задаем новые смещения векторов (ICW2) + outb(PIC1_DATA, master_offset); // Master IRQ 0-7 -> вешаем на master_offset (32) + io_wait(); + outb(PIC2_DATA, slave_offset); // Slave IRQ 8-15 -> вешаем на slave_offset (40) + io_wait(); + + // Связываем Master и Slave между собой (ICW3) + outb(PIC1_DATA, 4); // Сообщаем Master, что Slave сидит на IRQ2 (00000100б = 4) + io_wait(); + outb(PIC2_DATA, 2); // Сообщаем Slave его каскадный ID (2) + io_wait(); + + // Включаем режим 8086 (ICW4) + outb(PIC1_DATA, ICW4_8086); + io_wait(); + outb(PIC2_DATA, ICW4_8086); + io_wait(); + + // Восстанавливаем сохраненные маски + outb(PIC1_DATA, a1); + outb(PIC2_DATA, a2); + + log_info(LOG_DETAIL, "PIC remapped"); +} + +// Запретить прерывание от конкретного IRQ +void pic_set_mask(uint8_t irq_line) { + uint16_t port; + if (irq_line < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + irq_line -= 8; + } + uint8_t value = inb(port) | (1 << irq_line); + outb(port, value); +} + +// Разрешить прерывание от конкретного IRQ +void pic_clear_mask(uint8_t irq_line) { + uint16_t port; + if (irq_line < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + irq_line -= 8; + } + uint8_t value = inb(port) & ~(1 << irq_line); + outb(port, value); +} diff --git a/src/pic.h b/src/pic.h new file mode 100644 index 0000000..bd94bf9 --- /dev/null +++ b/src/pic.h @@ -0,0 +1,13 @@ +#ifndef PIC_H +#define PIC_H + +#include + +// Перемапливание базовых векторов прерываний для Master и Slave PIC +void pic_remap(uint8_t master_offset, uint8_t slave_offset); + +// Маскирование/размаскирование конкретной линии IRQ +void pic_set_mask(uint8_t irq_line); +void pic_clear_mask(uint8_t irq_line); + +#endif diff --git a/src/pmm.c b/src/pmm.c new file mode 100644 index 0000000..62859ec --- /dev/null +++ b/src/pmm.c @@ -0,0 +1,119 @@ +#include "pmm.h" +#include "string.h" +#include "logger.h" +#include "serial.h" + +// Указатель на начало нашей битовой карты в памяти +static uint32_t* pmm_bitmap = NULL; +static uint32_t pmm_bitmap_size = 0; +static uint32_t total_pages = 0; + +static pmm_stats_t global_stats; + +// Вспомогательные инлайны для быстрой работы с битами ядра +static inline void bitmap_set(uint32_t page_index) { + pmm_bitmap[page_index / 32] |= (1 << (page_index % 32)); +} + +static inline void bitmap_clear(uint32_t page_index) { + pmm_bitmap[page_index / 32] &= ~(1 << (page_index % 32)); +} + +static inline int bitmap_test(uint32_t page_index) { + return (pmm_bitmap[page_index / 32] & (1 << (page_index % 32))) != 0; +} + +void pmm_init(struct multiboot_info* mbi, uint32_t kernel_end) { + // Рассчитываем менеджер на 128 МБ памяти + uint32_t mem_size = 128 * 1024 * 1024; + total_pages = mem_size / PAGE_SIZE; + + // Саму карту пихаем прямо за концом ядра (куда указывает линкер) + pmm_bitmap = (uint32_t*)kernel_end; + pmm_bitmap_size = total_pages / 8; // Размер карты в байтах (4096 байт) + + uint32_t bitmap_end = kernel_end + pmm_bitmap_size; + + // Шаг 1: Помечаем вообще всё как занятое (1). Безопасность превыше всего. + memset(pmm_bitmap, 0xFF, pmm_bitmap_size); + + // Шаг 2: Парсим карту памяти от GRUB и открываем доступные регионы + struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr; + + while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) { + // Если GRUB говорит, что этот кусок RAM свободен для юзания + if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) { + uint32_t start_addr = (uint32_t)mmap->addr; + uint32_t end_addr = start_addr + (uint32_t)mmap->len; + + for (uint32_t addr = start_addr; addr < end_addr; addr += PAGE_SIZE) { + uint32_t page_idx = addr / PAGE_SIZE; + if (page_idx < total_pages) { + bitmap_clear(page_idx); // Ставим 0 (страница свободна) + } + } + } + // Шагаем к следующей записи в таблице Multiboot + mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size)); + } + + // Шаг 3: Жестко защищаем саму ОС! + // Всё, что лежит от физического адреса 0x0 до bitmap_end (код ядра, стек, сама битовая карта), + // мы принудительно забиваем единицами, чтобы аллокатор туда никогда не залез. + for (uint32_t addr = 0; addr < bitmap_end; addr += PAGE_SIZE) { + bitmap_set(addr / PAGE_SIZE); + } + + log_info(LOG_NORMAL, "pmm works."); +} + +void* pmm_alloc_page(void) { + // Тупо линейно ищем первый попавшийся бит, равный 0 + for (uint32_t i = 0; i < total_pages; i++) { + if (!bitmap_test(i)) { + bitmap_set(i); // Застолбили страницу (поставили 1) + return (void*)(i * PAGE_SIZE); // Считаем её реальный физический адрес + } + } + + log_error(LOG_NORMAL, "PMM: FUCK, WHERE IS ALL OUR FUCKING MEMORY?!"); + return NULL; +} + +void pmm_free_page(void* addr) { + uint32_t page_idx = ((uint32_t)addr) / PAGE_SIZE; + if (page_idx < total_pages) { + bitmap_clear(page_idx); // Сбросили в 0, страница снова чиста + } +} + +void pmm_get_stats(void) { + kprint_serial("Someone wants memory stats! Running the test!\n"); + uint32_t used = 0; + uint32_t free = 0; + + for (uint32_t i = 0; i < total_pages; i++) { + if (bitmap_test(i)) { + used++; + } else { + free++; + } + } + + kprint_serial("Okay, i am collected the stats. "); + + // Пишем в ГЛОБАЛЬНУЮ переменную + global_stats.total_pages = total_pages; + global_stats.used_pages = used; + global_stats.free_pages = free; + global_stats.total_memory = total_pages << 12; + global_stats.used_memory = used << 12; + global_stats.free_memory = free << 12; + + kprint_serial("Goodbye!\n"); +} + +// Добавим функцию-геттер, которая просто вернет указатель на эту глобальную память +pmm_stats_t* pmm_get_stats_ptr(void) { + return &global_stats; +} diff --git a/src/pmm.h b/src/pmm.h new file mode 100644 index 0000000..689176b --- /dev/null +++ b/src/pmm.h @@ -0,0 +1,30 @@ +#ifndef PMM_H +#define PMM_H + +#include +#include "multiboot.h" + +#define PAGE_SIZE 4096 + +// Инициализация: передаем инфу от GRUB и адрес конца ядра из линкера +void pmm_init(struct multiboot_info* mbi, uint32_t kernel_end); + +// Выделить одну страницу памяти (возвращает физический адрес) +void* pmm_alloc_page(void); + +// Освободить страницу памяти по адресу +void pmm_free_page(void* addr); + +typedef struct { + uint32_t total_memory; // Всего памяти в байтах + uint32_t used_memory; // Занято в байтах + uint32_t free_memory; // Свободно в байтах + uint32_t total_pages; // Всего страниц + uint32_t used_pages; // Занято страниц + uint32_t free_pages; // Свободно страниц +} pmm_stats_t; + +void pmm_get_stats(void); +pmm_stats_t* pmm_get_stats_ptr(void); + +#endif diff --git a/src/serial.c b/src/serial.c new file mode 100644 index 0000000..c39c816 --- /dev/null +++ b/src/serial.c @@ -0,0 +1,60 @@ + #include "io.h" // Подключаем наши порты +#include + +#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]); + } +} + +void kprint_serial_hex(uint32_t num) { + const char hex_chars[] = "0123456789ABCDEF"; + char buffer[11]; // "0x" + 8 символов + '\0' + + buffer[0] = '0'; + buffer[1] = 'x'; + buffer[10] = '\0'; + + // Заполняем с конца (8 символов) + for (int i = 9; i >= 2; i--) { + buffer[i] = hex_chars[num & 0xF]; + num >>= 4; + } + + kprint_serial(buffer); +} + +void kprint_serial_dec(uint32_t num) { + if (num == 0) { + kprint_serial("0"); + return; + } + char buf[12]; + int i = 10; + buf[11] = '\0'; + while (num > 0) { + buf[i--] = (num % 10) + '0'; + num /= 10; + } + kprint_serial(&buf[i + 1]); +} diff --git a/src/serial.h b/src/serial.h new file mode 100644 index 0000000..b4c18cd --- /dev/null +++ b/src/serial.h @@ -0,0 +1,20 @@ +#ifndef SERIAL_H +#define SERIAL_H +#include + +// Инициализация COM1 порта на скорость 38400 бод, 8N1 +void serial_init(void); + +// Проверка, пуст ли передающий буфер порта +int is_transmit_empty(void); + +// Отправка одного символа в COM1 +void serial_putc(char c); + +// Вывод строки (логов) в последовательный порт +void kprint_serial(const char* str); + +void kprint_serial_hex(uint32_t num); +void kprint_serial_dec(uint32_t num); + +#endif diff --git a/src/speaker.c b/src/speaker.c new file mode 100644 index 0000000..c626964 --- /dev/null +++ b/src/speaker.c @@ -0,0 +1,48 @@ +#include "speaker.h" +#include "timer.h" // Теперь здесь виден extern uint32_t system_ticks +#include "io.h" + +void sleep_ms(uint32_t ms) { + // 1 тик = 10 мс на частоте 100 Гц + uint32_t ticks_to_wait = ms / 10; + if (ticks_to_wait == 0) ticks_to_wait = 1; // Защита от слишком маленького интервала + + uint32_t start_ticks = system_ticks; + + // Ждем, пока разница тиков не достигнет нужного значения + while ((system_ticks - start_ticks) < ticks_to_wait) { + // Аппаратно засыпаем до следующего прерывания PIT + __asm__ __volatile__("hlt"); + } +} + +void speaker_play(uint32_t frequency) { + if (frequency == 0) return; + + uint32_t divisor = 1193182 / frequency; + + // Настраиваем Канал 2 PIT (порт 0x43, режим 0xB6) + outb(0x43, 0xB6); + + // Передаем делитель в порт данных Канала 2 (0x42) + outb(0x42, (uint8_t)(divisor & 0xFF)); + outb(0x42, (uint8_t)((divisor >> 8) & 0xFF)); + + // Подключаем PIT к динамику через порт управления системными функциями + uint8_t speaker_state = inb(0x61); + if ((speaker_state & 3) != 3) { + outb(0x61, speaker_state | 3); + } +} + +void speaker_stop(void) { + // Отключаем динамик, сбрасывая 0 и 1 биты + uint8_t speaker_state = inb(0x61) & 0xFC; + outb(0x61, speaker_state); +} + +void speaker_beep(uint32_t frequency, uint32_t duration_ms) { + speaker_play(frequency); + sleep_ms(duration_ms); // Наш халяльный sleep по прерываниям + speaker_stop(); +} diff --git a/src/speaker.h b/src/speaker.h new file mode 100644 index 0000000..f42f0e3 --- /dev/null +++ b/src/speaker.h @@ -0,0 +1,15 @@ +#ifndef SPEAKER_H +#define SPEAKER_H + +#include + +// Включить звук заданной частоты (в Герцах) +void speaker_play(uint32_t frequency); + +// Выключить звук +void speaker_stop(void); + +// Издать короткий писк (бип) заданной частоты и длительности +void speaker_beep(uint32_t frequency, uint32_t duration); + +#endif diff --git a/src/string.c b/src/string.c new file mode 100644 index 0000000..3233f8f --- /dev/null +++ b/src/string.c @@ -0,0 +1,183 @@ +#include "string.h" + +// Заполнить кусок памяти определенным байтом (очень нужно для очистки буферов) +void* memset(void* dest, int val, size_t count) { + uint8_t* temp = (uint8_t*)dest; + for (size_t i = 0; i < count; i++) { + temp[i] = (uint8_t)val; + } + return dest; +} + +// Скопировать кусок памяти (будем использовать для скроллинга VGA экрана) +void* memcpy(void* dest, const void* src, size_t count) { + uint8_t* d = (uint8_t*)dest; + const uint8_t* s = (const uint8_t*)src; + for (size_t i = 0; i < count; i++) { + d[i] = s[i]; + } + return dest; +} + +// Скопировать кусок памяти с учетом возможного пересечения областей +void* memmove(void* dest, const void* src, size_t count) { + uint8_t* d = (uint8_t*)dest; + const uint8_t* s = (const uint8_t*)src; + + if (d < s) { + for (size_t i = 0; i < count; i++) { + d[i] = s[i]; + } + } else { + for (size_t i = count; i > 0; i--) { + d[i - 1] = s[i - 1]; + } + } + return dest; +} + +// Сравнение блоков памяти +int memcmp(const void* s1, const void* s2, size_t count) { + const uint8_t* p1 = (const uint8_t*)s1; + const uint8_t* p2 = (const uint8_t*)s2; + for (size_t i = 0; i < count; i++) { + if (p1[i] != p2[i]) { + return p1[i] - p2[i]; + } + } + return 0; +} + +// Длина строки +size_t strlen(const char* str) { + size_t len = 0; + while (str[len] != '\0') { + len++; + } + return len; +} + +// Копирование строки +char* strcpy(char* dest, const char* src) { + size_t i = 0; + while ((dest[i] = src[i]) != '\0') { + i++; + } + return dest; +} + +// Конкатенация строк +char* strcat(char* dest, const char* src) { + size_t dest_len = strlen(dest); + size_t i = 0; + while ((dest[dest_len + i] = src[i]) != '\0') { + i++; + } + return dest; +} + +// Сравнение строк (понадобится, когда будем парсить команды в терминале) +int strcmp(const char* s1, const char* s2) { + while (*s1 && (*s1 == *s2)) { + s1++; + s2++; + } + return *(const unsigned char*)s1 - *(const unsigned char*)s2; +} + +// Строка в число +int atoi(const char* str) { + int res = 0; + int sign = 1; + int i = 0; + + // Пропускаем пробелы, если они есть + while (str[i] == ' ' || str[i] == '\t') { + i++; + } + + // Учитываем знак + if (str[i] == '-') { + sign = -1; + i++; + } else if (str[i] == '+') { + i++; + } + + // Считаем само число + while (str[i] >= '0' && str[i] <= '9') { + res = res * 10 + (str[i] - '0'); + i++; + } + + return sign * res; +} + +// Число в строку (поддерживает base = 10 и base = 2) +char* itoa(int value, char* str, int base) { + int i = 0; + int is_negative = 0; + + // Защита от дурака + if (base < 2 || base > 36) { + str[0] = '\0'; + return str; + } + + // Обрабатываем 0 + if (value == 0) { + str[i++] = '0'; + str[i] = '\0'; + return str; + } + + // Отрицательные числа обрабатываем только для десятичной системы + if (value < 0 && base == 10) { + is_negative = 1; + value = -value; + } + + // Переводим число в строку (но она получится задом наперед) + while (value != 0) { + int rem = value % base; + str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0'; + value = value / base; + } + + // Добавляем минус + if (is_negative) { + str[i++] = '-'; + } + + str[i] = '\0'; + + // Переворачиваем строку в нормальный вид + int start = 0; + int end = i - 1; + while (start < end) { + char temp = str[start]; + str[start] = str[end]; + str[end] = temp; + start++; + end--; + } + + return str; +} + +// Число в красивый Hex вид (например, 255 -> "0x000000ff") +char* itox(uint32_t value, char* str) { + char* hex_digits = "0123456789abcdef"; + + str[0] = '0'; + str[1] = 'x'; + str[10] = '\0'; // 2 символа на "0x" + 8 символов на 32-битное число + + // Заполняем с конца + for (int i = 9; i >= 2; i--) { + str[i] = hex_digits[value & 0xF]; + value >>= 4; + } + + return str; +} diff --git a/src/string.h b/src/string.h new file mode 100644 index 0000000..f3488d0 --- /dev/null +++ b/src/string.h @@ -0,0 +1,28 @@ +#ifndef STRING_H +#define STRING_H + +#include +#include + +// Работа с памятью +void* memset(void* dest, int val, size_t count); +void* memcpy(void* dest, const void* src, size_t count); +void* memmove(void* dest, const void* src, size_t count); +int memcmp(const void* s1, const void* s2, size_t count); + +// Работа со строками +size_t strlen(const char* str); +char* strcpy(char* dest, const char* src); +char* strcat(char* dest, const char* src); +int strcmp(const char* s1, const char* s2); + +// Перевод строки в число +int atoi(const char* str); + +// Перевод числа в строку (десятичную) +char* itoa(int value, char* str, int base); + +// Специальный быстрый аналог для Hex (16-ричной системы), незаменим для дампов памяти +char* itox(uint32_t value, char* str); + +#endif diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..5fb319a --- /dev/null +++ b/src/timer.c @@ -0,0 +1,27 @@ +#include "timer.h" +#include "io.h" +#include "serial.h" +#include "logger.h" + +// Глобальный счетчик тиков системы +uint32_t system_ticks = 0; + +void timer_init(uint32_t frequency) { + log_info(LOG_NORMAL, "Initializing PIT Timer..."); + + // Вычисляем делитель частоты + uint32_t divisor = 1193182 / frequency; + + // Шлем команду: Канал 0, Режим 3 (генератор меандра), доступ по очереди Младший/Старший байт + outb(0x43, 0x36); + + // Раскалываем 16-битный делитель на два байта и шлем в порт данных Канала 0 (0x40) + outb(0x40, (uint8_t)(divisor & 0xFF)); + outb(0x40, (uint8_t)((divisor >> 8) & 0xFF)); + + log_info(LOG_NORMAL, "Timer registered"); +} + +void timer_handler(void) { + system_ticks++; +} diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 0000000..ee61398 --- /dev/null +++ b/src/timer.h @@ -0,0 +1,12 @@ +#ifndef TIMER_H +#define TIMER_H + +#include + +// Делаем счетчик видимым для всего ядра +extern uint32_t system_ticks; + +void timer_init(uint32_t frequency); +void timer_handler(void); + +#endif diff --git a/src/vmm.c b/src/vmm.c new file mode 100644 index 0000000..ffd202f --- /dev/null +++ b/src/vmm.c @@ -0,0 +1,75 @@ +#include "vmm.h" +#include "pmm.h" +#include "serial.h" + +// 1. Обязательное выравнивание для CR3 и таблиц страниц +uint32_t page_directory[1024] __attribute__((aligned(4096))); +uint32_t first_page_table[1024] __attribute__((aligned(4096))); + +extern void vmm_enable_paging(uint32_t* page_directory); + +void vmm_init(void) { + kprint_serial("VMM: Initializing paging...\n"); + + // 2. Очищаем каталог (ставим Not Present) + for (int i = 0; i < 1024; i++) { + page_directory[i] = 0x00000002; // Read/Write, Not Present + } + + // 3. Identity Map для первых 4 МБ + // i * 4096 дает физический адрес страницы + for (uint32_t i = 0; i < 1024; i++) { + first_page_table[i] = (i * 0x1000) | 0x3; // Present + RW + } + + // 4. Указываем каталогу на таблицу + // (uint32_t)first_page_table — это уже физический адрес, + // если ядро по 1МБ и мы не используем сложные смещения. + page_directory[0] = ((uint32_t)first_page_table) | 0x3; + + kprint_serial("DEBUG: page_directory is at: 0x"); + kprint_serial_hex((uint32_t)page_directory); + kprint_serial("\n"); + + kprint_serial("DEBUG: page_directory[0] contains: 0x"); + kprint_serial_hex(page_directory[0]); + kprint_serial("\n"); + + kprint_serial("VMM: Paging configured. Enabling CR0...\n"); + + // 5. Включаем + vmm_enable_paging(page_directory); + + kprint_serial("VMM: FUCK, IT WORKS!\n"); +} + +void vmm_map_page(uint32_t phys, uint32_t virt, uint32_t flags) { + // Выковыриваем индексы из виртуального адреса + uint32_t pd_idx = virt >> 22; // Старшие 10 бит — индекс каталога + uint32_t pt_idx = (virt >> 12) & 0x3FF; // Средние 10 бит — индекс таблицы + + if (!(page_directory[pd_idx] & 1)) { + // 1. Выделяем физическую страницу под НОВУЮ таблицу + uint32_t phys_table = (uint32_t)pmm_alloc_page(); + + // 2. Ставим её в каталог (Present + Write) + page_directory[pd_idx] = (phys_table & 0xFFFFF000) | 3; + + // 3. Очищаем новую таблицу (важно!) + uint32_t* new_table = (uint32_t*)(page_directory[pd_idx] & 0xFFFFF000); + for(int i=0; i<1024; i++) new_table[i] = 0; + } + + // Достаем адрес таблицы страниц из каталога (обнуляем нижние 12 бит флагов) + uint32_t* table = (uint32_t*)(page_directory[pd_idx] & 0xFFFFF000); + + // Запихиваем физический адрес и флаги в таблицу страниц + // PAGE_SIZE у нас 4096, поэтому phys должен быть выровнен (нижние 12 бит затрутся флагами) + table[pt_idx] = (phys & 0xFFFFF000) | flags; + + // ВАЖНО: Процессор кэширует таблицы в TLB (Translation Lookaside Buffer). + // Если мы изменили маппинг существующей страницы, процессор об этом не узнает. + // Нужно сбросить кэш для этого адреса инструкцией invlpg: + __asm__ __volatile__("invlpg (%0)" : : "r"(virt) : "memory"); +} + diff --git a/src/vmm.h b/src/vmm.h new file mode 100644 index 0000000..9270791 --- /dev/null +++ b/src/vmm.h @@ -0,0 +1,17 @@ +#ifndef VMM_H +#define VMM_H + +#include + +// Элемент каталога/таблицы страниц +#define PAGE_PRESENT 0x1 // Страница есть в памяти +#define PAGE_WRITE 0x2 // Можно писать (Read/Write) +#define PAGE_USER 0x4 // Доступно из Ring 3 (User mode) + +// Инициализация страничного обмена +void vmm_init(void); + +// Отобразить виртуальный адрес на физический +void vmm_map_page(uint32_t phys, uint32_t virt, uint32_t flags); + +#endif diff --git a/src/vmm_asm.asm b/src/vmm_asm.asm new file mode 100644 index 0000000..5fe924d --- /dev/null +++ b/src/vmm_asm.asm @@ -0,0 +1,13 @@ +global vmm_enable_paging +vmm_enable_paging: + mov eax, [esp+4] ; Получили аргумент (phys_directory) + mov cr3, eax ; Загрузили CR3 + + mov eax, cr0 + or eax, 0x80000000 ; Включили пейджинг + mov cr0, eax + + ; ВОТ ЭТО ОБЯЗАТЕЛЬНО: + jmp 0x08:.flush ; Far jump сбрасывает пайплайн CPU +.flush: + ret