1
0
Fork 0

feat/kernel: Added built-in UART support

This commit is contained in:
Kevin Alavik 2025-05-14 13:33:38 +02:00
parent 9375cc74bd
commit 6cde9b4c19
Signed by: cmpsb
GPG key ID: 10D1CC0526FDC6D7
10 changed files with 295 additions and 91 deletions

6
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"files.associations": {
"stdnoreturn.h": "c",
"serial.h": "c"
}
}

View file

@ -2,7 +2,7 @@
MAKEFLAGS += -rR
.SUFFIXES:
QEMUFLAGS := -m 2G -debugcon stdio
QEMUFLAGS := -m 2G -serial stdio
IMAGE_NAME := release/emk
HOST_CC := cc

19
kernel/src/arch/cpu.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef CPU_H
#define CPU_H
#include <stdnoreturn.h>
static inline noreturn void hlt()
{
for (;;)
__asm__ volatile("hlt");
}
static inline noreturn void hcf()
{
__asm__ volatile("cli");
for (;;)
__asm__ volatile("hlt");
}
#endif // CPU_H

42
kernel/src/arch/io.c Normal file
View file

@ -0,0 +1,42 @@
#include <arch/io.h>
void outb(uint16_t port, uint8_t value)
{
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port) : "memory");
}
uint8_t inb(uint16_t port)
{
uint8_t result;
__asm__ volatile("inb %1, %0" : "=a"(result) : "Nd"(port));
return result;
}
void outw(uint16_t port, uint16_t value)
{
__asm__ volatile("outw %0, %1" : : "a"(value), "Nd"(port) : "memory");
}
uint16_t inw(uint16_t port)
{
uint16_t result;
__asm__ volatile("inw %1, %0" : "=a"(result) : "Nd"(port));
return result;
}
void outl(uint16_t port, uint32_t value)
{
__asm__ volatile("outl %0, %1" : : "a"(value), "Nd"(port) : "memory");
}
uint32_t inl(uint16_t port)
{
uint32_t result;
__asm__ volatile("inl %1, %0" : "=a"(result) : "Nd"(port));
return result;
}
void io_wait(void)
{
__asm__ volatile("outb %%al, $0x80" : : "a"(0) : "memory");
}

14
kernel/src/arch/io.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef IO_H
#define IO_H
#include <stdint.h>
void outb(uint16_t port, uint8_t value);
uint8_t inb(uint16_t port);
void outw(uint16_t port, uint16_t value);
uint16_t inw(uint16_t port);
void outl(uint16_t port, uint32_t value);
uint32_t inl(uint16_t port);
void io_wait(void);
#endif // IO_H

82
kernel/src/dev/serial.c Normal file
View file

@ -0,0 +1,82 @@
#include <dev/serial.h>
#include <arch/io.h>
int serial_init(uint16_t port)
{
uint16_t base = (uint16_t)port;
// Disable all interrupts
outb(base + UART_IER, 0x00);
io_wait();
// Enable DLAB to set baud rate
outb(base + UART_LCR, UART_LCR_DLAB);
io_wait();
outb(base + UART_DLL, UART_BAUD_38400 & 0xFF);
io_wait();
outb(base + UART_DLH, (UART_BAUD_38400 >> 8) & 0xFF);
io_wait();
// Disable DLAB and set 8N1 (8 bits, no parity, 1 stop bit)
outb(base + UART_LCR, UART_LCR_8N1);
io_wait();
// Enable FIFO, clear them, with 14-byte threshold
outb(base + UART_FCR, UART_FCR_ENABLE | UART_FCR_CLEAR_RX | UART_FCR_CLEAR_TX | UART_FCR_14BYTE);
io_wait();
// Set DTR, RTS, and OUT2
outb(base + UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
io_wait();
// Test serial chip (enable loopback mode)
outb(base + UART_MCR, UART_MCR_LOOP | UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
io_wait();
outb(base + UART_DATA, 0xAE); // Send test byte
io_wait();
if (inb(base + UART_DATA) != 0xAE)
{
return -1;
}
outb(base + UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
io_wait();
return 0;
}
int serial_write(uint16_t port, const uint8_t *data, uint32_t length)
{
uint16_t base = port;
uint32_t i;
for (i = 0; i < length; i++)
{
// Wait for transmitter holding register to be empty
while (!(inb(base + UART_LSR) & UART_LSR_THRE))
{
io_wait();
}
outb(base + UART_DATA, data[i]);
}
return i;
}
int serial_read(uint16_t port, uint8_t *buffer, uint32_t length)
{
uint16_t base = port;
uint32_t i = 0;
while (i < length)
{
if (inb(base + UART_LSR) & UART_LSR_DR)
{
buffer[i] = inb(base + UART_DATA);
i++;
}
else
{
break;
}
}
return i;
}

45
kernel/src/dev/serial.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef SERIAL_H
#define SERIAL_H
#include <stdint.h>
#define COM1 0x3f8
// UART register offsets
#define UART_DATA 0x00 // Data register (R/W)
#define UART_IER 0x01 // Interrupt Enable Register (R/W)
#define UART_DLL 0x00 // Divisor Latch Low (R/W, when DLAB=1)
#define UART_DLH 0x01 // Divisor Latch High (R/W, when DLAB=1)
#define UART_FCR 0x02 // FIFO Control Register (W)
#define UART_LCR 0x03 // Line Control Register (R/W)
#define UART_MCR 0x04 // Modem Control Register (R/W)
#define UART_LSR 0x05 // Line Status Register (R)
// Line Control Register bits
#define UART_LCR_DLAB 0x80 // Divisor Latch Access Bit
#define UART_LCR_8N1 0x03 // 8 bits, no parity, 1 stop bit
// FIFO Control Register bits
#define UART_FCR_ENABLE 0x01 // Enable FIFO
#define UART_FCR_CLEAR_RX 0x02 // Clear receive FIFO
#define UART_FCR_CLEAR_TX 0x04 // Clear transmit FIFO
#define UART_FCR_14BYTE 0xC0 // 14-byte trigger level
// Modem Control Register bits
#define UART_MCR_DTR 0x01 // Data Terminal Ready
#define UART_MCR_RTS 0x02 // Request to Send
#define UART_MCR_OUT2 0x08 // OUT2 (used for interrupts)
#define UART_MCR_LOOP 0x10 // Loopback mode
// Line Status Register bits
#define UART_LSR_DR 0x01 // Data Ready
#define UART_LSR_THRE 0x20 // Transmitter Holding Register Empty
// Baud rate divisor for 38400 baud (115200 / 3)
#define UART_BAUD_38400 3
int serial_init(uint16_t port);
int serial_write(uint16_t port, const uint8_t *data, uint32_t length);
int serial_read(uint16_t port, uint8_t *buffer, uint32_t length);
#endif // SERIAL_H

View file

@ -1,107 +1,26 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <boot/limine.h>
#include <arch/cpu.h>
#include <arch/io.h>
#include <dev/serial.h>
__attribute__((used, section(".limine_requests"))) static volatile LIMINE_BASE_REVISION(3);
__attribute__((used, section(".limine_requests"))) static volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0};
__attribute__((used, section(".limine_requests_start"))) static volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests_end"))) static volatile LIMINE_REQUESTS_END_MARKER;
void *memcpy(void *restrict dest, const void *restrict src, size_t n)
{
uint8_t *restrict pdest = (uint8_t *restrict)dest;
const uint8_t *restrict psrc = (const uint8_t *restrict)src;
for (size_t i = 0; i < n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void *memset(void *s, int c, size_t n)
{
uint8_t *p = (uint8_t *)s;
for (size_t i = 0; i < n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void *memmove(void *dest, const void *src, size_t n)
{
uint8_t *pdest = (uint8_t *)dest;
const uint8_t *psrc = (const uint8_t *)src;
if (src > dest)
{
for (size_t i = 0; i < n; i++)
{
pdest[i] = psrc[i];
}
}
else if (src < dest)
{
for (size_t i = n; i > 0; i--)
{
pdest[i - 1] = psrc[i - 1];
}
}
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n)
{
const uint8_t *p1 = (const uint8_t *)s1;
const uint8_t *p2 = (const uint8_t *)s2;
for (size_t i = 0; i < n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}
static void hcf(void)
{
asm("cli");
for (;;)
{
asm("hlt");
}
}
void emk_entry(void)
{
if (LIMINE_BASE_REVISION_SUPPORTED == false)
if (serial_init(COM1) != 0)
{
/* Just halt and say nothing */
hcf();
}
if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1)
if (!LIMINE_BASE_REVISION_SUPPORTED)
{
serial_write(COM1, (uint8_t *)"ERROR: Limine base revision is not supported\n", 45);
hcf();
}
struct limine_framebuffer *framebuffer = framebuffer_request.response->framebuffers[0];
for (size_t i = 0; i < 100; i++)
{
volatile uint32_t *fb_ptr = framebuffer->address;
fb_ptr[i * (framebuffer->pitch / 4) + i] = 0xffffff;
}
hcf();
serial_write(COM1, (uint8_t *)"Hello, World!\n", 14);
hlt();
}

65
kernel/src/lib/string.c Normal file
View file

@ -0,0 +1,65 @@
#include <lib/string.h>
void *memcpy(void *restrict dest, const void *restrict src, size_t n)
{
uint8_t *restrict pdest = (uint8_t *restrict)dest;
const uint8_t *restrict psrc = (const uint8_t *restrict)src;
for (size_t i = 0; i < n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void *memset(void *s, int c, size_t n)
{
uint8_t *p = (uint8_t *)s;
for (size_t i = 0; i < n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void *memmove(void *dest, const void *src, size_t n)
{
uint8_t *pdest = (uint8_t *)dest;
const uint8_t *psrc = (const uint8_t *)src;
if (src > dest)
{
for (size_t i = 0; i < n; i++)
{
pdest[i] = psrc[i];
}
}
else if (src < dest)
{
for (size_t i = n; i > 0; i--)
{
pdest[i - 1] = psrc[i - 1];
}
}
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n)
{
const uint8_t *p1 = (const uint8_t *)s1;
const uint8_t *p2 = (const uint8_t *)s2;
for (size_t i = 0; i < n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}

12
kernel/src/lib/string.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef STRING_H
#define STRING_H
#include <stdint.h>
#include <stddef.h>
void *memcpy(void *restrict dest, const void *restrict src, size_t n);
void *memset(void *s, int c, size_t n);
void *memmove(void *dest, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
#endif // STRING_H