summaryrefslogtreecommitdiff
path: root/Kernel/Graphics
diff options
context:
space:
mode:
authorLiav A <liavalb@gmail.com>2022-03-18 16:46:55 +0200
committerLinus Groh <mail@linusgroh.de>2022-05-06 18:04:57 +0200
commit340773ddb7058ddad59784fa946a5fd33612f6f9 (patch)
treef7780a2b30de0944ef39f22d80158fea8b0ac849 /Kernel/Graphics
parent530aa51816e574601ab8366d1d52fbbdd427b35a (diff)
downloadserenity-340773ddb7058ddad59784fa946a5fd33612f6f9.zip
Kernel/Graphics: Implement basic support for VMWare SVGA adapter
Diffstat (limited to 'Kernel/Graphics')
-rw-r--r--Kernel/Graphics/DisplayConnector.cpp2
-rw-r--r--Kernel/Graphics/GraphicsManagement.cpp4
-rw-r--r--Kernel/Graphics/VMWare/Console.cpp69
-rw-r--r--Kernel/Graphics/VMWare/Console.h32
-rw-r--r--Kernel/Graphics/VMWare/Definitions.h61
-rw-r--r--Kernel/Graphics/VMWare/DisplayConnector.cpp152
-rw-r--r--Kernel/Graphics/VMWare/DisplayConnector.h60
-rw-r--r--Kernel/Graphics/VMWare/GraphicsAdapter.cpp194
-rw-r--r--Kernel/Graphics/VMWare/GraphicsAdapter.h61
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 };
+};
+
+}