forked from Bananymous/banan-os
Kernel: Rewrite the whole VESA driver
We dont support vga text mode currently. I might add it later if needed. I also removed mouse 'support' from Shell since it didn't do anything and I didn't implement arbitary bitmap rendering to framebuffer
This commit is contained in:
parent
10c7ef7baa
commit
1b9f7aa815
|
@ -44,6 +44,7 @@ kernel/Serial.o \
|
||||||
kernel/Shell.o \
|
kernel/Shell.o \
|
||||||
kernel/SSP.o \
|
kernel/SSP.o \
|
||||||
kernel/TTY.o \
|
kernel/TTY.o \
|
||||||
|
kernel/VesaTerminalDriver.o \
|
||||||
icxxabi.o \
|
icxxabi.o \
|
||||||
|
|
||||||
OBJS= \
|
OBJS= \
|
||||||
|
|
|
@ -1,317 +0,0 @@
|
||||||
#include <kernel/font.h>
|
|
||||||
#include <kernel/IO.h>
|
|
||||||
#include <kernel/kmalloc.h>
|
|
||||||
#include <kernel/MMU.h>
|
|
||||||
#include <kernel/multiboot.h>
|
|
||||||
#include <kernel/Panic.h>
|
|
||||||
#include <kernel/Serial.h>
|
|
||||||
#include <kernel/VESA.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define MULTIBOOT_FLAGS_FRAMEBUFFER (1 << 12)
|
|
||||||
#define MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS 1
|
|
||||||
#define MULTIBOOT_FRAMEBUFFER_TYPE_TEXT 2
|
|
||||||
|
|
||||||
|
|
||||||
extern multiboot_info_t* s_multiboot_info;
|
|
||||||
extern const struct bitmap_font font;
|
|
||||||
|
|
||||||
namespace VESA
|
|
||||||
{
|
|
||||||
|
|
||||||
static uintptr_t s_addr = 0;
|
|
||||||
static uint8_t s_bpp = 0;
|
|
||||||
static uint32_t s_pitch = 0;
|
|
||||||
static uint32_t s_width = 0;
|
|
||||||
static uint32_t s_height = 0;
|
|
||||||
static uint8_t s_mode = 0;
|
|
||||||
|
|
||||||
static uint32_t s_terminal_width = 0;
|
|
||||||
static uint32_t s_terminal_height = 0;
|
|
||||||
|
|
||||||
static void (*PutCharAtImpl)(uint16_t, uint32_t, uint32_t, Color, Color) = nullptr;
|
|
||||||
static void (*ClearImpl)(Color) = nullptr;
|
|
||||||
static void (*SetCursorPositionImpl)(uint32_t, uint32_t, Color) = nullptr;
|
|
||||||
|
|
||||||
static void GraphicsPutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg);
|
|
||||||
static void GraphicsPutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg, Color bg);
|
|
||||||
static void GraphicsPutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg);
|
|
||||||
static void GraphicsClear(Color color);
|
|
||||||
static void GraphicsSetCursorPosition(uint32_t x, uint32_t y, Color fg);
|
|
||||||
|
|
||||||
static void TextPutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg);
|
|
||||||
static void TextClear(Color color);
|
|
||||||
static void TextSetCursorPosition(uint32_t x, uint32_t y, Color fg);
|
|
||||||
|
|
||||||
void PutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg)
|
|
||||||
{
|
|
||||||
if (s_mode == MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS)
|
|
||||||
GraphicsPutBitmapAt(bitmap, x, y, fg);
|
|
||||||
}
|
|
||||||
void PutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg, Color bg)
|
|
||||||
{
|
|
||||||
if (s_mode == MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS)
|
|
||||||
GraphicsPutBitmapAt(bitmap, x, y, fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
|
|
||||||
{
|
|
||||||
if (x >= s_width || y >= s_height)
|
|
||||||
return;
|
|
||||||
PutCharAtImpl(ch, x, y, fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clear(Color color)
|
|
||||||
{
|
|
||||||
ClearImpl(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetCursorPosition(uint32_t x, uint32_t y, Color fg)
|
|
||||||
{
|
|
||||||
SetCursorPositionImpl(x, y, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GetTerminalWidth()
|
|
||||||
{
|
|
||||||
return s_terminal_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GetTerminalHeight()
|
|
||||||
{
|
|
||||||
return s_terminal_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Initialize()
|
|
||||||
{
|
|
||||||
if (!(g_multiboot_info->flags & MULTIBOOT_FLAGS_FRAMEBUFFER))
|
|
||||||
{
|
|
||||||
derrorln("bootloader did not provide a memory map");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& framebuffer = g_multiboot_info->framebuffer;
|
|
||||||
s_addr = framebuffer.addr;
|
|
||||||
s_bpp = framebuffer.bpp;
|
|
||||||
s_pitch = framebuffer.pitch;
|
|
||||||
s_width = framebuffer.width;
|
|
||||||
s_height = framebuffer.height;
|
|
||||||
s_mode = framebuffer.type;
|
|
||||||
|
|
||||||
MMU::Get().AllocateRange(s_addr, s_pitch * s_height);
|
|
||||||
|
|
||||||
if (s_mode == MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS)
|
|
||||||
{
|
|
||||||
if (s_bpp != 24 && s_bpp != 32)
|
|
||||||
{
|
|
||||||
derrorln("Unsupported bpp {}", s_bpp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
dprintln("Graphics Mode {}x{} ({} bpp)", s_width, s_height, s_bpp);
|
|
||||||
PutCharAtImpl = GraphicsPutCharAt;
|
|
||||||
ClearImpl = GraphicsClear;
|
|
||||||
SetCursorPositionImpl = GraphicsSetCursorPosition;
|
|
||||||
s_terminal_width = s_width / font.Width;
|
|
||||||
s_terminal_height = s_height / font.Height;
|
|
||||||
}
|
|
||||||
else if (s_mode == MULTIBOOT_FRAMEBUFFER_TYPE_TEXT)
|
|
||||||
{
|
|
||||||
dprintln("Text Mode {}x{}", s_width, s_height);
|
|
||||||
PutCharAtImpl = TextPutCharAt;
|
|
||||||
ClearImpl = TextClear;
|
|
||||||
SetCursorPositionImpl = TextSetCursorPosition;
|
|
||||||
s_terminal_width = s_width;
|
|
||||||
s_terminal_height = s_height;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
derrorln("Unsupported type for VESA framebuffer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetCursorPositionImpl(0, 0, Color::BRIGHT_WHITE);
|
|
||||||
ClearImpl(Color::BLACK);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t s_graphics_colors[]
|
|
||||||
{
|
|
||||||
0x00'00'00'00,
|
|
||||||
0x00'00'00'AA,
|
|
||||||
0x00'00'AA'00,
|
|
||||||
0x00'00'AA'AA,
|
|
||||||
0x00'AA'00'00,
|
|
||||||
0x00'AA'00'AA,
|
|
||||||
0x00'AA'55'00,
|
|
||||||
0x00'AA'AA'AA,
|
|
||||||
0x00'55'55'55,
|
|
||||||
0x00'55'55'FF,
|
|
||||||
0x00'55'FF'55,
|
|
||||||
0x00'55'FF'FF,
|
|
||||||
0x00'FF'55'55,
|
|
||||||
0x00'FF'55'FF,
|
|
||||||
0x00'FF'FF'55,
|
|
||||||
0x00'FF'FF'FF,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void GraphicsSetPixel(uint32_t offset, uint32_t color)
|
|
||||||
{
|
|
||||||
uint32_t* address = (uint32_t*)(s_addr + offset);
|
|
||||||
switch (s_bpp)
|
|
||||||
{
|
|
||||||
case 24:
|
|
||||||
*address = (*address & 0xFF000000) | (color & 0x00FFFFFF);
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
*address = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GraphicsPutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg)
|
|
||||||
{
|
|
||||||
uint32_t u32_fg = s_graphics_colors[(uint8_t)fg];
|
|
||||||
|
|
||||||
uint32_t fx = x * font.Width;
|
|
||||||
uint32_t fy = y * font.Height;
|
|
||||||
|
|
||||||
uint32_t row_offset = (fy * s_pitch) + (fx * (s_bpp / 8));
|
|
||||||
for (uint32_t gy = 0; gy < font.Height; gy++)
|
|
||||||
{
|
|
||||||
uint32_t pixel_offset = row_offset;
|
|
||||||
for (uint32_t gx = 0; gx < font.Width; gx++)
|
|
||||||
{
|
|
||||||
if (bitmap[gy] & (1 << (font.Width - gx - 1)))
|
|
||||||
GraphicsSetPixel(pixel_offset, u32_fg);
|
|
||||||
pixel_offset += s_bpp / 8;
|
|
||||||
}
|
|
||||||
row_offset += s_pitch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GraphicsPutBitmapAt(const uint8_t* bitmap, uint32_t x, uint32_t y, Color fg, Color bg)
|
|
||||||
{
|
|
||||||
uint32_t u32_fg = s_graphics_colors[(uint8_t)fg];
|
|
||||||
uint32_t u32_bg = s_graphics_colors[(uint8_t)bg];
|
|
||||||
|
|
||||||
uint32_t fx = x * font.Width;
|
|
||||||
uint32_t fy = y * font.Height;
|
|
||||||
|
|
||||||
uint32_t row_offset = (fy * s_pitch) + (fx * (s_bpp / 8));
|
|
||||||
for (uint32_t gy = 0; gy < font.Height; gy++)
|
|
||||||
{
|
|
||||||
uint32_t pixel_offset = row_offset;
|
|
||||||
for (uint32_t gx = 0; gx < font.Width; gx++)
|
|
||||||
{
|
|
||||||
GraphicsSetPixel(pixel_offset, (bitmap[gy] & (1 << (font.Width - gx - 1))) ? u32_fg : u32_bg);
|
|
||||||
pixel_offset += s_bpp / 8;
|
|
||||||
}
|
|
||||||
row_offset += s_pitch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GraphicsPutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
|
|
||||||
{
|
|
||||||
// find correct bitmap
|
|
||||||
uint32_t index = 0;
|
|
||||||
for (uint32_t i = 0; i < font.Chars; i++)
|
|
||||||
{
|
|
||||||
if (font.Index[i] == ch)
|
|
||||||
{
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* glyph = font.Bitmap + index * font.Height;
|
|
||||||
|
|
||||||
GraphicsPutBitmapAt(glyph, x, y, fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GraphicsClear(Color color)
|
|
||||||
{
|
|
||||||
uint32_t u32_color = s_graphics_colors[(uint8_t)color];
|
|
||||||
|
|
||||||
if (s_bpp == 32)
|
|
||||||
{
|
|
||||||
uint32_t bytes_per_row = s_pitch / 4;
|
|
||||||
for (uint32_t y = 0; y < s_height; y++)
|
|
||||||
for (uint32_t x = 0; x < s_width; x++)
|
|
||||||
((uint32_t*)s_addr)[y * bytes_per_row + x] = u32_color;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t row_offset = 0;
|
|
||||||
for (uint32_t y = 0; y < s_height; y++)
|
|
||||||
{
|
|
||||||
uint32_t pixel_offset = row_offset;
|
|
||||||
for (uint32_t x = 0; x < s_width; x++)
|
|
||||||
{
|
|
||||||
GraphicsSetPixel(pixel_offset, u32_color);
|
|
||||||
pixel_offset += s_bpp / 8;
|
|
||||||
}
|
|
||||||
row_offset += s_pitch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GraphicsSetCursorPosition(uint32_t x, uint32_t y, Color fg)
|
|
||||||
{
|
|
||||||
if (font.Width == 8 && font.Height == 16)
|
|
||||||
{
|
|
||||||
uint8_t cursor[] = {
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
XXXXXXXX,
|
|
||||||
XXXXXXXX,
|
|
||||||
________,
|
|
||||||
};
|
|
||||||
|
|
||||||
GraphicsPutBitmapAt(cursor, x, y, fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint8_t TextColor(Color fg, Color bg)
|
|
||||||
{
|
|
||||||
return (((uint8_t)bg & 0x0F) << 4) | ((uint8_t)fg & 0x0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint16_t TextEntry(uint8_t ch, uint8_t color)
|
|
||||||
{
|
|
||||||
return ((uint16_t)color << 8) | ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void TextPutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
|
|
||||||
{
|
|
||||||
uint32_t index = y * s_width + x;
|
|
||||||
((uint16_t*)s_addr)[index] = TextEntry(ch, TextColor(fg, bg));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void TextClear(Color color)
|
|
||||||
{
|
|
||||||
for (uint32_t y = 0; y < s_height; y++)
|
|
||||||
for (uint32_t x = 0; x < s_width; x++)
|
|
||||||
TextPutCharAt(' ', x, y, Color::BRIGHT_WHITE, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void TextSetCursorPosition(uint32_t x, uint32_t y, Color)
|
|
||||||
{
|
|
||||||
uint16_t position = y * s_width + x;
|
|
||||||
IO::outb(0x3D4, 0x0F);
|
|
||||||
IO::outb(0x3D5, (uint8_t) (position & 0xFF));
|
|
||||||
IO::outb(0x3D4, 0x0E);
|
|
||||||
IO::outb(0x3D5, (uint8_t) ((position >> 8) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -9,5 +9,4 @@ $(ARCHDIR)/boot.o \
|
||||||
$(ARCHDIR)/CPUID.o \
|
$(ARCHDIR)/CPUID.o \
|
||||||
$(ARCHDIR)/IDT.o \
|
$(ARCHDIR)/IDT.o \
|
||||||
$(ARCHDIR)/MMU.o \
|
$(ARCHDIR)/MMU.o \
|
||||||
$(ARCHDIR)/VESA.o \
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ namespace Kernel
|
||||||
BAN::Vector<BAN::String> ParseArguments(BAN::StringView) const;
|
BAN::Vector<BAN::String> ParseArguments(BAN::StringView) const;
|
||||||
void ProcessCommand(const BAN::Vector<BAN::String>&);
|
void ProcessCommand(const BAN::Vector<BAN::String>&);
|
||||||
void KeyEventCallback(Input::KeyEvent);
|
void KeyEventCallback(Input::KeyEvent);
|
||||||
void MouseMoveEventCallback(Input::MouseMoveEvent);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TTY* m_tty;
|
TTY* m_tty;
|
||||||
|
@ -36,13 +35,6 @@ namespace Kernel
|
||||||
uint32_t col = 0;
|
uint32_t col = 0;
|
||||||
uint32_t index = 0;
|
uint32_t index = 0;
|
||||||
} m_cursor_pos;
|
} m_cursor_pos;
|
||||||
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
bool exists = false;
|
|
||||||
int32_t x = 0;
|
|
||||||
int32_t y = 0;
|
|
||||||
} m_mouse_pos;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <kernel/VESA.h>
|
#include <kernel/TerminalDriver.h>
|
||||||
#include <kernel/Serial.h>
|
#include <kernel/Serial.h>
|
||||||
|
|
||||||
class TTY
|
class TTY
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TTY();
|
TTY(TerminalDriver*);
|
||||||
void Clear();
|
void Clear();
|
||||||
void PutChar(char ch);
|
void PutChar(char ch);
|
||||||
void Write(const char* data, size_t size);
|
void Write(const char* data, size_t size);
|
||||||
|
@ -31,8 +31,8 @@ private:
|
||||||
private:
|
private:
|
||||||
struct Cell
|
struct Cell
|
||||||
{
|
{
|
||||||
VESA::Color foreground = VESA::Color::BRIGHT_WHITE;
|
TerminalDriver::Color foreground = TerminalColor::BRIGHT_WHITE;
|
||||||
VESA::Color background = VESA::Color::BLACK;
|
TerminalDriver::Color background = TerminalColor::BLACK;
|
||||||
uint16_t character = ' ';
|
uint16_t character = ' ';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,8 +47,9 @@ private:
|
||||||
uint32_t m_height { 0 };
|
uint32_t m_height { 0 };
|
||||||
uint32_t m_row { 0 };
|
uint32_t m_row { 0 };
|
||||||
uint32_t m_column { 0 };
|
uint32_t m_column { 0 };
|
||||||
VESA::Color m_foreground { VESA::Color::BRIGHT_WHITE };
|
TerminalDriver::Color m_foreground { TerminalColor::BRIGHT_WHITE };
|
||||||
VESA::Color m_background { VESA::Color::BLACK };
|
TerminalDriver::Color m_background { TerminalColor::BLACK };
|
||||||
Cell* m_buffer { nullptr };
|
Cell* m_buffer { nullptr };
|
||||||
AnsiState m_ansi_state;
|
AnsiState m_ansi_state;
|
||||||
|
TerminalDriver* m_terminal_driver { nullptr };
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
class TerminalDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Color
|
||||||
|
{
|
||||||
|
constexpr Color(uint32_t rgb)
|
||||||
|
: rgb(rgb)
|
||||||
|
{ }
|
||||||
|
constexpr Color(uint8_t r, uint8_t g, uint8_t b)
|
||||||
|
: rgb(((uint32_t)r << 16) | ((uint32_t)g << 8) | b)
|
||||||
|
{ }
|
||||||
|
uint8_t red() const { return (rgb >> 0x10) & 0xFF; }
|
||||||
|
uint8_t green() const { return (rgb >> 0x08) & 0xFF; }
|
||||||
|
uint8_t blue() const { return (rgb >> 0x00) & 0xFF; }
|
||||||
|
uint32_t rgb;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~TerminalDriver() {}
|
||||||
|
|
||||||
|
virtual uint32_t Width() const = 0;
|
||||||
|
virtual uint32_t Height() const = 0;
|
||||||
|
|
||||||
|
virtual void PutCharAt(uint16_t, uint32_t, uint32_t, Color, Color) = 0;
|
||||||
|
virtual void Clear(Color) = 0;
|
||||||
|
|
||||||
|
virtual void SetCursorPosition(uint32_t, uint32_t) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace TerminalColor
|
||||||
|
{
|
||||||
|
static constexpr TerminalDriver::Color BLACK = 0x000000;
|
||||||
|
static constexpr TerminalDriver::Color BLUE = 0x0000AA;
|
||||||
|
static constexpr TerminalDriver::Color GREEN = 0x00AA00;
|
||||||
|
static constexpr TerminalDriver::Color CYAN = 0x00AAAA;
|
||||||
|
static constexpr TerminalDriver::Color RED = 0xAA0000;
|
||||||
|
static constexpr TerminalDriver::Color MAGENTA = 0xAA00AA;
|
||||||
|
static constexpr TerminalDriver::Color YELLOW = 0xAA5500;
|
||||||
|
static constexpr TerminalDriver::Color WHITE = 0xAAAAAA;
|
||||||
|
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_BLACK = 0x555555;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_BLUE = 0x5555FF;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_GREEN = 0x55FF55;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_CYAN = 0x55FFFF;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_RED = 0xFF5555;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_MAGENTA = 0xFF55FF;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_YELLOW = 0xFFFF55;
|
||||||
|
static constexpr TerminalDriver::Color BRIGHT_WHITE = 0xFFFFFF;
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace VESA
|
|
||||||
{
|
|
||||||
|
|
||||||
enum class Color : uint8_t
|
|
||||||
{
|
|
||||||
BLACK = 0,
|
|
||||||
BLUE = 1,
|
|
||||||
GREEN = 2,
|
|
||||||
CYAN = 3,
|
|
||||||
RED = 4,
|
|
||||||
MAGENTA = 5,
|
|
||||||
YELLOW = 6,
|
|
||||||
WHITE = 7,
|
|
||||||
BRIGHT_BLACK = 8,
|
|
||||||
BRIGHT_BLUE = 9,
|
|
||||||
BRIGHT_GREEN = 10,
|
|
||||||
BRIGHT_CYAN = 11,
|
|
||||||
BRIGHT_RED = 12,
|
|
||||||
BRIGHT_MAGENTA = 13,
|
|
||||||
BRIGHT_YELLOW = 14,
|
|
||||||
BRIGHT_WHITE = 15,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool Initialize();
|
|
||||||
void PutBitmapAt(const uint8_t*, uint32_t, uint32_t, Color);
|
|
||||||
void PutBitmapAt(const uint8_t*, uint32_t, uint32_t, Color, Color);
|
|
||||||
void PutCharAt(uint16_t, uint32_t, uint32_t, Color, Color);
|
|
||||||
void Clear(Color);
|
|
||||||
void SetCursorPosition(uint32_t, uint32_t, Color);
|
|
||||||
|
|
||||||
uint32_t GetTerminalWidth();
|
|
||||||
uint32_t GetTerminalHeight();
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <kernel/font.h>
|
||||||
|
#include <kernel/TerminalDriver.h>
|
||||||
|
|
||||||
|
class VesaTerminalDriver final : public TerminalDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static VesaTerminalDriver* Create();
|
||||||
|
~VesaTerminalDriver();
|
||||||
|
|
||||||
|
virtual uint32_t Width() const override { return m_width / m_font.Width; }
|
||||||
|
virtual uint32_t Height() const override { return m_height / m_font.Height; }
|
||||||
|
|
||||||
|
virtual void PutCharAt(uint16_t, uint32_t, uint32_t, Color, Color) override;
|
||||||
|
virtual void Clear(Color) override;
|
||||||
|
|
||||||
|
virtual void SetCursorPosition(uint32_t, uint32_t) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VesaTerminalDriver(uint32_t width, uint32_t height, uint32_t pitch, uint8_t bpp, uintptr_t address, bitmap_font font)
|
||||||
|
: m_width(width)
|
||||||
|
, m_height(height)
|
||||||
|
, m_pitch(pitch)
|
||||||
|
, m_bpp(bpp)
|
||||||
|
, m_address(address)
|
||||||
|
, m_font(font)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void SetPixel(uint32_t, Color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t m_width = 0;
|
||||||
|
uint32_t m_height = 0;
|
||||||
|
uint32_t m_pitch = 0;
|
||||||
|
uint8_t m_bpp = 0;
|
||||||
|
uintptr_t m_address = 0;
|
||||||
|
bitmap_font m_font;
|
||||||
|
|
||||||
|
static constexpr Color s_cursor_color = TerminalColor::BRIGHT_WHITE;
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
// (c) 2009, 2010 Lutz Sammer, License: AGPLv3
|
// (c) 2009, 2010 Lutz Sammer, License: AGPLv3
|
||||||
|
|
||||||
/// bitmap font structure
|
/// bitmap font structure
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MULTIBOOT_FLAGS_FRAMEBUFFER (1 << 12)
|
||||||
|
|
||||||
|
#define MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS 1
|
||||||
|
#define MULTIBOOT_FRAMEBUFFER_TYPE_TEXT 2
|
||||||
|
|
||||||
struct framebuffer_info_t
|
struct framebuffer_info_t
|
||||||
{
|
{
|
||||||
uint64_t addr;
|
uint64_t addr;
|
||||||
|
|
|
@ -22,30 +22,10 @@ namespace Kernel
|
||||||
|
|
||||||
static auto s_default_prompt = "\\[\e[32m\\]user\\[\e[m\\]# "_sv;
|
static auto s_default_prompt = "\\[\e[32m\\]user\\[\e[m\\]# "_sv;
|
||||||
|
|
||||||
static uint8_t s_pointer[] {
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
________,
|
|
||||||
X_______,
|
|
||||||
XX______,
|
|
||||||
XXX_____,
|
|
||||||
XXXX____,
|
|
||||||
XXXXX___,
|
|
||||||
XXXXXX__,
|
|
||||||
XXXXXXX_,
|
|
||||||
XXXXXXXX,
|
|
||||||
XXX_____,
|
|
||||||
XX______,
|
|
||||||
X_______,
|
|
||||||
};
|
|
||||||
|
|
||||||
Shell::Shell(TTY* tty)
|
Shell::Shell(TTY* tty)
|
||||||
: m_tty(tty)
|
: m_tty(tty)
|
||||||
{
|
{
|
||||||
Input::register_key_event_callback({ &Shell::KeyEventCallback, this });
|
Input::register_key_event_callback({ &Shell::KeyEventCallback, this });
|
||||||
Input::register_mouse_move_event_callback({ &Shell::MouseMoveEventCallback, this });
|
|
||||||
SetPrompt(s_default_prompt);
|
SetPrompt(s_default_prompt);
|
||||||
MUST(m_buffer.PushBack(""_sv));
|
MUST(m_buffer.PushBack(""_sv));
|
||||||
}
|
}
|
||||||
|
@ -415,18 +395,6 @@ argument_done:
|
||||||
}
|
}
|
||||||
|
|
||||||
TTY_PRINT("\e[{}G", m_prompt_length + m_cursor_pos.col + 1);
|
TTY_PRINT("\e[{}G", m_prompt_length + m_cursor_pos.col + 1);
|
||||||
|
|
||||||
if (m_mouse_pos.exists)
|
|
||||||
VESA::PutBitmapAt(s_pointer, m_mouse_pos.x, m_mouse_pos.y, VESA::Color::BRIGHT_WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shell::MouseMoveEventCallback(Input::MouseMoveEvent event)
|
|
||||||
{
|
|
||||||
m_mouse_pos.exists = true;
|
|
||||||
m_tty->RenderFromBuffer(m_mouse_pos.x, m_mouse_pos.y);
|
|
||||||
m_mouse_pos.x = Math::clamp<int32_t>(m_mouse_pos.x + event.dx, 0, m_tty->Width() - 1);
|
|
||||||
m_mouse_pos.y = Math::clamp<int32_t>(m_mouse_pos.y - event.dy, 0, m_tty->Height() - 1);
|
|
||||||
VESA::PutBitmapAt(s_pointer, m_mouse_pos.x, m_mouse_pos.y, VESA::Color::BRIGHT_WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
#include <BAN/Errors.h>
|
#include <BAN/Errors.h>
|
||||||
#include <kernel/Serial.h>
|
#include <kernel/Serial.h>
|
||||||
#include <kernel/TTY.h>
|
#include <kernel/TTY.h>
|
||||||
#include <kernel/VESA.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
@ -21,10 +20,11 @@ template<typename T> inline constexpr T clamp(T x, T a, T b) { return x < a ? a
|
||||||
|
|
||||||
static TTY* s_tty = nullptr;
|
static TTY* s_tty = nullptr;
|
||||||
|
|
||||||
TTY::TTY()
|
TTY::TTY(TerminalDriver* driver)
|
||||||
|
: m_terminal_driver(driver)
|
||||||
{
|
{
|
||||||
m_width = VESA::GetTerminalWidth();
|
m_width = m_terminal_driver->Width();
|
||||||
m_height = VESA::GetTerminalHeight();
|
m_height = m_terminal_driver->Height();
|
||||||
|
|
||||||
m_buffer = new Cell[m_width * m_height];
|
m_buffer = new Cell[m_width * m_height];
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ void TTY::Clear()
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < m_width * m_height; i++)
|
for (uint32_t i = 0; i < m_width * m_height; i++)
|
||||||
m_buffer[i] = { .foreground = m_foreground, .background = m_background, .character = ' ' };
|
m_buffer[i] = { .foreground = m_foreground, .background = m_background, .character = ' ' };
|
||||||
VESA::Clear(m_background);
|
m_terminal_driver->Clear(m_background);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TTY::SetCursorPosition(uint32_t x, uint32_t y)
|
void TTY::SetCursorPosition(uint32_t x, uint32_t y)
|
||||||
|
@ -45,7 +45,7 @@ void TTY::SetCursorPosition(uint32_t x, uint32_t y)
|
||||||
static uint32_t last_y = -1;
|
static uint32_t last_y = -1;
|
||||||
if (last_x != uint32_t(-1) && last_y != uint32_t(-1))
|
if (last_x != uint32_t(-1) && last_y != uint32_t(-1))
|
||||||
RenderFromBuffer(last_x, last_y); // Hacky way to clear previous cursor in graphics mode :D
|
RenderFromBuffer(last_x, last_y); // Hacky way to clear previous cursor in graphics mode :D
|
||||||
VESA::SetCursorPosition(x, y, VESA::Color::BRIGHT_WHITE);
|
m_terminal_driver->SetCursorPosition(x, y);
|
||||||
last_x = m_column = x;
|
last_x = m_column = x;
|
||||||
last_y = m_row = y;
|
last_y = m_row = y;
|
||||||
}
|
}
|
||||||
|
@ -111,27 +111,27 @@ void TTY::HandleAnsiSGR()
|
||||||
{
|
{
|
||||||
case -1:
|
case -1:
|
||||||
case 0:
|
case 0:
|
||||||
m_foreground = VESA::Color::BRIGHT_WHITE;
|
m_foreground = TerminalColor::BRIGHT_WHITE;
|
||||||
m_background = VESA::Color::BLACK;
|
m_background = TerminalColor::BLACK;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 30: m_foreground = VESA::Color::BRIGHT_BLACK; break;
|
case 30: m_foreground = TerminalColor::BRIGHT_BLACK; break;
|
||||||
case 31: m_foreground = VESA::Color::BRIGHT_RED; break;
|
case 31: m_foreground = TerminalColor::BRIGHT_RED; break;
|
||||||
case 32: m_foreground = VESA::Color::BRIGHT_GREEN; break;
|
case 32: m_foreground = TerminalColor::BRIGHT_GREEN; break;
|
||||||
case 33: m_foreground = VESA::Color::BRIGHT_YELLOW; break;
|
case 33: m_foreground = TerminalColor::BRIGHT_YELLOW; break;
|
||||||
case 34: m_foreground = VESA::Color::BRIGHT_BLUE; break;
|
case 34: m_foreground = TerminalColor::BRIGHT_BLUE; break;
|
||||||
case 35: m_foreground = VESA::Color::BRIGHT_MAGENTA; break;
|
case 35: m_foreground = TerminalColor::BRIGHT_MAGENTA; break;
|
||||||
case 36: m_foreground = VESA::Color::BRIGHT_CYAN; break;
|
case 36: m_foreground = TerminalColor::BRIGHT_CYAN; break;
|
||||||
case 37: m_foreground = VESA::Color::BRIGHT_WHITE; break;
|
case 37: m_foreground = TerminalColor::BRIGHT_WHITE; break;
|
||||||
|
|
||||||
case 40: m_background = VESA::Color::BRIGHT_BLACK; break;
|
case 40: m_background = TerminalColor::BRIGHT_BLACK; break;
|
||||||
case 41: m_background = VESA::Color::BRIGHT_RED; break;
|
case 41: m_background = TerminalColor::BRIGHT_RED; break;
|
||||||
case 42: m_background = VESA::Color::BRIGHT_GREEN; break;
|
case 42: m_background = TerminalColor::BRIGHT_GREEN; break;
|
||||||
case 43: m_background = VESA::Color::BRIGHT_YELLOW; break;
|
case 43: m_background = TerminalColor::BRIGHT_YELLOW; break;
|
||||||
case 44: m_background = VESA::Color::BRIGHT_BLUE; break;
|
case 44: m_background = TerminalColor::BRIGHT_BLUE; break;
|
||||||
case 45: m_background = VESA::Color::BRIGHT_MAGENTA; break;
|
case 45: m_background = TerminalColor::BRIGHT_MAGENTA; break;
|
||||||
case 46: m_background = VESA::Color::BRIGHT_CYAN; break;
|
case 46: m_background = TerminalColor::BRIGHT_CYAN; break;
|
||||||
case 47: m_background = VESA::Color::BRIGHT_WHITE; break;
|
case 47: m_background = TerminalColor::BRIGHT_WHITE; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ void TTY::RenderFromBuffer(uint32_t x, uint32_t y)
|
||||||
{
|
{
|
||||||
ASSERT(x < m_width && y < m_height);
|
ASSERT(x < m_width && y < m_height);
|
||||||
const auto& cell = m_buffer[y * m_width + x];
|
const auto& cell = m_buffer[y * m_width + x];
|
||||||
VESA::PutCharAt(cell.character, x, y, cell.foreground, cell.background);
|
m_terminal_driver->PutCharAt(cell.character, x, y, cell.foreground, cell.background);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TTY::PutCharAt(uint16_t ch, uint32_t x, uint32_t y)
|
void TTY::PutCharAt(uint16_t ch, uint32_t x, uint32_t y)
|
||||||
|
@ -256,7 +256,7 @@ void TTY::PutCharAt(uint16_t ch, uint32_t x, uint32_t y)
|
||||||
cell.character = ch;
|
cell.character = ch;
|
||||||
cell.foreground = m_foreground;
|
cell.foreground = m_foreground;
|
||||||
cell.background = m_background;
|
cell.background = m_background;
|
||||||
VESA::PutCharAt(ch, x, y, m_foreground, m_background);
|
m_terminal_driver->PutCharAt(ch, x, y, m_foreground, m_background);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TTY::PutChar(char ch)
|
void TTY::PutChar(char ch)
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
#include <BAN/Errors.h>
|
||||||
|
#include <kernel/MMU.h>
|
||||||
|
#include <kernel/multiboot.h>
|
||||||
|
#include <kernel/Serial.h>
|
||||||
|
#include <kernel/VesaTerminalDriver.h>
|
||||||
|
|
||||||
|
extern const struct bitmap_font font;
|
||||||
|
|
||||||
|
VesaTerminalDriver* VesaTerminalDriver::Create()
|
||||||
|
{
|
||||||
|
if (!(g_multiboot_info->flags & MULTIBOOT_FLAGS_FRAMEBUFFER))
|
||||||
|
{
|
||||||
|
dprintln("Bootloader did not provide framebuffer");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& framebuffer = g_multiboot_info->framebuffer;
|
||||||
|
|
||||||
|
if (framebuffer.type == MULTIBOOT_FRAMEBUFFER_TYPE_GRAPHICS)
|
||||||
|
{
|
||||||
|
if (framebuffer.bpp != 24 && framebuffer.bpp != 32)
|
||||||
|
{
|
||||||
|
dprintln("Unsupported bpp {}", framebuffer.bpp);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
dprintln("Graphics Mode {}x{} ({} bpp)", framebuffer.width, framebuffer.height, framebuffer.bpp);
|
||||||
|
}
|
||||||
|
else if (framebuffer.type == MULTIBOOT_FRAMEBUFFER_TYPE_TEXT)
|
||||||
|
{
|
||||||
|
dprintln("Text Mode is currently not supported");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dprintln("Unknown framebuffer type {}", framebuffer.type);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MMU::Get().AllocateRange(framebuffer.addr, framebuffer.pitch * framebuffer.height);
|
||||||
|
|
||||||
|
auto* driver = new VesaTerminalDriver(
|
||||||
|
framebuffer.width,
|
||||||
|
framebuffer.height,
|
||||||
|
framebuffer.pitch,
|
||||||
|
framebuffer.bpp,
|
||||||
|
framebuffer.addr,
|
||||||
|
font
|
||||||
|
);
|
||||||
|
driver->SetCursorPosition(0, 0);
|
||||||
|
driver->Clear(TerminalColor::BLACK);
|
||||||
|
return driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VesaTerminalDriver::~VesaTerminalDriver()
|
||||||
|
{
|
||||||
|
MMU::Get().UnAllocateRange(m_address, m_pitch * m_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VesaTerminalDriver::SetPixel(uint32_t offset, Color color)
|
||||||
|
{
|
||||||
|
uint32_t* pixel = (uint32_t*)(m_address + offset);
|
||||||
|
switch (m_bpp)
|
||||||
|
{
|
||||||
|
case 24:
|
||||||
|
*pixel = (*pixel & 0xFF000000) | (color.rgb & 0x00FFFFFF);
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
*pixel = color.rgb;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VesaTerminalDriver::PutCharAt(uint16_t ch, uint32_t x, uint32_t y, Color fg, Color bg)
|
||||||
|
{
|
||||||
|
uint32_t glyph_index = 0;
|
||||||
|
for (uint32_t i = 0; i < m_font.Chars; i++)
|
||||||
|
{
|
||||||
|
if (m_font.Index[i] == ch)
|
||||||
|
{
|
||||||
|
glyph_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* glyph = m_font.Bitmap + glyph_index * m_font.Height;
|
||||||
|
|
||||||
|
x *= m_font.Width;
|
||||||
|
y *= m_font.Height;
|
||||||
|
|
||||||
|
uint32_t row_offset = y * m_pitch + x * m_bpp / 8;
|
||||||
|
for (uint32_t dy = 0; dy < m_font.Height && y + dy < m_height; dy++)
|
||||||
|
{
|
||||||
|
uint32_t pixel_offset = row_offset;
|
||||||
|
for (uint32_t dx = 0; dx < m_font.Width && x + dx < m_width; dx++)
|
||||||
|
{
|
||||||
|
uint8_t bitmask = 1 << (font.Width - dx - 1);
|
||||||
|
SetPixel(pixel_offset, glyph[dy] & bitmask ? fg : bg);
|
||||||
|
pixel_offset += m_bpp / 8;
|
||||||
|
}
|
||||||
|
row_offset += m_pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VesaTerminalDriver::Clear(Color color)
|
||||||
|
{
|
||||||
|
if (m_bpp == 32)
|
||||||
|
{
|
||||||
|
uint32_t cells_per_row = m_pitch / 4;
|
||||||
|
for (uint32_t y = 0; y < m_height; y++)
|
||||||
|
for (uint32_t x = 0; x < m_width; x++)
|
||||||
|
((uint32_t*)m_address)[y * cells_per_row + x] = color.rgb;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t row_offset = 0;
|
||||||
|
for (uint32_t y = 0; y < m_height; y++)
|
||||||
|
{
|
||||||
|
uint32_t pixel_offset = row_offset;
|
||||||
|
for (uint32_t x = 0; x < m_width; x++)
|
||||||
|
{
|
||||||
|
SetPixel(pixel_offset, color);
|
||||||
|
pixel_offset += m_bpp / 8;
|
||||||
|
}
|
||||||
|
row_offset += m_pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VesaTerminalDriver::SetCursorPosition(uint32_t x, uint32_t y)
|
||||||
|
{
|
||||||
|
ASSERT(m_font.Height == 16 && m_font.Width == 8);
|
||||||
|
constexpr uint8_t cursor[] = {
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
________,
|
||||||
|
XXXXXXXX,
|
||||||
|
XXXXXXXX,
|
||||||
|
________,
|
||||||
|
};
|
||||||
|
|
||||||
|
x *= m_font.Width;
|
||||||
|
y *= m_font.Height;
|
||||||
|
|
||||||
|
uint32_t row_offset = y * m_pitch + x * m_bpp / 8;
|
||||||
|
for (uint32_t dy = 0; dy < m_font.Height && y + dy < m_height; dy++)
|
||||||
|
{
|
||||||
|
uint32_t pixel_offset = row_offset;
|
||||||
|
for (uint32_t dx = 0; dx < m_font.Width && x + dx < m_width; dx++)
|
||||||
|
{
|
||||||
|
uint8_t bitmask = 1 << (font.Width - dx - 1);
|
||||||
|
if (cursor[dy] & bitmask)
|
||||||
|
SetPixel(pixel_offset, s_cursor_color);
|
||||||
|
pixel_offset += m_bpp / 8;
|
||||||
|
}
|
||||||
|
row_offset += m_pitch;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
#include <kernel/Serial.h>
|
#include <kernel/Serial.h>
|
||||||
#include <kernel/Shell.h>
|
#include <kernel/Shell.h>
|
||||||
#include <kernel/TTY.h>
|
#include <kernel/TTY.h>
|
||||||
#include <kernel/VESA.h>
|
#include <kernel/VesaTerminalDriver.h>
|
||||||
|
|
||||||
#define DISABLE_INTERRUPTS() asm volatile("cli")
|
#define DISABLE_INTERRUPTS() asm volatile("cli")
|
||||||
#define ENABLE_INTERRUPTS() asm volatile("sti")
|
#define ENABLE_INTERRUPTS() asm volatile("sti")
|
||||||
|
@ -76,10 +76,10 @@ extern "C" void kernel_main()
|
||||||
MMU::Intialize();
|
MMU::Intialize();
|
||||||
dprintln("MMU initialized");
|
dprintln("MMU initialized");
|
||||||
|
|
||||||
if (!VESA::Initialize())
|
TerminalDriver* terminal_driver = VesaTerminalDriver::Create();
|
||||||
return;
|
ASSERT(terminal_driver);
|
||||||
dprintln("VESA initialized");
|
dprintln("VESA initialized");
|
||||||
TTY* tty1 = new TTY;
|
TTY* tty1 = new TTY(terminal_driver);
|
||||||
|
|
||||||
APIC::Initialize(cmdline.force_pic);
|
APIC::Initialize(cmdline.force_pic);
|
||||||
dprintln("APIC initialized");
|
dprintln("APIC initialized");
|
||||||
|
|
Loading…
Reference in New Issue