diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 6da4d4a8..17754d5b 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -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 diff --git a/kernel/include/kernel/Device/FramebufferDevice.h b/kernel/include/kernel/Device/FramebufferDevice.h index 20c9b333..0a94190e 100644 --- a/kernel/include/kernel/Device/FramebufferDevice.h +++ b/kernel/include/kernel/Device/FramebufferDevice.h @@ -6,15 +6,22 @@ namespace Kernel { + class BGAController; + class FramebufferDevice : public CharacterDevice { public: + static BAN::ErrorOr> create(paddr_t paddr, uint32_t width, uint32_t height, uint32_t pitch, uint8_t bpp); + static BAN::ErrorOr> create(BAN::RefPtr); static BAN::ErrorOr> create_from_boot_framebuffer(); static BAN::RefPtr 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 set_bga_controller(BAN::RefPtr); 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 m_video_buffer; + BAN::RefPtr m_bga_controller; friend class FramebufferMemoryRegion; }; diff --git a/kernel/include/kernel/Graphics/BGA.h b/kernel/include/kernel/Graphics/BGA.h new file mode 100644 index 00000000..49c8790d --- /dev/null +++ b/kernel/include/kernel/Graphics/BGA.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Kernel +{ + + class BGAController : public BAN::RefCounted + { + public: + static BAN::ErrorOr> create(PCI::Device&); + + fb_fix_info get_fb_fix_info() const; + fb_var_info get_fb_var_info() const; + BAN::ErrorOr set_fb_var_info(const fb_var_info&); + + private: + BGAController(PCI::Device&); + BAN::ErrorOr 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 m_lfb_bar; + + fb_fix_info m_fix_info {}; + fb_var_info m_var_info {}; + }; + +} diff --git a/kernel/include/kernel/Terminal/TTY.h b/kernel/include/kernel/Terminal/TTY.h index eabbea24..a1fb15b7 100644 --- a/kernel/include/kernel/Terminal/TTY.h +++ b/kernel/include/kernel/Terminal/TTY.h @@ -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); diff --git a/kernel/include/kernel/Terminal/VirtualTTY.h b/kernel/include/kernel/Terminal/VirtualTTY.h index 9fe3e045..590a0749 100644 --- a/kernel/include/kernel/Terminal/VirtualTTY.h +++ b/kernel/include/kernel/Terminal/VirtualTTY.h @@ -21,6 +21,7 @@ namespace Kernel void clear() override; + bool is_vtty() const override { return true; } BAN::ErrorOr set_terminal_driver(BAN::RefPtr); protected: diff --git a/kernel/kernel/Device/FramebufferDevice.cpp b/kernel/kernel/Device/FramebufferDevice.cpp index ade0b653..f083c3f2 100644 --- a/kernel/kernel/Device/FramebufferDevice.cpp +++ b/kernel/kernel/Device/FramebufferDevice.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -26,19 +27,14 @@ namespace Kernel return s_boot_framebuffer; } - BAN::ErrorOr> FramebufferDevice::create_from_boot_framebuffer() + BAN::ErrorOr> 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> FramebufferDevice::create(BAN::RefPtr 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> 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 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(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 FramebufferDevice::set_bga_controller(BAN::RefPtr 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 FramebufferDevice::read_impl(off_t offset, BAN::ByteSpan buffer) { // Reading from negative offset will fill buffer with framebuffer info diff --git a/kernel/kernel/Graphics/BGA.cpp b/kernel/kernel/Graphics/BGA.cpp new file mode 100644 index 00000000..26b33256 --- /dev/null +++ b/kernel/kernel/Graphics/BGA.cpp @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include + +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> 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::adopt(bga_controller_ptr); + TRY(bga_controller->initialize()); + return bga_controller; + } + + BGAController::BGAController(PCI::Device& pci_device) + : m_pci_device(pci_device) + { } + + BAN::ErrorOr 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(m_lfb_bar->paddr()), + .mem_size = static_cast(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(tty.ptr())->set_terminal_driver(fb_terminal_driver)); + else + TRY(VirtualTTY::create(fb_terminal_driver)); + } + + return {}; + } + + BAN::ErrorOr BGAController::set_fb_var_info(const fb_var_info& var_info) + { + const auto validate_u16 = [](auto value) -> BAN::ErrorOr { + if (value > BAN::numeric_limits::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(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; + } + +} diff --git a/kernel/kernel/PCI.cpp b/kernel/kernel/PCI.cpp index 40e2ab66..20459d81 100644 --- a/kernel/kernel/PCI.cpp +++ b/kernel/kernel/PCI.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -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: diff --git a/kernel/kernel/Terminal/TTY.cpp b/kernel/kernel/Terminal/TTY.cpp index 2e0540a2..5f333bbd 100644 --- a/kernel/kernel/Terminal/TTY.cpp +++ b/kernel/kernel/Terminal/TTY.cpp @@ -82,7 +82,6 @@ namespace Kernel BAN::RefPtr TTY::current() { - ASSERT(s_tty); return s_tty; } diff --git a/kernel/kernel/kernel.cpp b/kernel/kernel/kernel.cpp index 5547ed99..668b371a 100644 --- a/kernel/kernel/kernel.cpp +++ b/kernel/kernel/kernel.cpp @@ -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(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(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(console.ptr())->set_as_current(); + } + else + { + BAN::RefPtr tty; + DevFileSystem::get().for_each_inode([&](BAN::RefPtr inode) -> BAN::Iteration { + if (!inode->is_tty()) + return BAN::Iteration::Continue; + tty = static_cast(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(&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() diff --git a/userspace/libraries/LibC/include/sys/framebuffer.h b/userspace/libraries/LibC/include/sys/framebuffer.h index a201ed85..100e4d75 100644 --- a/userspace/libraries/LibC/include/sys/framebuffer.h +++ b/userspace/libraries/LibC/include/sys/framebuffer.h @@ -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