From 252c92d565120eeaa1a5dd4b52c006120afdde09 Mon Sep 17 00:00:00 2001 From: Liav A Date: Fri, 16 Sep 2022 13:17:02 +0300 Subject: [PATCH] Kernel/Graphics: Introduce support for QEMU isa-vga device This device is supposed to be used in microvm and ISA-PC machine types, and we assume that if we are able to probe for the QEMU BGA version of 0xB0C5, then we have an existing ISA Bochs VGA adapter to utilize. To ensure we don't instantiate the driver for non isa-vga devices, we try to ensure that PCI is disabled because hardware IO test probe failed so we can be sure that we use this special handling code only in the QEMU microvm and ISA-PC machine types. Unfortunately, this means that if for some reason the isa-vga device is attached for the i440FX or Q35 machine types, we simply are not able to drive the device in such setups at all. To determine the amount of VRAM being available, we read VBE register at offset 0xA. That register holds the amount of VRAM divided by 64K, so we need to multiply the value in our code to use the actual VRAM size value again. The isa-vga device requires us to hardcode the framebuffer physical address to 0xE0000000, and that address is not expected to change in the future as many other projects rely on the isa-vga framebuffer to be present at that physical memory address. --- .../x86/Hypervisor/BochsDisplayConnector.cpp | 52 ++++++++++++++----- .../x86/Hypervisor/BochsDisplayConnector.h | 3 ++ Kernel/Bus/PCI/Access.cpp | 5 ++ Kernel/Bus/PCI/Access.h | 1 + Kernel/Graphics/Bochs/Definitions.h | 2 + Kernel/Graphics/GraphicsManagement.cpp | 22 ++++++++ Kernel/Graphics/GraphicsManagement.h | 2 + 7 files changed, 75 insertions(+), 12 deletions(-) diff --git a/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.cpp b/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.cpp index 4f52ed358d..74fb9b3338 100644 --- a/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.cpp +++ b/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,45 @@ namespace Kernel { +static void set_register_with_io(u16 index, u16 data) +{ + IO::out16(VBE_DISPI_IOPORT_INDEX, index); + IO::out16(VBE_DISPI_IOPORT_DATA, data); +} + +static u16 get_register_with_io(u16 index) +{ + IO::out16(VBE_DISPI_IOPORT_INDEX, index); + return IO::in16(VBE_DISPI_IOPORT_DATA); +} + +LockRefPtr BochsDisplayConnector::try_create_for_vga_isa_connector() +{ + VERIFY(PCI::Access::is_hardware_disabled()); + BochsDisplayConnector::IndexID index_id = get_register_with_io(0); + if (index_id != VBE_DISPI_ID5) + return {}; + + auto video_ram_64k_chunks_count = get_register_with_io(to_underlying(BochsDISPIRegisters::VIDEO_RAM_64K_CHUNKS_COUNT)); + if (video_ram_64k_chunks_count == 0 || video_ram_64k_chunks_count == 0xffff) { + dmesgln("Graphics: Bochs ISA VGA compatible adapter does not indicate amount of VRAM, default to 8 MiB"); + video_ram_64k_chunks_count = (8 * MiB) / (64 * KiB); + } else { + dmesgln("Graphics: Bochs ISA VGA compatible adapter indicates {} bytes of VRAM", video_ram_64k_chunks_count * (64 * KiB)); + } + + // Note: The default physical address for isa-vga framebuffer in QEMU is 0xE0000000. + // Since this is probably hardcoded at other OSes in their guest drivers, + // we can assume this is going to stay the same framebuffer physical address for + // this device and will not be changed in the future. + auto device_or_error = DeviceManagement::try_create_device(PhysicalAddress(0xE0000000), video_ram_64k_chunks_count * (64 * KiB)); + VERIFY(!device_or_error.is_error()); + auto connector = device_or_error.release_value(); + MUST(connector->create_attached_framebuffer_console()); + MUST(connector->initialize_edid_for_generic_monitor({})); + return connector; +} + NonnullLockRefPtr BochsDisplayConnector::must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool virtual_box_hardware) { auto device_or_error = DeviceManagement::try_create_device(framebuffer_address, framebuffer_resource_size); @@ -41,18 +81,6 @@ ErrorOr BochsDisplayConnector::create_attached_framebuffer_console() return {}; } -static void set_register_with_io(u16 index, u16 data) -{ - IO::out16(VBE_DISPI_IOPORT_INDEX, index); - IO::out16(VBE_DISPI_IOPORT_DATA, data); -} - -static u16 get_register_with_io(u16 index) -{ - IO::out16(VBE_DISPI_IOPORT_INDEX, index); - return IO::in16(VBE_DISPI_IOPORT_DATA); -} - BochsDisplayConnector::IndexID BochsDisplayConnector::index_id() const { return get_register_with_io(0); diff --git a/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.h b/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.h index b13976d8b4..cf9f79f9a4 100644 --- a/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.h +++ b/Kernel/Arch/x86/Hypervisor/BochsDisplayConnector.h @@ -20,10 +20,13 @@ class BochsDisplayConnector : public DisplayConnector { friend class BochsGraphicsAdapter; friend class DeviceManagement; + friend class GraphicsManagement; public: AK_TYPEDEF_DISTINCT_ORDERED_ID(u16, IndexID); + static LockRefPtr try_create_for_vga_isa_connector(); + static NonnullLockRefPtr must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool virtual_box_hardware); private: diff --git a/Kernel/Bus/PCI/Access.cpp b/Kernel/Bus/PCI/Access.cpp index 4c89d5c6b3..95d0162d73 100644 --- a/Kernel/Bus/PCI/Access.cpp +++ b/Kernel/Bus/PCI/Access.cpp @@ -40,6 +40,11 @@ bool Access::is_initialized() return (s_access != nullptr); } +bool Access::is_hardware_disabled() +{ + return g_pci_access_io_probe_failed; +} + bool Access::is_disabled() { return g_pci_access_is_disabled_from_commandline || g_pci_access_io_probe_failed; diff --git a/Kernel/Bus/PCI/Access.h b/Kernel/Bus/PCI/Access.h index 7f91d50e4e..51d7776b5e 100644 --- a/Kernel/Bus/PCI/Access.h +++ b/Kernel/Bus/PCI/Access.h @@ -30,6 +30,7 @@ public: static Access& the(); static bool is_initialized(); static bool is_disabled(); + static bool is_hardware_disabled(); void write8_field(Address address, u32 field, u8 value); void write16_field(Address address, u32 field, u16 value); diff --git a/Kernel/Graphics/Bochs/Definitions.h b/Kernel/Graphics/Bochs/Definitions.h index 12ba53650a..1891b9f756 100644 --- a/Kernel/Graphics/Bochs/Definitions.h +++ b/Kernel/Graphics/Bochs/Definitions.h @@ -34,6 +34,7 @@ enum class BochsDISPIRegisters { VIRT_HEIGHT = 0x7, X_OFFSET = 0x8, Y_OFFSET = 0x9, + VIDEO_RAM_64K_CHUNKS_COUNT = 0xA, }; struct [[gnu::packed]] DISPIInterface { @@ -47,6 +48,7 @@ struct [[gnu::packed]] DISPIInterface { u16 virt_height; u16 x_offset; u16 y_offset; + u16 vram_64k_chunks_count; }; struct [[gnu::packed]] ExtensionRegisters { diff --git a/Kernel/Graphics/GraphicsManagement.cpp b/Kernel/Graphics/GraphicsManagement.cpp index ef1f9ac195..6659b02b6f 100644 --- a/Kernel/Graphics/GraphicsManagement.cpp +++ b/Kernel/Graphics/GraphicsManagement.cpp @@ -7,6 +7,9 @@ #include #include #include +#if ARCH(I386) || ARCH(X86_64) +# include +#endif #include #include #include @@ -206,6 +209,25 @@ UNMAP_AFTER_INIT bool GraphicsManagement::initialize() return true; } + // Note: Don't try to initialize an ISA Bochs VGA adapter if PCI hardware is + // present but the user decided to disable its usage nevertheless. + // Otherwise we risk using the Bochs VBE driver on a wrong physical address + // for the framebuffer. + if (PCI::Access::is_hardware_disabled() && !(graphics_subsystem_mode == CommandLine::GraphicsSubsystemMode::Limited && !multiboot_framebuffer_addr.is_null() && multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB)) { +#if ARCH(I386) || ARCH(X86_64) + auto vga_isa_bochs_display_connector = BochsDisplayConnector::try_create_for_vga_isa_connector(); + if (vga_isa_bochs_display_connector) { + dmesgln("Graphics: Using a Bochs ISA VGA compatible adapter"); + MUST(vga_isa_bochs_display_connector->set_safe_mode_setting()); + m_platform_board_specific_display_connector = vga_isa_bochs_display_connector; + dmesgln("Graphics: Invoking manual blanking with VGA ISA ports"); + SpinlockLocker locker(m_main_vga_lock); + IO::out8(0x3c0, 0x20); + return true; + } +#endif + } + if (graphics_subsystem_mode == CommandLine::GraphicsSubsystemMode::Limited && !multiboot_framebuffer_addr.is_null() && multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB) { initialize_preset_resolution_generic_display_connector(); return true; diff --git a/Kernel/Graphics/GraphicsManagement.h b/Kernel/Graphics/GraphicsManagement.h index 28ffd32737..842c837d75 100644 --- a/Kernel/Graphics/GraphicsManagement.h +++ b/Kernel/Graphics/GraphicsManagement.h @@ -56,6 +56,8 @@ private: // Note: This is only used when booting with kernel commandline that includes "graphics_subsystem_mode=limited" LockRefPtr m_preset_resolution_generic_display_connector; + LockRefPtr m_platform_board_specific_display_connector; + unsigned m_current_minor_number { 0 }; SpinlockProtected> m_display_connector_nodes { LockRank::None };