mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 17:52:45 +00:00 
			
		
		
		
	Kernel/Graphics: Use DisplayConnector design for the Bochs driver
This commit is contained in:
		
							parent
							
								
									6d7e2596e0
								
							
						
					
					
						commit
						e9a74cbefb
					
				
					 8 changed files with 450 additions and 226 deletions
				
			
		|  | @ -2,8 +2,8 @@ | |||
| MainScreen=0 | ||||
| 
 | ||||
| [Screen0] | ||||
| Mode=Device | ||||
| Device=/dev/fb0 | ||||
| Mode=DisplayConnectorDevice | ||||
| Device=/dev/gpu/connector0 | ||||
| Left=0 | ||||
| Top=0 | ||||
| Width=1024 | ||||
|  |  | |||
|  | @ -74,7 +74,9 @@ set(KERNEL_SOURCES | |||
|     Devices/HID/PS2MouseDevice.cpp | ||||
|     Devices/HID/VMWareMouseDevice.cpp | ||||
|     GlobalProcessExposed.cpp | ||||
|     Graphics/Bochs/DisplayConnector.cpp | ||||
|     Graphics/Bochs/GraphicsAdapter.cpp | ||||
|     Graphics/Bochs/QEMUDisplayConnector.cpp | ||||
|     Graphics/Console/BootFramebufferConsole.cpp | ||||
|     Graphics/Console/GenericFramebufferConsole.cpp | ||||
|     Graphics/Console/ContiguousFramebufferConsole.cpp | ||||
|  |  | |||
							
								
								
									
										161
									
								
								Kernel/Graphics/Bochs/DisplayConnector.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Kernel/Graphics/Bochs/DisplayConnector.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,161 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include <Kernel/Arch/x86/IO.h> | ||||
| #include <Kernel/Debug.h> | ||||
| #include <Kernel/Devices/DeviceManagement.h> | ||||
| #include <Kernel/Graphics/Bochs/Definitions.h> | ||||
| #include <Kernel/Graphics/Bochs/DisplayConnector.h> | ||||
| #include <Kernel/Graphics/Console/ContiguousFramebufferConsole.h> | ||||
| #include <Kernel/Graphics/GraphicsManagement.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| NonnullRefPtr<BochsDisplayConnector> BochsDisplayConnector::must_create(PhysicalAddress framebuffer_address) | ||||
| { | ||||
|     auto device_or_error = DeviceManagement::try_create_device<BochsDisplayConnector>(framebuffer_address); | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| BochsDisplayConnector::BochsDisplayConnector(PhysicalAddress framebuffer_address) | ||||
|     : DisplayConnector() | ||||
|     , m_framebuffer_address(framebuffer_address) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::create_attached_framebuffer_console() | ||||
| { | ||||
|     auto rounded_size = TRY(Memory::page_round_up(1024 * sizeof(u32) * 768 * 2)); | ||||
|     m_framebuffer_region = TRY(MM.allocate_kernel_region(m_framebuffer_address.page_base(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite)); | ||||
|     [[maybe_unused]] auto result = m_framebuffer_region->set_write_combine(true); | ||||
|     m_framebuffer_data = m_framebuffer_region->vaddr().offset(m_framebuffer_address.offset_in_page()).as_ptr(); | ||||
|     // We assume safe resolution is 1024x768x32
 | ||||
|     m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address, 1024, 768, 1024 * sizeof(u32)); | ||||
|     GraphicsManagement::the().set_console(*m_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); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<size_t> BochsDisplayConnector::write_to_first_surface(u64 offset, UserOrKernelBuffer const& buffer, size_t length) | ||||
| { | ||||
|     VERIFY(m_control_lock.is_locked()); | ||||
|     if (offset + length > m_framebuffer_region->size()) | ||||
|         return Error::from_errno(EOVERFLOW); | ||||
|     TRY(buffer.read(m_framebuffer_data + offset, 0, length)); | ||||
|     return length; | ||||
| } | ||||
| 
 | ||||
| void BochsDisplayConnector::enable_console() | ||||
| { | ||||
|     VERIFY(m_control_lock.is_locked()); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     m_framebuffer_console->enable(); | ||||
| } | ||||
| 
 | ||||
| void BochsDisplayConnector::disable_console() | ||||
| { | ||||
|     VERIFY(m_control_lock.is_locked()); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     m_framebuffer_console->disable(); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::flush_first_surface() | ||||
| { | ||||
|     return Error::from_errno(ENOTSUP); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::set_safe_mode_setting() | ||||
| { | ||||
|     DisplayConnector::ModeSetting safe_mode_set { | ||||
|         .horizontal_stride = 1024 * sizeof(u32), | ||||
|         .pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
 | ||||
|         .horizontal_active = 1024, | ||||
|         .horizontal_front_porch_pixels = 0, // Note: There's no horizontal_front_porch_pixels in paravirtualized hardware
 | ||||
|         .horizontal_sync_time_pixels = 0,   // Note: There's no horizontal_sync_time_pixels in paravirtualized hardware
 | ||||
|         .horizontal_blank_pixels = 0,       // Note: There's no horizontal_blank_pixels in paravirtualized hardware
 | ||||
|         .vertical_active = 768, | ||||
|         .vertical_front_porch_lines = 0, // Note: There's no vertical_front_porch_lines in paravirtualized hardware
 | ||||
|         .vertical_sync_time_lines = 0,   // Note: There's no vertical_sync_time_lines in paravirtualized hardware
 | ||||
|         .vertical_blank_lines = 0,       // Note: There's no vertical_blank_lines in paravirtualized hardware
 | ||||
|         .horizontal_offset = 0, | ||||
|         .vertical_offset = 0, | ||||
|     }; | ||||
|     return set_mode_setting(safe_mode_set); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::set_mode_setting(ModeSetting const& mode_setting) | ||||
| { | ||||
|     SpinlockLocker locker(m_modeset_lock); | ||||
|     size_t width = mode_setting.horizontal_active; | ||||
|     size_t height = mode_setting.vertical_active; | ||||
| 
 | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsDisplayConnector resolution registers set to - {}x{}", width, height); | ||||
| 
 | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::ENABLE), 0); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::XRES), (u16)width); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::YRES), (u16)height); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::VIRT_WIDTH), (u16)width); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::VIRT_HEIGHT), (u16)height * 2); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::BPP), 32); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::ENABLE), to_underlying(BochsFramebufferSettings::Enabled) | to_underlying(BochsFramebufferSettings::LinearFramebuffer)); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::BANK), 0); | ||||
|     if ((u16)width != get_register_with_io(to_underlying(BochsDISPIRegisters::XRES)) || (u16)height != get_register_with_io(to_underlying(BochsDISPIRegisters::YRES))) { | ||||
|         return Error::from_errno(ENOTIMPL); | ||||
|     } | ||||
|     auto current_horizontal_active = get_register_with_io(to_underlying(BochsDISPIRegisters::XRES)); | ||||
|     auto current_vertical_active = get_register_with_io(to_underlying(BochsDISPIRegisters::YRES)); | ||||
|     DisplayConnector::ModeSetting mode_set { | ||||
|         .horizontal_stride = current_horizontal_active * sizeof(u32), | ||||
|         .pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
 | ||||
|         .horizontal_active = current_horizontal_active, | ||||
|         .horizontal_front_porch_pixels = 0, // Note: There's no horizontal_front_porch_pixels in paravirtualized hardware
 | ||||
|         .horizontal_sync_time_pixels = 0,   // Note: There's no horizontal_sync_time_pixels in paravirtualized hardware
 | ||||
|         .horizontal_blank_pixels = 0,       // Note: There's no horizontal_blank_pixels in paravirtualized hardware
 | ||||
|         .vertical_active = current_vertical_active, | ||||
|         .vertical_front_porch_lines = 0, // Note: There's no vertical_front_porch_lines in paravirtualized hardware
 | ||||
|         .vertical_sync_time_lines = 0,   // Note: There's no vertical_sync_time_lines in paravirtualized hardware
 | ||||
|         .vertical_blank_lines = 0,       // Note: There's no vertical_blank_lines in paravirtualized hardware
 | ||||
|         .horizontal_offset = 0, | ||||
|         .vertical_offset = 0, | ||||
|     }; | ||||
|     m_current_mode_setting = mode_set; | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::set_y_offset(size_t) | ||||
| { | ||||
|     // Note: Although when using this device on QEMU we can actually set the horizontal and vertical offsets
 | ||||
|     // with IO ports, this class is meant to be used for plain old Bochs graphics which might not support
 | ||||
|     // this feature at all.
 | ||||
|     return Error::from_errno(ENOTIMPL); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> BochsDisplayConnector::unblank() | ||||
| { | ||||
|     return Error::from_errno(ENOTIMPL); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										59
									
								
								Kernel/Graphics/Bochs/DisplayConnector.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Kernel/Graphics/Bochs/DisplayConnector.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <AK/RefPtr.h> | ||||
| #include <AK/Try.h> | ||||
| #include <Kernel/Graphics/Bochs/Definitions.h> | ||||
| #include <Kernel/Graphics/Console/GenericFramebufferConsole.h> | ||||
| #include <Kernel/Graphics/DisplayConnector.h> | ||||
| #include <Kernel/Locking/Spinlock.h> | ||||
| #include <Kernel/Memory/TypedMapping.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| class BochsDisplayConnector | ||||
|     : public DisplayConnector { | ||||
|     friend class BochsGraphicsAdapter; | ||||
|     friend class DeviceManagement; | ||||
| 
 | ||||
| public: | ||||
|     TYPEDEF_DISTINCT_ORDERED_ID(u16, IndexID); | ||||
| 
 | ||||
|     static NonnullRefPtr<BochsDisplayConnector> must_create(PhysicalAddress framebuffer_address); | ||||
| 
 | ||||
|     virtual IndexID index_id() const; | ||||
| 
 | ||||
| protected: | ||||
|     ErrorOr<void> create_attached_framebuffer_console(); | ||||
| 
 | ||||
|     explicit BochsDisplayConnector(PhysicalAddress framebuffer_address); | ||||
| 
 | ||||
|     virtual bool mutable_mode_setting_capable() const override final { return true; } | ||||
|     virtual bool double_framebuffering_capable() const override { return false; } | ||||
|     virtual ErrorOr<void> set_mode_setting(ModeSetting const&) override; | ||||
|     virtual ErrorOr<void> set_safe_mode_setting() override final; | ||||
|     virtual ErrorOr<void> set_y_offset(size_t y) override; | ||||
|     virtual ErrorOr<void> unblank() override; | ||||
| 
 | ||||
|     virtual bool partial_flush_support() const override final { return false; } | ||||
|     virtual bool flush_support() const override final { return false; } | ||||
|     // Note: Paravirtualized hardware doesn't require a defined refresh rate for modesetting.
 | ||||
|     virtual bool refresh_rate_support() const override final { return false; } | ||||
| 
 | ||||
|     virtual ErrorOr<size_t> write_to_first_surface(u64 offset, UserOrKernelBuffer const&, size_t length) override final; | ||||
|     virtual ErrorOr<void> flush_first_surface() override final; | ||||
| 
 | ||||
|     virtual void enable_console() override final; | ||||
|     virtual void disable_console() override final; | ||||
| 
 | ||||
|     const PhysicalAddress m_framebuffer_address; | ||||
|     RefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console; | ||||
|     OwnPtr<Memory::Region> m_framebuffer_region; | ||||
|     u8* m_framebuffer_data {}; | ||||
| }; | ||||
| } | ||||
|  | @ -6,11 +6,15 @@ | |||
| 
 | ||||
| #include <AK/Atomic.h> | ||||
| #include <AK/Checked.h> | ||||
| #include <AK/Try.h> | ||||
| #include <Kernel/Arch/x86/IO.h> | ||||
| #include <Kernel/Bus/PCI/API.h> | ||||
| #include <Kernel/Bus/PCI/IDs.h> | ||||
| #include <Kernel/Debug.h> | ||||
| #include <Kernel/Graphics/Bochs/Definitions.h> | ||||
| #include <Kernel/Graphics/Bochs/DisplayConnector.h> | ||||
| #include <Kernel/Graphics/Bochs/GraphicsAdapter.h> | ||||
| #include <Kernel/Graphics/Bochs/QEMUDisplayConnector.h> | ||||
| #include <Kernel/Graphics/Console/ContiguousFramebufferConsole.h> | ||||
| #include <Kernel/Graphics/GraphicsManagement.h> | ||||
| #include <Kernel/Memory/TypedMapping.h> | ||||
|  | @ -22,68 +26,44 @@ UNMAP_AFTER_INIT NonnullRefPtr<BochsGraphicsAdapter> BochsGraphicsAdapter::initi | |||
| { | ||||
|     PCI::HardwareID id = pci_device_identifier.hardware_id(); | ||||
|     VERIFY((id.vendor_id == PCI::VendorID::QEMUOld && id.device_id == 0x1111) || (id.vendor_id == PCI::VendorID::VirtualBox && id.device_id == 0xbeef)); | ||||
|     return adopt_ref(*new BochsGraphicsAdapter(pci_device_identifier)); | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::set_framebuffer_to_big_endian_format() | ||||
| { | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsGraphicsAdapter set_framebuffer_to_big_endian_format"); | ||||
|     full_memory_barrier(); | ||||
|     if (m_registers->extension_regs.region_size == 0xFFFFFFFF || m_registers->extension_regs.region_size == 0) | ||||
|         return; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->extension_regs.framebuffer_byteorder = BOCHS_DISPLAY_BIG_ENDIAN; | ||||
|     full_memory_barrier(); | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::set_framebuffer_to_little_endian_format() | ||||
| { | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsGraphicsAdapter set_framebuffer_to_little_endian_format"); | ||||
|     full_memory_barrier(); | ||||
|     if (m_registers->extension_regs.region_size == 0xFFFFFFFF || m_registers->extension_regs.region_size == 0) | ||||
|         return; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->extension_regs.framebuffer_byteorder = BOCHS_DISPLAY_LITTLE_ENDIAN; | ||||
|     full_memory_barrier(); | ||||
|     auto adapter = adopt_ref(*new BochsGraphicsAdapter(pci_device_identifier)); | ||||
|     MUST(adapter->initialize_adapter(pci_device_identifier)); | ||||
|     return adapter; | ||||
| } | ||||
| 
 | ||||
| UNMAP_AFTER_INIT BochsGraphicsAdapter::BochsGraphicsAdapter(PCI::DeviceIdentifier const& pci_device_identifier) | ||||
|     : PCI::Device(pci_device_identifier.address()) | ||||
|     , m_mmio_registers(PCI::get_BAR2(pci_device_identifier.address()) & 0xfffffff0) | ||||
|     , m_registers(Memory::map_typed_writable<BochsDisplayMMIORegisters volatile>(m_mmio_registers).release_value_but_fixme_should_propagate_errors()) | ||||
| { | ||||
|     // We assume safe resolution is 1024x768x32
 | ||||
|     m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(PhysicalAddress(PCI::get_BAR0(pci_device_identifier.address()) & 0xfffffff0), 1024, 768, 1024 * sizeof(u32)); | ||||
|     GraphicsManagement::the().set_console(*m_framebuffer_console); | ||||
| 
 | ||||
|     auto vendor_id = pci_device_identifier.hardware_id().vendor_id; | ||||
|     auto device_id = pci_device_identifier.hardware_id().device_id; | ||||
|     auto revision_id = pci_device_identifier.revision_id(); | ||||
| 
 | ||||
|     auto is_bochs = vendor_id == PCI::VendorID::QEMUOld && device_id == 0x1111 && revision_id == 0; | ||||
|     auto is_virtualbox = vendor_id == PCI::VendorID::VirtualBox && device_id == 0xbeef; | ||||
| 
 | ||||
|     if (is_bochs || is_virtualbox) | ||||
|         m_io_required = true; | ||||
| 
 | ||||
|     if (pci_device_identifier.class_code().value() == 0x3 && pci_device_identifier.subclass_code().value() == 0x0) | ||||
|         m_is_vga_capable = true; | ||||
| } | ||||
| 
 | ||||
| UNMAP_AFTER_INIT ErrorOr<void> BochsGraphicsAdapter::initialize_adapter(PCI::DeviceIdentifier const& pci_device_identifier) | ||||
| { | ||||
|     // Note: If we use VirtualBox graphics adapter (which is based on Bochs one), we need to use IO ports
 | ||||
|     // Note: Bochs (the real bochs graphics adapter in the Bochs emulator) uses revision ID of 0x0
 | ||||
|     // and doesn't support memory-mapped IO registers.
 | ||||
|     if (pci_device_identifier.revision_id().value() == 0x0 | ||||
|         || (pci_device_identifier.hardware_id().vendor_id == 0x80ee && pci_device_identifier.hardware_id().device_id == 0xbeef)) { | ||||
|         m_display_connector = BochsDisplayConnector::must_create(PhysicalAddress(PCI::get_BAR0(pci_device_identifier.address()) & 0xfffffff0)); | ||||
|     } else { | ||||
|         auto registers_mapping = TRY(Memory::map_typed_writable<BochsDisplayMMIORegisters volatile>(PhysicalAddress(PCI::get_BAR2(pci_device_identifier.address()) & 0xfffffff0))); | ||||
|         VERIFY(registers_mapping.region); | ||||
|         m_display_connector = QEMUDisplayConnector::must_create(PhysicalAddress(PCI::get_BAR0(pci_device_identifier.address()) & 0xfffffff0), move(registers_mapping)); | ||||
|     } | ||||
| 
 | ||||
|     // Note: According to Gerd Hoffmann - "The linux driver simply does
 | ||||
|     // the unblank unconditionally. With bochs-display this is not needed but
 | ||||
|     // it also has no bad side effect".
 | ||||
|     unblank(); | ||||
|     set_safe_resolution(); | ||||
| } | ||||
|     // FIXME: If the error is ENOTIMPL, ignore it for now until we implement
 | ||||
|     // unblank support for VBoxDisplayConnector class too.
 | ||||
|     auto result = m_display_connector->unblank(); | ||||
|     if (result.is_error() && result.error().code() != ENOTIMPL) | ||||
|         return result; | ||||
| 
 | ||||
| UNMAP_AFTER_INIT void BochsGraphicsAdapter::initialize_framebuffer_devices() | ||||
| { | ||||
|     // FIXME: Find a better way to determine default resolution...
 | ||||
|     m_framebuffer_device = FramebufferDevice::create(*this, PhysicalAddress(PCI::get_BAR0(pci_address()) & 0xfffffff0), 1024, 768, 1024 * sizeof(u32)); | ||||
|     // While write-combine helps greatly on actual hardware, it greatly reduces performance in QEMU
 | ||||
|     m_framebuffer_device->enable_write_combine(false); | ||||
|     // FIXME: Would be nice to be able to return a ErrorOr<void> here.
 | ||||
|     VERIFY(!m_framebuffer_device->try_to_initialize().is_error()); | ||||
|     TRY(m_display_connector->set_safe_mode_setting()); | ||||
| 
 | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| bool BochsGraphicsAdapter::vga_compatible() const | ||||
|  | @ -91,151 +71,11 @@ bool BochsGraphicsAdapter::vga_compatible() const | |||
|     return m_is_vga_capable; | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::unblank() | ||||
| { | ||||
|     full_memory_barrier(); | ||||
|     m_registers->vga_ioports[0] = 0x20; | ||||
|     full_memory_barrier(); | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::set_safe_resolution() | ||||
| { | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     auto result = try_to_set_resolution(0, 1024, 768); | ||||
|     VERIFY(result); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| BochsGraphicsAdapter::IndexID BochsGraphicsAdapter::index_id() const | ||||
| { | ||||
|     if (m_io_required) { | ||||
|         return get_register_with_io(0); | ||||
|     } | ||||
|     return m_registers->bochs_regs.index_id; | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::set_resolution_registers_via_io(size_t width, size_t height) | ||||
| { | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsGraphicsAdapter resolution registers set to - {}x{}", width, height); | ||||
| 
 | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::ENABLE), 0); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::XRES), (u16)width); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::YRES), (u16)height); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::VIRT_WIDTH), (u16)width); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::VIRT_HEIGHT), (u16)height * 2); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::BPP), 32); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::ENABLE), to_underlying(BochsFramebufferSettings::Enabled) | to_underlying(BochsFramebufferSettings::LinearFramebuffer)); | ||||
|     set_register_with_io(to_underlying(BochsDISPIRegisters::BANK), 0); | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::set_resolution_registers(size_t width, size_t height) | ||||
| { | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsGraphicsAdapter resolution registers set to - {}x{}", width, height); | ||||
|     m_registers->bochs_regs.enable = 0; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.xres = width; | ||||
|     m_registers->bochs_regs.yres = height; | ||||
|     m_registers->bochs_regs.virt_width = width; | ||||
|     m_registers->bochs_regs.virt_height = height * 2; | ||||
|     m_registers->bochs_regs.bpp = 32; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.enable = to_underlying(BochsFramebufferSettings::Enabled) | to_underlying(BochsFramebufferSettings::LinearFramebuffer); | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.bank = 0; | ||||
|     if (index_id().value() == VBE_DISPI_ID5) { | ||||
|         set_framebuffer_to_little_endian_format(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool BochsGraphicsAdapter::try_to_set_resolution(size_t output_port_index, size_t width, size_t height) | ||||
| { | ||||
|     // Note: There's only one output port for this adapter
 | ||||
|     VERIFY(output_port_index == 0); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     if (Checked<size_t>::multiplication_would_overflow(width, height, sizeof(u32))) | ||||
|         return false; | ||||
| 
 | ||||
|     if (m_io_required) | ||||
|         set_resolution_registers_via_io(width, height); | ||||
|     else | ||||
|         set_resolution_registers(width, height); | ||||
|     dbgln_if(BXVGA_DEBUG, "BochsGraphicsAdapter resolution test - {}x{}", width, height); | ||||
|     if (m_io_required) { | ||||
|         if (!validate_setup_resolution_with_io(width, height)) | ||||
|             return false; | ||||
|     } else { | ||||
|         if (!validate_setup_resolution(width, height)) | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
|     dbgln("BochsGraphicsAdapter: resolution set to {}x{}", width, height); | ||||
|     m_framebuffer_console->set_resolution(width, height, width * sizeof(u32)); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BochsGraphicsAdapter::validate_setup_resolution_with_io(size_t width, size_t height) | ||||
| { | ||||
|     if ((u16)width != get_register_with_io(to_underlying(BochsDISPIRegisters::XRES)) || (u16)height != get_register_with_io(to_underlying(BochsDISPIRegisters::YRES))) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BochsGraphicsAdapter::validate_setup_resolution(size_t width, size_t height) | ||||
| { | ||||
|     if ((u16)width != m_registers->bochs_regs.xres || (u16)height != m_registers->bochs_regs.yres) { | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BochsGraphicsAdapter::set_y_offset(size_t output_port_index, size_t y_offset) | ||||
| { | ||||
|     VERIFY(output_port_index == 0); | ||||
|     if (m_console_enabled) | ||||
|         return false; | ||||
|     m_registers->bochs_regs.y_offset = y_offset; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void BochsGraphicsAdapter::enable_consoles() | ||||
| { | ||||
|     SpinlockLocker lock(m_console_mode_switch_lock); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     m_console_enabled = true; | ||||
|     m_registers->bochs_regs.y_offset = 0; | ||||
|     if (m_framebuffer_device) | ||||
|         m_framebuffer_device->deactivate_writes(); | ||||
|     m_framebuffer_console->enable(); | ||||
| } | ||||
| void BochsGraphicsAdapter::disable_consoles() | ||||
| { | ||||
|     SpinlockLocker lock(m_console_mode_switch_lock); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     VERIFY(m_framebuffer_device); | ||||
|     m_console_enabled = false; | ||||
|     m_registers->bochs_regs.y_offset = 0; | ||||
|     m_framebuffer_console->disable(); | ||||
|     m_framebuffer_device->activate_writes(); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<ByteBuffer> BochsGraphicsAdapter::get_edid(size_t output_port_index) const | ||||
| { | ||||
|     if (output_port_index != 0) | ||||
|         return Error::from_errno(ENODEV); | ||||
| 
 | ||||
|     return ByteBuffer::copy(const_cast<u8 const*>(m_registers->edid_data), sizeof(m_registers->edid_data)); | ||||
|     return m_display_connector->get_edid(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -20,18 +20,17 @@ namespace Kernel { | |||
| class GraphicsManagement; | ||||
| struct BochsDisplayMMIORegisters; | ||||
| 
 | ||||
| class BochsDisplayConnector; | ||||
| class BochsGraphicsAdapter final : public GenericGraphicsAdapter | ||||
|     , public PCI::Device { | ||||
|     friend class GraphicsManagement; | ||||
| 
 | ||||
| private: | ||||
|     TYPEDEF_DISTINCT_ORDERED_ID(u16, IndexID); | ||||
| 
 | ||||
| public: | ||||
|     static NonnullRefPtr<BochsGraphicsAdapter> initialize(PCI::DeviceIdentifier const&); | ||||
|     virtual ~BochsGraphicsAdapter() = default; | ||||
|     virtual bool framebuffer_devices_initialized() const override { return !m_framebuffer_device.is_null(); } | ||||
| 
 | ||||
|     // FIXME: Remove all of these methods when we get rid of the FramebufferDevice class.
 | ||||
|     virtual bool framebuffer_devices_initialized() const override { return false; } | ||||
|     virtual bool modesetting_capable() const override { return true; } | ||||
|     virtual bool double_framebuffering_capable() const override { return true; } | ||||
| 
 | ||||
|  | @ -40,39 +39,19 @@ public: | |||
| private: | ||||
|     ErrorOr<ByteBuffer> get_edid(size_t output_port_index) const override; | ||||
| 
 | ||||
|     ErrorOr<void> initialize_adapter(PCI::DeviceIdentifier const&); | ||||
| 
 | ||||
|     // ^GenericGraphicsAdapter
 | ||||
|     virtual bool try_to_set_resolution(size_t output_port_index, size_t width, size_t height) override; | ||||
|     virtual bool set_y_offset(size_t output_port_index, size_t y) override; | ||||
| 
 | ||||
|     virtual void initialize_framebuffer_devices() override; | ||||
| 
 | ||||
|     virtual void enable_consoles() override; | ||||
|     virtual void disable_consoles() override; | ||||
|     // FIXME: Remove all of these methods when we get rid of the FramebufferDevice class.
 | ||||
|     virtual bool try_to_set_resolution(size_t, size_t, size_t) override { VERIFY_NOT_REACHED(); } | ||||
|     virtual bool set_y_offset(size_t, size_t) override { VERIFY_NOT_REACHED(); } | ||||
|     virtual void initialize_framebuffer_devices() override { } | ||||
|     virtual void enable_consoles() override { } | ||||
|     virtual void disable_consoles() override { } | ||||
| 
 | ||||
|     explicit BochsGraphicsAdapter(PCI::DeviceIdentifier const&); | ||||
| 
 | ||||
|     IndexID index_id() const; | ||||
| 
 | ||||
|     void set_safe_resolution(); | ||||
|     void unblank(); | ||||
| 
 | ||||
|     bool validate_setup_resolution(size_t width, size_t height); | ||||
|     u32 find_framebuffer_address(); | ||||
|     void set_resolution_registers(size_t width, size_t height); | ||||
|     void set_resolution_registers_via_io(size_t width, size_t height); | ||||
|     bool validate_setup_resolution_with_io(size_t width, size_t height); | ||||
|     void set_y_offset(size_t); | ||||
| 
 | ||||
|     void set_framebuffer_to_big_endian_format(); | ||||
|     void set_framebuffer_to_little_endian_format(); | ||||
| 
 | ||||
|     PhysicalAddress m_mmio_registers; | ||||
|     Memory::TypedMapping<BochsDisplayMMIORegisters volatile> m_registers; | ||||
|     RefPtr<FramebufferDevice> m_framebuffer_device; | ||||
|     RefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console; | ||||
|     Spinlock m_console_mode_switch_lock; | ||||
|     bool m_console_enabled { false }; | ||||
|     bool m_io_required { false }; | ||||
|     RefPtr<BochsDisplayConnector> m_display_connector; | ||||
|     bool m_is_vga_capable { false }; | ||||
| }; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										139
									
								
								Kernel/Graphics/Bochs/QEMUDisplayConnector.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								Kernel/Graphics/Bochs/QEMUDisplayConnector.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,139 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include <Kernel/Debug.h> | ||||
| #include <Kernel/Devices/DeviceManagement.h> | ||||
| #include <Kernel/Graphics/Bochs/QEMUDisplayConnector.h> | ||||
| #include <Kernel/Graphics/GraphicsManagement.h> | ||||
| #include <LibEDID/Definitions.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| NonnullRefPtr<QEMUDisplayConnector> QEMUDisplayConnector::must_create(PhysicalAddress framebuffer_address, Memory::TypedMapping<BochsDisplayMMIORegisters volatile> registers_mapping) | ||||
| { | ||||
|     auto device_or_error = DeviceManagement::try_create_device<QEMUDisplayConnector>(framebuffer_address, move(registers_mapping)); | ||||
|     VERIFY(!device_or_error.is_error()); | ||||
|     auto connector = device_or_error.release_value(); | ||||
|     MUST(connector->create_attached_framebuffer_console()); | ||||
|     MUST(connector->fetch_and_initialize_edid()); | ||||
|     return connector; | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> QEMUDisplayConnector::fetch_and_initialize_edid() | ||||
| { | ||||
|     Array<u8, 128> bochs_edid; | ||||
|     static_assert(sizeof(BochsDisplayMMIORegisters::edid_data) >= sizeof(EDID::Definitions::EDID)); | ||||
|     memcpy(bochs_edid.data(), (u8 const*)(m_registers.base_address().offset(__builtin_offsetof(BochsDisplayMMIORegisters, edid_data)).as_ptr()), sizeof(bochs_edid)); | ||||
|     set_edid_bytes(bochs_edid); | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| QEMUDisplayConnector::QEMUDisplayConnector(PhysicalAddress framebuffer_address, Memory::TypedMapping<BochsDisplayMMIORegisters volatile> registers_mapping) | ||||
|     : BochsDisplayConnector(framebuffer_address) | ||||
|     , m_registers(move(registers_mapping)) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| QEMUDisplayConnector::IndexID QEMUDisplayConnector::index_id() const | ||||
| { | ||||
|     return m_registers->bochs_regs.index_id; | ||||
| } | ||||
| 
 | ||||
| void QEMUDisplayConnector::set_framebuffer_to_big_endian_format() | ||||
| { | ||||
|     VERIFY(m_modeset_lock.is_locked()); | ||||
|     dbgln_if(BXVGA_DEBUG, "QEMUDisplayConnector set_framebuffer_to_big_endian_format"); | ||||
|     full_memory_barrier(); | ||||
|     if (m_registers->extension_regs.region_size == 0xFFFFFFFF || m_registers->extension_regs.region_size == 0) | ||||
|         return; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->extension_regs.framebuffer_byteorder = BOCHS_DISPLAY_BIG_ENDIAN; | ||||
|     full_memory_barrier(); | ||||
| } | ||||
| 
 | ||||
| void QEMUDisplayConnector::set_framebuffer_to_little_endian_format() | ||||
| { | ||||
|     VERIFY(m_modeset_lock.is_locked()); | ||||
|     dbgln_if(BXVGA_DEBUG, "QEMUDisplayConnector set_framebuffer_to_little_endian_format"); | ||||
|     full_memory_barrier(); | ||||
|     if (m_registers->extension_regs.region_size == 0xFFFFFFFF || m_registers->extension_regs.region_size == 0) | ||||
|         return; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->extension_regs.framebuffer_byteorder = BOCHS_DISPLAY_LITTLE_ENDIAN; | ||||
|     full_memory_barrier(); | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> QEMUDisplayConnector::unblank() | ||||
| { | ||||
|     SpinlockLocker locker(m_modeset_lock); | ||||
|     full_memory_barrier(); | ||||
|     m_registers->vga_ioports[0] = 0x20; | ||||
|     full_memory_barrier(); | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> QEMUDisplayConnector::set_y_offset(size_t y_offset) | ||||
| { | ||||
|     VERIFY(m_modeset_lock.is_locked()); | ||||
|     m_registers->bochs_regs.y_offset = y_offset; | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| ErrorOr<void> QEMUDisplayConnector::set_mode_setting(ModeSetting const& mode_setting) | ||||
| { | ||||
|     SpinlockLocker locker(m_modeset_lock); | ||||
|     VERIFY(m_framebuffer_console); | ||||
|     size_t width = mode_setting.horizontal_active; | ||||
|     size_t height = mode_setting.vertical_active; | ||||
| 
 | ||||
|     if (Checked<size_t>::multiplication_would_overflow(width, height, sizeof(u32))) | ||||
|         return EOVERFLOW; | ||||
| 
 | ||||
|     dbgln_if(BXVGA_DEBUG, "QEMUDisplayConnector resolution registers set to - {}x{}", width, height); | ||||
|     m_registers->bochs_regs.enable = 0; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.xres = width; | ||||
|     m_registers->bochs_regs.yres = height; | ||||
|     m_registers->bochs_regs.virt_width = width; | ||||
|     m_registers->bochs_regs.virt_height = height * 2; | ||||
|     m_registers->bochs_regs.bpp = 32; | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.enable = to_underlying(BochsFramebufferSettings::Enabled) | to_underlying(BochsFramebufferSettings::LinearFramebuffer); | ||||
|     full_memory_barrier(); | ||||
|     m_registers->bochs_regs.bank = 0; | ||||
|     if (index_id().value() == VBE_DISPI_ID5) { | ||||
|         set_framebuffer_to_little_endian_format(); | ||||
|     } | ||||
| 
 | ||||
|     if ((u16)width != m_registers->bochs_regs.xres || (u16)height != m_registers->bochs_regs.yres) { | ||||
|         return Error::from_errno(ENOTIMPL); | ||||
|     } | ||||
|     auto rounded_size = TRY(Memory::page_round_up(width * sizeof(u32) * height * 2)); | ||||
|     m_framebuffer_region = TRY(MM.allocate_kernel_region(m_framebuffer_address.page_base(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite)); | ||||
|     [[maybe_unused]] auto result = m_framebuffer_region->set_write_combine(true); | ||||
|     m_framebuffer_data = m_framebuffer_region->vaddr().offset(m_framebuffer_address.offset_in_page()).as_ptr(); | ||||
|     m_framebuffer_console->set_resolution(width, height, width * sizeof(u32)); | ||||
| 
 | ||||
|     DisplayConnector::ModeSetting mode_set { | ||||
|         .horizontal_stride = m_registers->bochs_regs.xres * sizeof(u32), | ||||
|         .pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
 | ||||
|         .horizontal_active = m_registers->bochs_regs.xres, | ||||
|         .horizontal_front_porch_pixels = 0, // Note: There's no horizontal_front_porch_pixels in paravirtualized hardware
 | ||||
|         .horizontal_sync_time_pixels = 0,   // Note: There's no horizontal_sync_time_pixels in paravirtualized hardware
 | ||||
|         .horizontal_blank_pixels = 0,       // Note: There's no horizontal_blank_pixels in paravirtualized hardware
 | ||||
|         .vertical_active = m_registers->bochs_regs.yres, | ||||
|         .vertical_front_porch_lines = 0, // Note: There's no vertical_front_porch_lines in paravirtualized hardware
 | ||||
|         .vertical_sync_time_lines = 0,   // Note: There's no vertical_sync_time_lines in paravirtualized hardware
 | ||||
|         .vertical_blank_lines = 0,       // Note: There's no vertical_blank_lines in paravirtualized hardware
 | ||||
|         .horizontal_offset = 0, | ||||
|         .vertical_offset = 0, | ||||
|     }; | ||||
| 
 | ||||
|     m_current_mode_setting = mode_set; | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										44
									
								
								Kernel/Graphics/Bochs/QEMUDisplayConnector.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Kernel/Graphics/Bochs/QEMUDisplayConnector.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> | ||||
|  * | ||||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <AK/RefPtr.h> | ||||
| #include <AK/Try.h> | ||||
| #include <Kernel/Graphics/Bochs/DisplayConnector.h> | ||||
| #include <Kernel/Graphics/Console/GenericFramebufferConsole.h> | ||||
| #include <Kernel/Locking/Spinlock.h> | ||||
| #include <Kernel/Memory/TypedMapping.h> | ||||
| 
 | ||||
| namespace Kernel { | ||||
| 
 | ||||
| struct BochsDisplayMMIORegisters; | ||||
| class QEMUDisplayConnector final | ||||
|     : public BochsDisplayConnector { | ||||
|     friend class BochsGraphicsAdapter; | ||||
|     friend class DeviceManagement; | ||||
| 
 | ||||
| public: | ||||
|     static NonnullRefPtr<QEMUDisplayConnector> must_create(PhysicalAddress framebuffer_address, Memory::TypedMapping<BochsDisplayMMIORegisters volatile>); | ||||
| 
 | ||||
|     virtual IndexID index_id() const override; | ||||
| 
 | ||||
| private: | ||||
|     ErrorOr<void> fetch_and_initialize_edid(); | ||||
|     QEMUDisplayConnector(PhysicalAddress framebuffer_address, Memory::TypedMapping<BochsDisplayMMIORegisters volatile>); | ||||
| 
 | ||||
|     virtual bool double_framebuffering_capable() const override { return true; } | ||||
|     virtual ErrorOr<void> set_mode_setting(ModeSetting const&) override; | ||||
|     virtual ErrorOr<void> set_y_offset(size_t y) override; | ||||
|     virtual ErrorOr<void> unblank() override; | ||||
| 
 | ||||
|     void set_framebuffer_to_big_endian_format(); | ||||
|     void set_framebuffer_to_little_endian_format(); | ||||
| 
 | ||||
| private: | ||||
|     Memory::TypedMapping<BochsDisplayMMIORegisters volatile> m_registers; | ||||
| }; | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Liav A
						Liav A