diff --git a/Makefile b/Makefile index 6e0b811..4e3ef4e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -Iinclude +CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -Iinclude -fno-stack-protector AS = nasm ASFLAGS = -f elf32 @@ -19,8 +19,11 @@ OBJ = boot/boot.o \ kernel/interrupt.o \ kernel/logger.o \ kernel/io.o \ + kernel/pic.o \ + kernel/string.o \ drivers/serial.o \ - drivers/vga.o + drivers/vga.o \ + drivers/timer.o # Дефолтное правило (просто сборка бинарника) all: $(TARGET) diff --git a/README.md b/README.md index 1706726..205afeb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - [x] Драйвер VGA - [x] Прерывания (GDT/IDT) - [x] UART отладка -- [ ] Драйвер таймера +- [x] Драйвер таймера - [ ] Драйвер клавиатуры - [ ] Нормальный драйвер VGA (скролл итд) - [ ] Нормальный драйвер клавиатуры (Caps Lock, Shift, Numpad итд) diff --git a/drivers/timer.c b/drivers/timer.c new file mode 100644 index 0000000..85edf33 --- /dev/null +++ b/drivers/timer.c @@ -0,0 +1,31 @@ +#include "timer.h" +#include "../kernel/io.h" +#include "../kernel/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++; + log_info(LOG_DETAIL, "Timer: tick!"); + // Каждые 100 тиков (1 раз в секунду) шлем инфу в логгер + if (system_ticks % 100 == 0) { + log_info(LOG_DETAIL, "Timer: one second passed"); + } +} diff --git a/drivers/timer.h b/drivers/timer.h new file mode 100644 index 0000000..7300f53 --- /dev/null +++ b/drivers/timer.h @@ -0,0 +1,12 @@ +#ifndef TIMER_H +#define TIMER_H + +#include + +// Инициализация таймера нужной частотой (например, 100 Гц) +void timer_init(uint32_t frequency); + +// Эта функция будет вызываться из нашего ассемблерного обработчика irq0 +void timer_handler(void); + +#endif diff --git a/kernel/idt.c b/kernel/idt.c index 38cb3ac..2eb6aa7 100644 --- a/kernel/idt.c +++ b/kernel/idt.c @@ -1,4 +1,7 @@ #include "idt.h" +#include "string.h" +#include "../drivers/serial.h" +#include "../drivers/timer.h" // Сам массив IDT на 256 векторов и указатель на него struct idt_entry idt[256]; @@ -71,12 +74,19 @@ void isr_handler(struct registers regs) { // Сюда сходятся все прерывания от железа (32-47) void irq_handler(struct registers regs) { + if (regs.int_no == 32) { + timer_handler(); + } else { + char irq_str[4]; + itoa(regs.int_no, irq_str, 10); + kprint_serial("got unknown IRQ "); + kprint_serial(irq_str); + kprint_serial("\n"); + } // Если прерывание пришло от чипа 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/kmain.c b/kernel/kmain.c index 7d33482..fe6e0c3 100644 --- a/kernel/kmain.c +++ b/kernel/kmain.c @@ -1,7 +1,9 @@ #include "../drivers/vga.h" #include "../drivers/serial.h" +#include "../drivers/timer.h" #include "gdt.h" #include "idt.h" +#include "pic.h" #include "logger.h" void kernel_main(void) { @@ -15,5 +17,13 @@ void kernel_main(void) { 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, "Registering timer at 100Hz"); + timer_init(100); + log_info(LOG_NORMAL, "Enabling IRQ..."); + __asm__ __volatile__("sti"); while (1); } diff --git a/kernel/logger.c b/kernel/logger.c index 6b2164f..39cd131 100644 --- a/kernel/logger.c +++ b/kernel/logger.c @@ -9,17 +9,11 @@ void logger_init(void) { 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"); } diff --git a/kernel/pic.c b/kernel/pic.c new file mode 100644 index 0000000..6199f21 --- /dev/null +++ b/kernel/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/kernel/pic.h b/kernel/pic.h new file mode 100644 index 0000000..bd94bf9 --- /dev/null +++ b/kernel/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/kernel/string.c b/kernel/string.c new file mode 100644 index 0000000..3233f8f --- /dev/null +++ b/kernel/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/kernel/string.h b/kernel/string.h new file mode 100644 index 0000000..f3488d0 --- /dev/null +++ b/kernel/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