diff options
author | Liav A <liavalb@gmail.com> | 2022-03-18 16:46:55 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-05-06 18:04:57 +0200 |
commit | 340773ddb7058ddad59784fa946a5fd33612f6f9 (patch) | |
tree | f7780a2b30de0944ef39f22d80158fea8b0ac849 /Kernel/Graphics | |
parent | 530aa51816e574601ab8366d1d52fbbdd427b35a (diff) | |
download | serenity-340773ddb7058ddad59784fa946a5fd33612f6f9.zip |
Kernel/Graphics: Implement basic support for VMWare SVGA adapter
Diffstat (limited to 'Kernel/Graphics')
-rw-r--r-- | Kernel/Graphics/DisplayConnector.cpp | 2 | ||||
-rw-r--r-- | Kernel/Graphics/GraphicsManagement.cpp | 4 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/Console.cpp | 69 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/Console.h | 32 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/Definitions.h | 61 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/DisplayConnector.cpp | 152 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/DisplayConnector.h | 60 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/GraphicsAdapter.cpp | 194 | ||||
-rw-r--r-- | Kernel/Graphics/VMWare/GraphicsAdapter.h | 61 |
9 files changed, 635 insertions, 0 deletions
diff --git a/Kernel/Graphics/DisplayConnector.cpp b/Kernel/Graphics/DisplayConnector.cpp index 939b563d59..8186804f6d 100644 --- a/Kernel/Graphics/DisplayConnector.cpp +++ b/Kernel/Graphics/DisplayConnector.cpp @@ -293,6 +293,8 @@ ErrorOr<void> DisplayConnector::ioctl(OpenFileDescription&, unsigned request, Us return copy_to_user(user_head_vertical_buffer_offset, &head_vertical_buffer_offset); } case GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS: { + if (console_mode()) + return {}; if (!partial_flush_support()) return Error::from_errno(ENOTSUP); MutexLocker locker(m_flushing_lock); diff --git a/Kernel/Graphics/GraphicsManagement.cpp b/Kernel/Graphics/GraphicsManagement.cpp index 8f966ee0be..50ff928cdb 100644 --- a/Kernel/Graphics/GraphicsManagement.cpp +++ b/Kernel/Graphics/GraphicsManagement.cpp @@ -16,6 +16,7 @@ #include <Kernel/Graphics/VGA/ISAAdapter.h> #include <Kernel/Graphics/VGA/PCIAdapter.h> #include <Kernel/Graphics/VGA/VGACompatibleAdapter.h> +#include <Kernel/Graphics/VMWare/GraphicsAdapter.h> #include <Kernel/Graphics/VirtIOGPU/GraphicsAdapter.h> #include <Kernel/Memory/AnonymousVMObject.h> #include <Kernel/Multiboot.h> @@ -167,6 +168,9 @@ UNMAP_AFTER_INIT bool GraphicsManagement::determine_and_initialize_graphics_devi dmesgln("Graphics: Using VirtIO console"); adapter = VirtIOGraphicsAdapter::initialize(device_identifier); break; + case PCI::VendorID::VMWare: + adapter = VMWareGraphicsAdapter::try_initialize(device_identifier); + break; default: if (!is_vga_compatible_pci_device(device_identifier)) break; diff --git a/Kernel/Graphics/VMWare/Console.cpp b/Kernel/Graphics/VMWare/Console.cpp new file mode 100644 index 0000000000..badef5d531 --- /dev/null +++ b/Kernel/Graphics/VMWare/Console.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <Kernel/Graphics/VMWare/Console.h> +#include <Kernel/WorkQueue.h> + +namespace Kernel { + +constexpr static AK::Time refresh_interval = AK::Time::from_milliseconds(16); + +NonnullRefPtr<VMWareFramebufferConsole> VMWareFramebufferConsole::initialize(VMWareDisplayConnector& parent_display_connector) +{ + auto current_resolution = parent_display_connector.current_mode_setting(); + return adopt_ref(*new (nothrow) VMWareFramebufferConsole(parent_display_connector, current_resolution)); +} + +VMWareFramebufferConsole::VMWareFramebufferConsole(VMWareDisplayConnector const& parent_display_connector, DisplayConnector::ModeSetting current_resolution) + : GenericFramebufferConsole(current_resolution.horizontal_active, current_resolution.vertical_active, current_resolution.horizontal_stride) + , m_parent_display_connector(parent_display_connector) +{ + enqueue_refresh_timer(); +} + +void VMWareFramebufferConsole::set_resolution(size_t width, size_t height, size_t pitch) +{ + m_width = width; + m_height = height; + m_pitch = pitch; + m_dirty = true; +} + +void VMWareFramebufferConsole::flush(size_t, size_t, size_t, size_t) +{ + m_dirty = true; +} + +void VMWareFramebufferConsole::enqueue_refresh_timer() +{ + NonnullRefPtr<Timer> refresh_timer = adopt_ref(*new (nothrow) Timer()); + refresh_timer->setup(CLOCK_MONOTONIC, refresh_interval, [this]() { + if (m_enabled.load() && m_dirty) { + MUST(g_io_work->try_queue([this]() { + MUST(m_parent_display_connector->flush_first_surface()); + m_dirty = false; + })); + } + enqueue_refresh_timer(); + }); + TimerQueue::the().add_timer(move(refresh_timer)); +} + +void VMWareFramebufferConsole::enable() +{ + auto current_resolution = m_parent_display_connector->current_mode_setting(); + GenericFramebufferConsole::enable(); + m_width = current_resolution.horizontal_active; + m_height = current_resolution.vertical_active; + m_pitch = current_resolution.horizontal_stride; +} + +u8* VMWareFramebufferConsole::framebuffer_data() +{ + return m_parent_display_connector->framebuffer_data(); +} + +} diff --git a/Kernel/Graphics/VMWare/Console.h b/Kernel/Graphics/VMWare/Console.h new file mode 100644 index 0000000000..cb095976c8 --- /dev/null +++ b/Kernel/Graphics/VMWare/Console.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <Kernel/Graphics/Console/GenericFramebufferConsole.h> +#include <Kernel/Graphics/VMWare/DisplayConnector.h> +#include <Kernel/TimerQueue.h> + +namespace Kernel { + +class VMWareFramebufferConsole final : public Graphics::GenericFramebufferConsole { +public: + static NonnullRefPtr<VMWareFramebufferConsole> initialize(VMWareDisplayConnector& parent_display_connector); + + virtual void set_resolution(size_t width, size_t height, size_t pitch) override; + virtual void flush(size_t x, size_t y, size_t width, size_t height) override; + virtual void enable() override; + +private: + void enqueue_refresh_timer(); + virtual u8* framebuffer_data() override; + + VMWareFramebufferConsole(VMWareDisplayConnector const& parent_display_connector, DisplayConnector::ModeSetting current_resolution); + RefPtr<VMWareDisplayConnector> m_parent_display_connector; + bool m_dirty { false }; +}; + +} diff --git a/Kernel/Graphics/VMWare/Definitions.h b/Kernel/Graphics/VMWare/Definitions.h new file mode 100644 index 0000000000..dfbdb8769d --- /dev/null +++ b/Kernel/Graphics/VMWare/Definitions.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Types.h> + +namespace Kernel { + +static constexpr size_t vmware_svga_version_2_id = (0x900000UL << 8 | (2)); + +enum class VMWareDisplayRegistersOffset { + ID = 0, + ENABLE = 1, + WIDTH = 2, + HEIGHT = 3, + MAX_WIDTH = 4, + MAX_HEIGHT = 5, + DEPTH = 6, + BITS_PER_PIXEL = 7, /* Current bpp in the guest */ + PSEUDOCOLOR = 8, + RED_MASK = 9, + GREEN_MASK = 10, + BLUE_MASK = 11, + BYTES_PER_LINE = 12, + FB_OFFSET = 14, + VRAM_SIZE = 15, + FB_SIZE = 16, + + CAPABILITIES = 17, + MEM_SIZE = 19, + CONFIG_DONE = 20, /* Set when memory area configured */ + SYNC = 21, /* See "FIFO Synchronization Registers" */ + BUSY = 22, /* See "FIFO Synchronization Registers" */ + SCRATCH_SIZE = 29, /* Number of scratch registers */ + MEM_REGS = 30, /* Number of FIFO registers */ + PITCHLOCK = 32, /* Fixed pitch for all modes */ + IRQMASK = 33, /* Interrupt mask */ + + GMR_ID = 41, + GMR_DESCRIPTOR = 42, + GMR_MAX_IDS = 43, + GMR_MAX_DESCRIPTOR_LENGTH = 44, + + TRACES = 45, /* Enable trace-based updates even when FIFO is on */ + GMRS_MAX_PAGES = 46, /* Maximum number of 4KB pages for all GMRs */ + MEMORY_SIZE = 47, /* Total dedicated device memory excluding FIFO */ +}; + +struct [[gnu::packed]] VMWareDisplayFIFORegisters { + u32 start; + u32 size; + u32 next_command; + u32 stop; + u32 commands[]; +}; + +} diff --git a/Kernel/Graphics/VMWare/DisplayConnector.cpp b/Kernel/Graphics/VMWare/DisplayConnector.cpp new file mode 100644 index 0000000000..a5ee870de4 --- /dev/null +++ b/Kernel/Graphics/VMWare/DisplayConnector.cpp @@ -0,0 +1,152 @@ +/* + * 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/GraphicsManagement.h> +#include <Kernel/Graphics/VMWare/Console.h> +#include <Kernel/Graphics/VMWare/DisplayConnector.h> + +namespace Kernel { + +NonnullRefPtr<VMWareDisplayConnector> VMWareDisplayConnector::must_create(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address) +{ + auto connector = MUST(DeviceManagement::try_create_device<VMWareDisplayConnector>(parent_adapter, framebuffer_address)); + MUST(connector->create_attached_framebuffer_console()); + MUST(connector->initialize_edid_for_generic_monitor()); + return connector; +} + +ErrorOr<void> VMWareDisplayConnector::create_attached_framebuffer_console() +{ + auto rounded_size = TRY(Memory::page_round_up(1024 * sizeof(u32) * 768)); + 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 = VMWareFramebufferConsole::initialize(*this); + GraphicsManagement::the().set_console(*m_framebuffer_console); + return {}; +} + +VMWareDisplayConnector::VMWareDisplayConnector(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address) + : DisplayConnector() + , m_framebuffer_address(framebuffer_address) + , m_parent_adapter(parent_adapter) +{ +} + +ErrorOr<void> VMWareDisplayConnector::set_safe_mode_setting() +{ + // We assume safe resolution is 1024x768x32 + DisplayConnector::ModeSetting safe_mode_setting { + .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_setting); +} + +ErrorOr<void> VMWareDisplayConnector::unblank() +{ + return Error::from_errno(ENOTIMPL); +} + +ErrorOr<size_t> VMWareDisplayConnector::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 VMWareDisplayConnector::enable_console() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_framebuffer_console); + m_framebuffer_console->enable(); +} + +void VMWareDisplayConnector::disable_console() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_framebuffer_console); + m_framebuffer_console->disable(); +} + +ErrorOr<void> VMWareDisplayConnector::flush_first_surface() +{ + // FIXME: Cache these values but keep them in sync with the parent adapter. + auto width = m_parent_adapter->primary_screen_width({}); + auto height = m_parent_adapter->primary_screen_height({}); + m_parent_adapter->primary_screen_flush({}, width, height); + return {}; +} + +ErrorOr<void> VMWareDisplayConnector::set_y_offset(size_t) +{ + return Error::from_errno(ENOTSUP); +} + +ErrorOr<void> VMWareDisplayConnector::flush_rectangle(size_t, FBRect const&) +{ + // FIXME: It costs really nothing to flush the entire screen (at least in QEMU). + // Try to implement better partial rectangle flush method instead here. + VERIFY(m_flushing_lock.is_locked()); + // FIXME: Cache these values but keep them in sync with the parent adapter. + auto width = m_parent_adapter->primary_screen_width({}); + auto height = m_parent_adapter->primary_screen_height({}); + m_parent_adapter->primary_screen_flush({}, width, height); + return {}; +} + +ErrorOr<void> VMWareDisplayConnector::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; + + TRY(m_parent_adapter->modeset_primary_screen_resolution({}, width, height)); + + auto rounded_size = TRY(Memory::page_round_up(width * sizeof(u32) * height)); + 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)); + + auto pitch = m_parent_adapter->primary_screen_pitch({}); + DisplayConnector::ModeSetting current_mode_setting { + .horizontal_stride = pitch, + .pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware + .horizontal_active = width, + .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 = height, + .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 = current_mode_setting; + return {}; +} + +} diff --git a/Kernel/Graphics/VMWare/DisplayConnector.h b/Kernel/Graphics/VMWare/DisplayConnector.h new file mode 100644 index 0000000000..d17e51b8e3 --- /dev/null +++ b/Kernel/Graphics/VMWare/DisplayConnector.h @@ -0,0 +1,60 @@ +/* + * 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/Console/GenericFramebufferConsole.h> +#include <Kernel/Graphics/DisplayConnector.h> +#include <Kernel/Graphics/VMWare/GraphicsAdapter.h> +#include <Kernel/Locking/Spinlock.h> +#include <Kernel/Memory/TypedMapping.h> + +namespace Kernel { + +class VMWareFramebufferConsole; +class VMWareDisplayConnector : public DisplayConnector { + friend class VMWareGraphicsAdapter; + friend class VMWareFramebufferConsole; + friend class DeviceManagement; + +public: + static NonnullRefPtr<VMWareDisplayConnector> must_create(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address); + +private: + VMWareDisplayConnector(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address); + ErrorOr<void> create_attached_framebuffer_console(); + + virtual bool mutable_mode_setting_capable() const override { 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; + virtual ErrorOr<void> set_y_offset(size_t y) override; + virtual ErrorOr<void> unblank() override; + + virtual bool partial_flush_support() const override { return true; } + virtual bool flush_support() const override { return true; } + // Note: Paravirtualized hardware doesn't require a defined refresh rate for modesetting. + virtual bool refresh_rate_support() const override { return false; } + + virtual ErrorOr<size_t> write_to_first_surface(u64 offset, UserOrKernelBuffer const&, size_t length) override; + virtual ErrorOr<void> flush_first_surface() override; + virtual ErrorOr<void> flush_rectangle(size_t buffer_index, FBRect const& rect) override; + + virtual void enable_console() override; + virtual void disable_console() override; + +private: + u8* framebuffer_data() { return m_framebuffer_data; } + + const PhysicalAddress m_framebuffer_address; + NonnullRefPtr<VMWareGraphicsAdapter> m_parent_adapter; + RefPtr<VMWareFramebufferConsole> m_framebuffer_console; + OwnPtr<Memory::Region> m_framebuffer_region; + u8* m_framebuffer_data {}; +}; +} diff --git a/Kernel/Graphics/VMWare/GraphicsAdapter.cpp b/Kernel/Graphics/VMWare/GraphicsAdapter.cpp new file mode 100644 index 0000000000..debea6043e --- /dev/null +++ b/Kernel/Graphics/VMWare/GraphicsAdapter.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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/Console/ContiguousFramebufferConsole.h> +#include <Kernel/Graphics/GraphicsManagement.h> +#include <Kernel/Graphics/VMWare/Definitions.h> +#include <Kernel/Graphics/VMWare/DisplayConnector.h> +#include <Kernel/Graphics/VMWare/GraphicsAdapter.h> +#include <Kernel/Memory/TypedMapping.h> +#include <Kernel/Sections.h> + +namespace Kernel { + +UNMAP_AFTER_INIT RefPtr<VMWareGraphicsAdapter> VMWareGraphicsAdapter::try_initialize(PCI::DeviceIdentifier const& pci_device_identifier) +{ + PCI::HardwareID id = pci_device_identifier.hardware_id(); + VERIFY(id.vendor_id == PCI::VendorID::VMWare); + // Note: We only support VMWare SVGA II adapter + if (id.device_id != 0x0405) + return {}; + auto adapter = MUST(adopt_nonnull_ref_or_enomem(new (nothrow) VMWareGraphicsAdapter(pci_device_identifier))); + MUST(adapter->initialize_adapter()); + return adapter; +} + +UNMAP_AFTER_INIT VMWareGraphicsAdapter::VMWareGraphicsAdapter(PCI::DeviceIdentifier const& pci_device_identifier) + : PCI::Device(pci_device_identifier.address()) + , m_io_registers_base(PCI::get_BAR0(pci_device_identifier.address()) & 0xfffffff0) +{ + dbgln("VMWare SVGA @ {}, {}", pci_device_identifier.address(), m_io_registers_base); +} + +u32 VMWareGraphicsAdapter::read_io_register(VMWareDisplayRegistersOffset register_offset) const +{ + SpinlockLocker locker(m_io_access_lock); + m_io_registers_base.out<u32>(to_underlying(register_offset)); + return m_io_registers_base.offset(1).in<u32>(); +} +void VMWareGraphicsAdapter::write_io_register(VMWareDisplayRegistersOffset register_offset, u32 value) +{ + SpinlockLocker locker(m_io_access_lock); + m_io_registers_base.out<u32>(to_underlying(register_offset)); + m_io_registers_base.offset(1).out<u32>(value); +} + +UNMAP_AFTER_INIT ErrorOr<void> VMWareGraphicsAdapter::negotiate_device_version() +{ + write_io_register(VMWareDisplayRegistersOffset::ID, vmware_svga_version_2_id); + auto accepted_version = read_io_register(VMWareDisplayRegistersOffset::ID); + dbgln("VMWare SVGA @ {}: Accepted version {}", pci_address(), accepted_version); + if (read_io_register(VMWareDisplayRegistersOffset::ID) == vmware_svga_version_2_id) + return {}; + return Error::from_errno(ENOTSUP); +} + +UNMAP_AFTER_INIT ErrorOr<void> VMWareGraphicsAdapter::initialize_fifo_registers() +{ + auto framebuffer_size = read_io_register(VMWareDisplayRegistersOffset::FB_SIZE); + auto fifo_size = read_io_register(VMWareDisplayRegistersOffset::MEM_SIZE); + auto fifo_physical_address = PhysicalAddress(PCI::get_BAR2(pci_address()) & 0xfffffff0); + + dbgln("VMWare SVGA @ {}: framebuffer size {} bytes, FIFO size {} bytes @ {}", pci_address(), framebuffer_size, fifo_size, fifo_physical_address); + if (framebuffer_size < 0x100000 || fifo_size < 0x10000) { + dbgln("VMWare SVGA @ {}: invalid framebuffer or fifo size", pci_address()); + return Error::from_errno(ENOTSUP); + } + + m_fifo_registers = TRY(Memory::map_typed<volatile VMWareDisplayFIFORegisters>(fifo_physical_address, fifo_size, Memory::Region::Access::ReadWrite)); + m_fifo_registers->start = 16; + m_fifo_registers->size = 16 + (10 * 1024); + m_fifo_registers->next_command = 16; + m_fifo_registers->stop = 16; + return {}; +} + +UNMAP_AFTER_INIT void VMWareGraphicsAdapter::print_svga_capabilities() const +{ + auto svga_capabilities = read_io_register(VMWareDisplayRegistersOffset::CAPABILITIES); + dbgln("VMWare SVGA capabilities (raw {:x}):", svga_capabilities); + if (svga_capabilities & (1 << 1)) + dbgln("\tRect copy"); + if (svga_capabilities & (1 << 5)) + dbgln("\tCursor"); + if (svga_capabilities & (1 << 6)) + dbgln("\tCursor Bypass"); + if (svga_capabilities & (1 << 7)) + dbgln("\tCursor Bypass 2"); + if (svga_capabilities & (1 << 8)) + dbgln("\t8 Bit emulation"); + if (svga_capabilities & (1 << 9)) + dbgln("\tAlpha Cursor"); + if (svga_capabilities & (1 << 14)) + dbgln("\t3D acceleration"); + if (svga_capabilities & (1 << 15)) + dbgln("\tExtended FIFO"); + if (svga_capabilities & (1 << 16)) + dbgln("\tMulti-monitor (legacy)"); + if (svga_capabilities & (1 << 17)) + dbgln("\tPitch lock"); + if (svga_capabilities & (1 << 18)) + dbgln("\tIRQ masking"); + if (svga_capabilities & (1 << 19)) + dbgln("\tDisplay topology"); + if (svga_capabilities & (1 << 20)) + dbgln("\tGMR"); + if (svga_capabilities & (1 << 21)) + dbgln("\tTraces"); + if (svga_capabilities & (1 << 22)) + dbgln("\tGMR2"); + if (svga_capabilities & (1 << 23)) + dbgln("\tScreen object 2"); +} + +ErrorOr<void> VMWareGraphicsAdapter::modeset_primary_screen_resolution(Badge<VMWareDisplayConnector>, size_t width, size_t height) +{ + auto max_width = read_io_register(VMWareDisplayRegistersOffset::MAX_WIDTH); + auto max_height = read_io_register(VMWareDisplayRegistersOffset::MAX_HEIGHT); + if (width > max_width || height > max_height) + return Error::from_errno(ENOTSUP); + modeset_primary_screen_resolution(width, height); + return {}; +} + +size_t VMWareGraphicsAdapter::primary_screen_width(Badge<VMWareDisplayConnector>) const +{ + SpinlockLocker locker(m_operation_lock); + return read_io_register(VMWareDisplayRegistersOffset::WIDTH); +} +size_t VMWareGraphicsAdapter::primary_screen_height(Badge<VMWareDisplayConnector>) const +{ + SpinlockLocker locker(m_operation_lock); + return read_io_register(VMWareDisplayRegistersOffset::HEIGHT); +} +size_t VMWareGraphicsAdapter::primary_screen_pitch(Badge<VMWareDisplayConnector>) const +{ + SpinlockLocker locker(m_operation_lock); + return read_io_register(VMWareDisplayRegistersOffset::BYTES_PER_LINE); +} + +void VMWareGraphicsAdapter::primary_screen_flush(Badge<VMWareDisplayConnector>, size_t current_width, size_t current_height) +{ + SpinlockLocker locker(m_operation_lock); + m_fifo_registers->start = 16; + m_fifo_registers->size = 16 + (10 * 1024); + m_fifo_registers->next_command = 16 + 4 * 5; + m_fifo_registers->stop = 16; + m_fifo_registers->commands[0] = 1; + m_fifo_registers->commands[1] = 0; + m_fifo_registers->commands[2] = 0; + m_fifo_registers->commands[3] = current_width; + m_fifo_registers->commands[4] = current_height; + write_io_register(VMWareDisplayRegistersOffset::SYNC, 1); +} + +void VMWareGraphicsAdapter::modeset_primary_screen_resolution(size_t width, size_t height) +{ + SpinlockLocker locker(m_operation_lock); + write_io_register(VMWareDisplayRegistersOffset::ENABLE, 0); + write_io_register(VMWareDisplayRegistersOffset::WIDTH, width); + write_io_register(VMWareDisplayRegistersOffset::HEIGHT, height); + write_io_register(VMWareDisplayRegistersOffset::BITS_PER_PIXEL, 32); + write_io_register(VMWareDisplayRegistersOffset::ENABLE, 1); + write_io_register(VMWareDisplayRegistersOffset::CONFIG_DONE, 1); +} + +UNMAP_AFTER_INIT ErrorOr<void> VMWareGraphicsAdapter::initialize_adapter() +{ + TRY(negotiate_device_version()); + print_svga_capabilities(); + TRY(initialize_fifo_registers()); + // Note: enable the device by modesetting the primary screen resolution + modeset_primary_screen_resolution(640, 480); + + m_display_connector = VMWareDisplayConnector::must_create(*this, PhysicalAddress(PCI::get_BAR1(pci_address()) & 0xfffffff0)); + TRY(m_display_connector->set_safe_mode_setting()); + return {}; +} + +bool VMWareGraphicsAdapter::vga_compatible() const +{ + return false; +} + +} diff --git a/Kernel/Graphics/VMWare/GraphicsAdapter.h b/Kernel/Graphics/VMWare/GraphicsAdapter.h new file mode 100644 index 0000000000..bbe56483c6 --- /dev/null +++ b/Kernel/Graphics/VMWare/GraphicsAdapter.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Types.h> +#include <Kernel/Arch/x86/IO.h> +#include <Kernel/Bus/PCI/Device.h> +#include <Kernel/Graphics/GenericGraphicsAdapter.h> +#include <Kernel/Graphics/VMWare/Definitions.h> +#include <Kernel/Locking/Spinlock.h> +#include <Kernel/Memory/TypedMapping.h> +#include <Kernel/PhysicalAddress.h> + +namespace Kernel { + +class GraphicsManagement; + +class VMWareDisplayConnector; +class VMWareGraphicsAdapter final + : public GenericGraphicsAdapter + , public PCI::Device { + friend class GraphicsManagement; + +public: + static RefPtr<VMWareGraphicsAdapter> try_initialize(PCI::DeviceIdentifier const&); + virtual ~VMWareGraphicsAdapter() = default; + + virtual bool vga_compatible() const override; + + ErrorOr<void> modeset_primary_screen_resolution(Badge<VMWareDisplayConnector>, size_t width, size_t height); + size_t primary_screen_width(Badge<VMWareDisplayConnector>) const; + size_t primary_screen_height(Badge<VMWareDisplayConnector>) const; + size_t primary_screen_pitch(Badge<VMWareDisplayConnector>) const; + void primary_screen_flush(Badge<VMWareDisplayConnector>, size_t current_width, size_t current_height); + +private: + ErrorOr<void> initialize_adapter(); + ErrorOr<void> initialize_fifo_registers(); + ErrorOr<void> negotiate_device_version(); + + u32 read_io_register(VMWareDisplayRegistersOffset) const; + void write_io_register(VMWareDisplayRegistersOffset, u32 value); + + void print_svga_capabilities() const; + void modeset_primary_screen_resolution(size_t width, size_t height); + + explicit VMWareGraphicsAdapter(PCI::DeviceIdentifier const&); + + Memory::TypedMapping<volatile VMWareDisplayFIFORegisters> m_fifo_registers; + RefPtr<VMWareDisplayConnector> m_display_connector; + const IOAddress m_io_registers_base; + mutable Spinlock m_io_access_lock; + mutable RecursiveSpinlock m_operation_lock; + bool m_is_vga_capable { false }; +}; + +} |