MULTIPLE: 3 изменения

PIC: добавил ремаппер
IRQ: добавил таймер
KERNEL: работа со строками и памятью
This commit is contained in:
msh356 2026-07-01 19:55:26 +03:00
parent 884b58f304
commit d64856e1b1
11 changed files with 371 additions and 11 deletions

View file

@ -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)

View file

@ -10,7 +10,7 @@
- [x] Драйвер VGA
- [x] Прерывания (GDT/IDT)
- [x] UART отладка
- [ ] Драйвер таймера
- [x] Драйвер таймера
- [ ] Драйвер клавиатуры
- [ ] Нормальный драйвер VGA (скролл итд)
- [ ] Нормальный драйвер клавиатуры (Caps Lock, Shift, Numpad итд)

31
drivers/timer.c Normal file
View file

@ -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");
}
}

12
drivers/timer.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef TIMER_H
#define TIMER_H
#include <stdint.h>
// Инициализация таймера нужной частотой (например, 100 Гц)
void timer_init(uint32_t frequency);
// Эта функция будет вызываться из нашего ассемблерного обработчика irq0
void timer_handler(void);
#endif

View file

@ -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));
// Сюда в будущем добавим вызовы конкретных драйверов таймера и клавы
}

View file

@ -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);
}

View file

@ -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");
}

76
kernel/pic.c Normal file
View file

@ -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);
}

13
kernel/pic.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef PIC_H
#define PIC_H
#include <stdint.h>
// Перемапливание базовых векторов прерываний для 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

183
kernel/string.c Normal file
View file

@ -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;
}

28
kernel/string.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef STRING_H
#define STRING_H
#include <stddef.h>
#include <stdint.h>
// Работа с памятью
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