Kernel: Add very simple BGA driver

Only thing this is currently helpful is qemu when booting with -kernel
flag as that does not provide framebuffer info in multiboot headers
This commit is contained in:
2026-08-24 01:35:06 +03:00
parent 9a9a4fe8c0
commit cd6269d406
11 changed files with 442 additions and 37 deletions
+1
View File
@@ -41,6 +41,7 @@ set(KERNEL_SOURCES
kernel/FS/USTARModule.cpp
kernel/FS/VirtualFileSystem.cpp
kernel/GDT.cpp
kernel/Graphics/BGA.cpp
kernel/IDT.cpp
kernel/Input/InputDevice.cpp
kernel/Input/PS2/Controller.cpp
@@ -6,15 +6,22 @@
namespace Kernel
{
class BGAController;
class FramebufferDevice : public CharacterDevice
{
public:
static BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> create(paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch, uint8_t bpp);
static BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> create(BAN::RefPtr<BGAController>);
static BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> create_from_boot_framebuffer();
static BAN::RefPtr<FramebufferDevice> boot_framebuffer();
~FramebufferDevice();
uint32_t width() const { return m_width; }
uint32_t height() const { return m_height; }
uint8_t bpp() const { return m_bpp; }
BAN::ErrorOr<void> set_bga_controller(BAN::RefPtr<BGAController>);
uint32_t get_pixel(uint32_t x, uint32_t y) const;
void set_pixel(uint32_t x, uint32_t y, uint32_t rgb);
@@ -50,14 +57,16 @@ namespace Kernel
private:
const BAN::String m_name;
vaddr_t m_video_memory_vaddr { 0 };
const paddr_t m_video_memory_paddr;
const uint32_t m_width;
const uint32_t m_height;
const uint32_t m_pitch;
const uint8_t m_bpp;
vaddr_t m_video_memory_vaddr { 0 };
paddr_t m_video_memory_paddr { 0 };
uint32_t m_width { 0 };
uint32_t m_height { 0 };
uint32_t m_pitch { 0 };
uint8_t m_bpp { 0 };
BAN::UniqPtr<VirtualRange> m_video_buffer;
BAN::RefPtr<BGAController> m_bga_controller;
friend class FramebufferMemoryRegion;
};
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <BAN/RefPtr.h>
#include <kernel/Lock/SpinLock.h>
#include <kernel/PCI.h>
#include <sys/framebuffer.h>
namespace Kernel
{
class BGAController : public BAN::RefCounted<BGAController>
{
public:
static BAN::ErrorOr<BAN::RefPtr<BGAController>> create(PCI::Device&);
fb_fix_info get_fb_fix_info() const;
fb_var_info get_fb_var_info() const;
BAN::ErrorOr<void> set_fb_var_info(const fb_var_info&);
private:
BGAController(PCI::Device&);
BAN::ErrorOr<void> initialize();
void write_reg(uint16_t reg, uint16_t value);
uint16_t read_reg(uint16_t reg);
private:
mutable RecursiveSpinLock m_lock;
PCI::Device& m_pci_device;
BAN::UniqPtr<PCI::BarRegion> m_lfb_bar;
fb_fix_info m_fix_info {};
fb_var_info m_var_info {};
};
}
+2
View File
@@ -53,6 +53,8 @@ namespace Kernel
virtual bool master_has_closed() const { return false; }
virtual bool is_vtty() const { return false; }
protected:
TTY(termios termios, mode_t mode, uid_t uid, gid_t gid);
@@ -21,6 +21,7 @@ namespace Kernel
void clear() override;
bool is_vtty() const override { return true; }
BAN::ErrorOr<void> set_terminal_driver(BAN::RefPtr<TerminalDriver>);
protected:
+69 -11
View File
@@ -2,6 +2,7 @@
#include <kernel/Device/DeviceNumbers.h>
#include <kernel/Device/FramebufferDevice.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/Graphics/BGA.h>
#include <kernel/Memory/Heap.h>
#include <kernel/Terminal/FramebufferTerminal.h>
@@ -26,19 +27,14 @@ namespace Kernel
return s_boot_framebuffer;
}
BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> FramebufferDevice::create_from_boot_framebuffer()
BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> FramebufferDevice::create(paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch, uint8_t bpp)
{
ASSERT(g_boot_info.framebuffer.type == FramebufferInfo::Type::RGB);
if (g_boot_info.framebuffer.bpp != 24 && g_boot_info.framebuffer.bpp != 32)
if (bpp != 24 && bpp != 32)
return BAN::Error::from_errno(ENOTSUP);
auto* device_ptr = new FramebufferDevice(
0660, 0, 900,
makedev(DeviceNumber::Framebuffer, get_framebuffer_device_index()),
g_boot_info.framebuffer.address,
g_boot_info.framebuffer.width,
g_boot_info.framebuffer.height,
g_boot_info.framebuffer.pitch,
g_boot_info.framebuffer.bpp
paddr, width, height, pitch, bpp
);
if (device_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
@@ -49,6 +45,33 @@ namespace Kernel
return device;
}
BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> FramebufferDevice::create(BAN::RefPtr<BGAController> bga_controller)
{
const auto fix_info = bga_controller->get_fb_fix_info();
const auto var_info = bga_controller->get_fb_var_info();
auto controller = TRY(create(
fix_info.mem_start,
var_info.xres,
var_info.yres,
fix_info.pitch,
var_info.bpp
));
controller->m_bga_controller = bga_controller;
return controller;
}
BAN::ErrorOr<BAN::RefPtr<FramebufferDevice>> FramebufferDevice::create_from_boot_framebuffer()
{
s_boot_framebuffer = TRY(create(
g_boot_info.framebuffer.address,
g_boot_info.framebuffer.width,
g_boot_info.framebuffer.height,
g_boot_info.framebuffer.pitch,
g_boot_info.framebuffer.bpp
));
return s_boot_framebuffer;
}
FramebufferDevice::FramebufferDevice(mode_t mode, uid_t uid, gid_t gid, dev_t rdev, paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch, uint8_t bpp)
: CharacterDevice(mode, uid, gid)
, m_name(MUST(BAN::String::formatted("fb{}", minor(rdev))))
@@ -65,16 +88,20 @@ namespace Kernel
{
if (m_video_memory_vaddr == 0)
return;
size_t video_memory_pages = range_page_count(m_video_memory_paddr, m_height * m_pitch);
const size_t video_memory_bytes = m_bga_controller ? m_bga_controller->get_fb_fix_info().mem_size : m_height * m_pitch;
const size_t video_memory_pages = range_page_count(m_video_memory_paddr, video_memory_bytes);
PageTable::kernel().unmap_range(m_video_memory_vaddr, video_memory_pages * PAGE_SIZE);
}
BAN::ErrorOr<void> FramebufferDevice::initialize()
{
size_t video_memory_pages = range_page_count(m_video_memory_paddr, m_height * m_pitch);
const size_t video_memory_bytes = m_bga_controller ? m_bga_controller->get_fb_fix_info().mem_size : m_height * m_pitch;
const size_t video_memory_pages = range_page_count(m_video_memory_paddr, video_memory_bytes);
m_video_memory_vaddr = PageTable::kernel().reserve_free_contiguous_pages(video_memory_pages, KERNEL_OFFSET);
if (m_video_memory_vaddr == 0)
return BAN::Error::from_errno(ENOMEM);
PageTable::kernel().map_range_at(
m_video_memory_paddr & PAGE_ADDR_MASK,
m_video_memory_vaddr,
@@ -86,7 +113,7 @@ namespace Kernel
m_video_buffer = TRY(VirtualRange::create_to_vaddr_range(
PageTable::kernel(),
{ KERNEL_OFFSET, UINTPTR_MAX },
BAN::Math::div_round_up<size_t>(m_width * m_height * (BANAN_FB_BPP / 8), PAGE_SIZE) * PAGE_SIZE,
video_memory_pages * PAGE_SIZE,
PageTable::Flags::ReadWrite | PageTable::Flags::Present,
false
));
@@ -94,6 +121,37 @@ namespace Kernel
return {};
}
BAN::ErrorOr<void> FramebufferDevice::set_bga_controller(BAN::RefPtr<BGAController> bga_controller)
{
ASSERT(!m_bga_controller);
if (auto var_info = bga_controller->get_fb_var_info(); var_info.bpp != 32)
{
var_info.bpp = 32;
TRY(bga_controller->set_fb_var_info(var_info));
}
const auto fix_info = bga_controller->get_fb_fix_info();
const auto var_info = bga_controller->get_fb_var_info();
ASSERT(m_video_memory_vaddr);
PageTable::kernel().unmap_range(m_video_memory_vaddr, m_height * m_pitch);
m_video_memory_vaddr = 0;
m_video_memory_paddr = fix_info.mem_start;
m_width = var_info.xres;
m_height = var_info.yres;
m_pitch = fix_info.pitch;
m_bpp = var_info.bpp;
m_bga_controller = bga_controller;
TRY(initialize());
return {};
}
BAN::ErrorOr<size_t> FramebufferDevice::read_impl(off_t offset, BAN::ByteSpan buffer)
{
// Reading from negative offset will fill buffer with framebuffer info
+242
View File
@@ -0,0 +1,242 @@
#include <kernel/Device/FramebufferDevice.h>
#include <kernel/Graphics/BGA.h>
#include <kernel/IO.h>
#include <kernel/Terminal/FramebufferTerminal.h>
#include <kernel/Terminal/VirtualTTY.h>
namespace Kernel
{
enum BGA_IO_PORT : uint16_t
{
BGA_IO_PORT_INDEX = 0x1CE,
BGA_IO_PORT_DATA = 0x1CF,
};
enum BGA_REG : uint16_t
{
BGA_REG_ID = 0,
BGA_REG_XRES = 1,
BGA_REG_YRES = 2,
BGA_REG_BPP = 3,
BGA_REG_ENABLE = 4,
BGA_REG_BANK = 5,
BGA_REG_XRES_VIRT = 6,
BGA_REG_YRES_VIRT = 7,
BGA_REG_XOFF = 8,
BGA_REG_YOFF = 9,
};
enum BGA_ID : uint16_t
{
BGA_ID_0 = 0xB0C0,
BGA_ID_1 = 0xB0C1,
BGA_ID_2 = 0xB0C2,
BGA_ID_3 = 0xB0C3,
BGA_ID_4 = 0xB0C4,
BGA_ID_5 = 0xB0C5,
};
enum BGA_ENABLE : uint16_t
{
BGA_ENABLE_ENABLE = 0x01,
BGA_ENABLE_LFB_ENABLE = 0x40,
BGA_ENABLE_NOCLEARMEM = 0x80,
};
BAN::ErrorOr<BAN::RefPtr<BGAController>> BGAController::create(PCI::Device& pci_device)
{
auto* bga_controller_ptr = new BGAController(pci_device);
if (bga_controller_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto bga_controller = BAN::RefPtr<BGAController>::adopt(bga_controller_ptr);
TRY(bga_controller->initialize());
return bga_controller;
}
BGAController::BGAController(PCI::Device& pci_device)
: m_pci_device(pci_device)
{ }
BAN::ErrorOr<void> BGAController::initialize()
{
auto boot_framebuffer = FramebufferDevice::boot_framebuffer();
m_lfb_bar = TRY(m_pci_device.allocate_bar_region(0));
if (m_lfb_bar->type() != PCI::BarType::MEM)
{
dwarnln("BGA LFB is not memory bar");
return BAN::Error::from_errno(EINVAL);
}
m_fix_info = {
.xpanstep = 1,
.ypanstep = 1,
.pitch = 0,
.mem_start = static_cast<uint64_t>(m_lfb_bar->paddr()),
.mem_size = static_cast<uint32_t>(m_lfb_bar->size()),
};
const auto ready_to_use_flags = BGA_ENABLE_LFB_ENABLE | BGA_ENABLE_ENABLE;
if ((read_reg(BGA_REG_ENABLE) & ready_to_use_flags) == ready_to_use_flags)
{
m_var_info = {
.xres = read_reg(BGA_REG_XRES),
.yres = read_reg(BGA_REG_YRES),
.xres_virt = read_reg(BGA_REG_XRES_VIRT),
.yres_virt = read_reg(BGA_REG_XRES_VIRT),
.xoff = read_reg(BGA_REG_XOFF),
.yoff = read_reg(BGA_REG_YOFF),
.bpp = read_reg(BGA_REG_BPP),
};
m_fix_info.pitch = m_var_info.xres_virt * m_var_info.bpp / 8;
}
else
{
const uint32_t xres = boot_framebuffer ? boot_framebuffer->width() : 1280;
const uint32_t yres = boot_framebuffer ? boot_framebuffer->height() : 800;
const uint32_t bpp = boot_framebuffer ? boot_framebuffer->bpp() : 32;
TRY(set_fb_var_info({
.xres = xres,
.yres = yres,
.xres_virt = xres,
.yres_virt = yres * 2,
.xoff = 0,
.yoff = 0,
.bpp = bpp,
}));
}
if (boot_framebuffer)
{
// FIXME: check that this is actually the boot framebuffer
TRY(boot_framebuffer->set_bga_controller(this));
}
else
{
auto framebuffer_device = TRY(FramebufferDevice::create(this));
auto fb_terminal_driver = TRY(FramebufferTerminalDriver::create(framebuffer_device));
// FIXME: query vtty instead of checking the current tty
if (auto tty = TTY::current(); tty && tty->is_vtty())
TRY(static_cast<VirtualTTY*>(tty.ptr())->set_terminal_driver(fb_terminal_driver));
else
TRY(VirtualTTY::create(fb_terminal_driver));
}
return {};
}
BAN::ErrorOr<void> BGAController::set_fb_var_info(const fb_var_info& var_info)
{
const auto validate_u16 = [](auto value) -> BAN::ErrorOr<void> {
if (value > BAN::numeric_limits<uint16_t>::max())
return BAN::Error::from_errno(EINVAL);
return {};
};
TRY(validate_u16(var_info.xres));
TRY(validate_u16(var_info.xres));
TRY(validate_u16(var_info.xres_virt));
TRY(validate_u16(var_info.yres_virt));
TRY(validate_u16(var_info.xoff));
TRY(validate_u16(var_info.yoff));
TRY(validate_u16(var_info.bpp));
if (var_info.xres + var_info.xoff > var_info.xres_virt)
return BAN::Error::from_errno(EINVAL);
if (var_info.yres + var_info.yoff > var_info.yres_virt)
return BAN::Error::from_errno(EINVAL);
if (static_cast<uint64_t>(var_info.xres_virt) * var_info.yres_virt * var_info.bpp / 8 > m_lfb_bar->size())
return BAN::Error::from_errno(EINVAL);
SpinLockGuard _(m_lock);
const bool needs_reconf =
(m_var_info.xres != var_info.xres) ||
(m_var_info.yres != var_info.yres) ||
(m_var_info.xres_virt != var_info.xres_virt) ||
(m_var_info.yres_virt != var_info.yres_virt) ||
(m_var_info.bpp != var_info.bpp);
if (!needs_reconf)
{
write_reg(BGA_REG_XOFF, var_info.xoff);
write_reg(BGA_REG_YOFF, var_info.yoff);
m_var_info.xoff = var_info.xoff;
m_var_info.yoff = var_info.yoff;
return {};
}
const auto old_enable = read_reg(BGA_REG_ENABLE);
write_reg(BGA_REG_ENABLE, 0);
write_reg(BGA_REG_BPP, var_info.bpp);
write_reg(BGA_REG_XRES, var_info.xres);
write_reg(BGA_REG_YRES, var_info.yres);
write_reg(BGA_REG_XRES_VIRT, var_info.xres_virt);
write_reg(BGA_REG_YRES_VIRT, var_info.yres_virt);
const bool valid_config =
read_reg(BGA_REG_BPP) == var_info.bpp &&
read_reg(BGA_REG_XRES) == var_info.xres &&
read_reg(BGA_REG_YRES) == var_info.yres &&
read_reg(BGA_REG_XRES_VIRT) == var_info.xres_virt;
if (!valid_config)
{
write_reg(BGA_REG_BPP, m_var_info.bpp);
write_reg(BGA_REG_XRES, m_var_info.xres);
write_reg(BGA_REG_YRES, m_var_info.yres);
write_reg(BGA_REG_XRES_VIRT, m_var_info.xres_virt);
write_reg(BGA_REG_YRES_VIRT, m_var_info.yres_virt);
write_reg(BGA_REG_ENABLE, BGA_ENABLE_NOCLEARMEM | old_enable);
return BAN::Error::from_errno(EINVAL);
}
write_reg(BGA_REG_ENABLE, BGA_ENABLE_LFB_ENABLE | BGA_ENABLE_ENABLE);
// NOTE: At least qemu only sets virtual yres on enable and it will be
// maximum that fits within vram. Our initial bounds check should
// make sure this always succeeds
ASSERT(read_reg(BGA_REG_YRES_VIRT) >= var_info.yres_virt);
write_reg(BGA_REG_XOFF, var_info.xoff);
write_reg(BGA_REG_YOFF, var_info.yoff);
m_var_info = var_info;
m_fix_info.pitch = m_var_info.xres_virt * m_var_info.bpp / 8;
return {};
}
void BGAController::write_reg(uint16_t reg, uint16_t value)
{
SpinLockGuard _(m_lock);
IO::outw(BGA_IO_PORT_INDEX, reg);
IO::outw(BGA_IO_PORT_DATA, value);
}
uint16_t BGAController::read_reg(uint16_t reg)
{
SpinLockGuard _(m_lock);
IO::outw(BGA_IO_PORT_INDEX, reg);
return IO::inw(BGA_IO_PORT_DATA);
}
fb_fix_info BGAController::get_fb_fix_info() const
{
SpinLockGuard _(m_lock);
return m_fix_info;
}
fb_var_info BGAController::get_fb_var_info() const
{
SpinLockGuard _(m_lock);
return m_var_info;
}
}
+8
View File
@@ -3,6 +3,7 @@
#include <kernel/ACPI/ACPI.h>
#include <kernel/APIC.h>
#include <kernel/Audio/Controller.h>
#include <kernel/Graphics/BGA.h>
#include <kernel/IDT.h>
#include <kernel/IO.h>
#include <kernel/Memory/PageTable.h>
@@ -258,6 +259,13 @@ namespace Kernel::PCI
for_each_device(
[&](PCI::Device& pci_device)
{
if (pci_device.vendor_id() == 0x1234 && pci_device.device_id() == 0x1111)
{
if (auto ret = BGAController::create(pci_device); ret.is_error())
dprintln("BGA: {}", ret.error());
return;
}
switch (pci_device.class_code())
{
case 0x01:
-1
View File
@@ -82,7 +82,6 @@ namespace Kernel
BAN::RefPtr<TTY> TTY::current()
{
ASSERT(s_tty);
return s_tty;
}
+48 -19
View File
@@ -68,6 +68,8 @@ static bool should_disable_serial(BAN::StringView full_command_line)
extern bool g_disable_debug;
static ParsedCommandLine cmdline;
static bool s_has_early_console { false };
static void parse_command_line()
{
auto full_command_line = Kernel::g_boot_info.command_line.sv();
@@ -160,7 +162,24 @@ extern "C" void kernel_main(uint32_t boot_magic, uint32_t boot_info)
if (auto ret = TerminalDriver::initialize_from_boot_info(); ret.is_error())
dprintln("failed to initialize terminal driver: {}", ret.error());
else
dprintln("terminal driver initialized");
{
MUST(VirtualTTY::create(g_terminal_driver));
dprintln("Virtual TTY initialized");
}
if (Serial::has_devices())
{
Serial::initialize_devices();
dprintln("Serial devices initialized");
}
if (auto ret = DevFileSystem::get().root_inode()->find_inode(cmdline.console); !ret.is_error())
{
auto console = ret.release_value();
ASSERT(console->is_tty());
static_cast<Kernel::TTY*>(console.ptr())->set_as_current();
s_has_early_console = true;
}
if (!cmdline.disable_smp)
InterruptController::get().initialize_multiprocessor();
@@ -171,18 +190,6 @@ extern "C" void kernel_main(uint32_t boot_magic, uint32_t boot_info)
MUST(SharedMemoryObjectManager::initialize());
dprintln("Shared memory object system initialized");
if (Serial::has_devices())
{
Serial::initialize_devices();
dprintln("Serial devices initialized");
}
if (g_terminal_driver)
{
auto vtty = MUST(VirtualTTY::create(g_terminal_driver));
dprintln("Virtual TTY initialized");
}
Random::initialize();
dprintln("RNG initialized");
@@ -213,10 +220,6 @@ static void init2(void*)
ProcFileSystem::get().post_scheduler_initialize();
auto console = MUST(DevFileSystem::get().root_inode()->find_inode(cmdline.console));
ASSERT(console->is_tty());
static_cast<Kernel::TTY*>(console.ptr())->set_as_current();
// This only initializes PCIManager by enumerating available devices and choosing PCIe/legacy
// ACPI might require PCI access during its namespace initialization
PCI::PCIManager::initialize();
@@ -256,6 +259,32 @@ static void init2(void*)
PCI::PCIManager::get().initialize_devices(cmdline.disable_usb);
dprintln("PCI devices initialized");
if (!s_has_early_console)
{
auto console_or_error = DevFileSystem::get().root_inode()->find_inode(cmdline.console);
if (!console_or_error.is_error())
{
auto console = console_or_error.release_value();
ASSERT(console->is_tty());
static_cast<TTY*>(console.ptr())->set_as_current();
}
else
{
BAN::RefPtr<TTY> tty;
DevFileSystem::get().for_each_inode([&](BAN::RefPtr<Inode> inode) -> BAN::Iteration {
if (!inode->is_tty())
return BAN::Iteration::Continue;
tty = static_cast<TTY*>(inode.ptr());
return BAN::Iteration::Break;
});
ASSERT(tty);
dwarnln("Could not find /dev/{}, falling back to /dev/{}", cmdline.console, tty->name());
tty->set_as_current();
}
}
VirtualFileSystem::initialize(cmdline.root);
dprintln("VFS initialized");
@@ -266,9 +295,9 @@ static void init2(void*)
Banos::initialize_initial_drivers();
auto console_path = MUST(BAN::String::formatted("/dev/{}", cmdline.console));
auto console_path = MUST(BAN::String::formatted("/dev/{}", TTY::current()->name()));
auto console_path_sv = console_path.sv();
MUST(Process::create_userspace({ 0, 0, 0, 0 }, "/usr/bin/init"_sv, BAN::Span<BAN::StringView>(&console_path_sv, 1)));
MUST(Process::create_userspace({ 0, 0, 0, 0 }, "/usr/bin/init"_sv, { &console_path_sv, 1 }));
}
extern "C" void ap_main()
@@ -15,6 +15,25 @@ struct framebuffer_info_t
uint32_t height;
};
struct fb_fix_info
{
uint32_t xpanstep;
uint32_t ypanstep;
uint32_t pitch;
uint64_t mem_start;
uint32_t mem_size;
};
struct fb_var_info
{
uint32_t xres;
uint32_t yres;
uint32_t xres_virt;
uint32_t yres_virt;
uint32_t xoff;
uint32_t yoff;
uint32_t bpp;
};
__END_DECLS
#endif