1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 18:37:34 +00:00

Kernel: Move all Graphics-related code into Devices/GPU directory

Like the HID, Audio and Storage subsystem, the Graphics subsystem (which
handles GPUs technically) exposes unix device files (typically in /dev).
To ensure consistency across the repository, move all related files to a
new directory under Kernel/Devices called "GPU".

Also remove the redundant "GPU" word from the VirtIO driver directory,
and the word "Graphics" from GraphicsManagement.{h,cpp} filenames.
This commit is contained in:
Liav A 2023-06-03 14:47:47 +03:00 committed by Jelle Raaijmakers
parent 31a7dabf02
commit 9ee098b119
69 changed files with 167 additions and 167 deletions

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel {
#define VBE_DISPI_IOPORT_INDEX 0x01CE
#define VBE_DISPI_IOPORT_DATA 0x01CF
#define BOCHS_DISPLAY_LITTLE_ENDIAN 0x1e1e1e1e
#define BOCHS_DISPLAY_BIG_ENDIAN 0xbebebebe
#define VBE_DISPI_ID5 0xB0C5
enum class BochsFramebufferSettings {
Enabled = 0x1,
LinearFramebuffer = 0x40,
};
enum class BochsDISPIRegisters {
ID = 0x0,
XRES = 0x1,
YRES = 0x2,
BPP = 0x3,
ENABLE = 0x4,
BANK = 0x5,
VIRT_WIDTH = 0x6,
VIRT_HEIGHT = 0x7,
X_OFFSET = 0x8,
Y_OFFSET = 0x9,
VIDEO_RAM_64K_CHUNKS_COUNT = 0xA,
};
struct [[gnu::packed]] DISPIInterface {
u16 index_id;
u16 xres;
u16 yres;
u16 bpp;
u16 enable;
u16 bank;
u16 virt_width;
u16 virt_height;
u16 x_offset;
u16 y_offset;
u16 vram_64k_chunks_count;
};
struct [[gnu::packed]] ExtensionRegisters {
u32 region_size;
u32 framebuffer_byteorder;
};
struct [[gnu::packed]] BochsDisplayMMIORegisters {
u8 edid_data[0x400];
u16 vga_ioports[0x10];
u8 reserved[0xE0];
DISPIInterface bochs_regs;
u8 reserved2[0x100 - sizeof(DISPIInterface)];
ExtensionRegisters extension_regs;
};
}

View file

@ -0,0 +1,85 @@
/*
* 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>
#if ARCH(X86_64)
# include <Kernel/Arch/x86_64/Hypervisor/BochsDisplayConnector.h>
#endif
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/GPU/Bochs/Definitions.h>
#include <Kernel/Devices/GPU/Bochs/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/Bochs/QEMUDisplayConnector.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Memory/TypedMapping.h>
#include <Kernel/Sections.h>
namespace Kernel {
UNMAP_AFTER_INIT ErrorOr<bool> BochsGraphicsAdapter::probe(PCI::DeviceIdentifier const& pci_device_identifier)
{
PCI::HardwareID id = pci_device_identifier.hardware_id();
if (id.vendor_id == PCI::VendorID::QEMUOld && id.device_id == 0x1111)
return true;
if (id.vendor_id == PCI::VendorID::VirtualBox && id.device_id == 0xbeef)
return true;
return false;
}
UNMAP_AFTER_INIT ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> BochsGraphicsAdapter::create(PCI::DeviceIdentifier const& pci_device_identifier)
{
auto adapter = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) BochsGraphicsAdapter(pci_device_identifier)));
MUST(adapter->initialize_adapter(pci_device_identifier));
return adapter;
}
UNMAP_AFTER_INIT BochsGraphicsAdapter::BochsGraphicsAdapter(PCI::DeviceIdentifier const& device_identifier)
: PCI::Device(const_cast<PCI::DeviceIdentifier&>(device_identifier))
{
}
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.
// Note: In non x86-builds, we should never encounter VirtualBox hardware nor Pure Bochs VBE graphics,
// so just assume we can use the QEMU BochsVBE-compatible graphics adapter only.
auto bar0_space_size = PCI::get_BAR_space_size(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR0);
#if ARCH(X86_64)
bool virtual_box_hardware = (pci_device_identifier.hardware_id().vendor_id == 0x80ee && pci_device_identifier.hardware_id().device_id == 0xbeef);
if (pci_device_identifier.revision_id().value() == 0x0 || virtual_box_hardware) {
m_display_connector = BochsDisplayConnector::must_create(PhysicalAddress(PCI::get_BAR0(pci_device_identifier) & PCI::bar_address_mask), bar0_space_size, virtual_box_hardware);
} else {
auto registers_mapping = TRY(Memory::map_typed_writable<BochsDisplayMMIORegisters volatile>(PhysicalAddress(PCI::get_BAR2(pci_device_identifier) & PCI::bar_address_mask)));
VERIFY(registers_mapping.region);
m_display_connector = QEMUDisplayConnector::must_create(PhysicalAddress(PCI::get_BAR0(pci_device_identifier) & PCI::bar_address_mask), bar0_space_size, move(registers_mapping));
}
#else
auto registers_mapping = TRY(Memory::map_typed_writable<BochsDisplayMMIORegisters volatile>(PhysicalAddress(PCI::get_BAR2(pci_device_identifier) & PCI::bar_address_mask)));
VERIFY(registers_mapping.region);
m_display_connector = QEMUDisplayConnector::must_create(PhysicalAddress(PCI::get_BAR0(pci_device_identifier) & PCI::bar_address_mask), bar0_space_size, move(registers_mapping));
#endif
// 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".
// 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;
TRY(m_display_connector->set_safe_mode_setting());
return {};
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Devices/GPU/Bochs/Definitions.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
#include <Kernel/Memory/PhysicalAddress.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class GraphicsManagement;
struct BochsDisplayMMIORegisters;
class BochsGraphicsAdapter final : public GenericGraphicsAdapter
, public PCI::Device {
friend class GraphicsManagement;
public:
static ErrorOr<bool> probe(PCI::DeviceIdentifier const&);
static ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> create(PCI::DeviceIdentifier const&);
virtual ~BochsGraphicsAdapter() = default;
virtual StringView device_name() const override { return "BochsGraphicsAdapter"sv; }
private:
ErrorOr<void> initialize_adapter(PCI::DeviceIdentifier const&);
explicit BochsGraphicsAdapter(PCI::DeviceIdentifier const&);
LockRefPtr<DisplayConnector> m_display_connector;
};
}

View file

@ -0,0 +1,183 @@
/*
* 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/Devices/GPU/Bochs/QEMUDisplayConnector.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <LibEDID/Definitions.h>
namespace Kernel {
NonnullLockRefPtr<QEMUDisplayConnector> QEMUDisplayConnector::must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<BochsDisplayMMIORegisters volatile> registers_mapping)
{
auto device_or_error = DeviceManagement::try_create_device<QEMUDisplayConnector>(framebuffer_address, framebuffer_resource_size, 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 {};
}
ErrorOr<void> QEMUDisplayConnector::create_attached_framebuffer_console()
{
// We assume safe resolution is 1024x768x32
m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), 1024, 768, 1024 * sizeof(u32));
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
void QEMUDisplayConnector::enable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_framebuffer_console);
m_framebuffer_console->enable();
}
void QEMUDisplayConnector::disable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_framebuffer_console);
m_framebuffer_console->disable();
}
ErrorOr<void> QEMUDisplayConnector::flush_first_surface()
{
return Error::from_errno(ENOTSUP);
}
ErrorOr<void> QEMUDisplayConnector::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);
}
QEMUDisplayConnector::QEMUDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<BochsDisplayMMIORegisters volatile> registers_mapping)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, false)
, 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);
}
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 {};
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Try.h>
#include <Kernel/Devices/GPU/Bochs/Definitions.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
struct BochsDisplayMMIORegisters;
class QEMUDisplayConnector final
: public DisplayConnector {
friend class BochsGraphicsAdapter;
friend class DeviceManagement;
public:
AK_TYPEDEF_DISTINCT_ORDERED_ID(u16, IndexID);
static NonnullLockRefPtr<QEMUDisplayConnector> must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<BochsDisplayMMIORegisters volatile>);
private:
IndexID index_id() const;
ErrorOr<void> fetch_and_initialize_edid();
ErrorOr<void> create_attached_framebuffer_console();
QEMUDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<BochsDisplayMMIORegisters volatile>);
virtual bool mutable_mode_setting_capable() const override final { return true; }
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> set_safe_mode_setting() override final;
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<void> flush_first_surface() override final;
void set_framebuffer_to_big_endian_format();
void set_framebuffer_to_little_endian_format();
virtual void enable_console() override final;
virtual void disable_console() override final;
LockRefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console;
Memory::TypedMapping<BochsDisplayMMIORegisters volatile> m_registers;
};
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/MemoryManager.h>
namespace Kernel::Graphics {
BootFramebufferConsole::BootFramebufferConsole(PhysicalAddress framebuffer_addr, size_t width, size_t height, size_t pitch)
: GenericFramebufferConsoleImpl(width, height, pitch)
{
// NOTE: We're very early in the boot process, memory allocations shouldn't really fail
auto framebuffer_end = Memory::page_round_up(framebuffer_addr.offset(height * pitch).get()).release_value();
m_framebuffer = MM.allocate_kernel_region(framebuffer_addr.page_base(), framebuffer_end - framebuffer_addr.page_base().get(), "Boot Framebuffer"sv, Memory::Region::Access::ReadWrite).release_value();
[[maybe_unused]] auto result = m_framebuffer->set_write_combine(true);
m_framebuffer_data = m_framebuffer->vaddr().offset(framebuffer_addr.offset_in_page()).as_ptr();
memset(m_framebuffer_data, 0, height * pitch);
}
void BootFramebufferConsole::clear(size_t x, size_t y, size_t length)
{
SpinlockLocker lock(m_lock);
if (m_framebuffer_data)
GenericFramebufferConsoleImpl::clear(x, y, length);
}
void BootFramebufferConsole::clear_glyph(size_t x, size_t y)
{
VERIFY(m_lock.is_locked());
GenericFramebufferConsoleImpl::clear_glyph(x, y);
}
void BootFramebufferConsole::enable()
{
// Once disabled, ignore requests to re-enable
}
void BootFramebufferConsole::disable()
{
SpinlockLocker lock(m_lock);
GenericFramebufferConsoleImpl::disable();
m_framebuffer = nullptr;
m_framebuffer_data = nullptr;
}
void BootFramebufferConsole::write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical)
{
SpinlockLocker lock(m_lock);
if (m_framebuffer_data)
GenericFramebufferConsoleImpl::write(x, y, ch, background, foreground, critical);
}
void BootFramebufferConsole::set_cursor(size_t x, size_t y)
{
// Note: To ensure we don't trigger a deadlock, let's assert in
// case we already locked the spinlock, so we know there's a bug
// in the call path.
VERIFY(!m_lock.is_locked());
SpinlockLocker lock(m_lock);
hide_cursor();
m_x = x;
m_y = y;
show_cursor();
}
void BootFramebufferConsole::hide_cursor()
{
VERIFY(m_lock.is_locked());
GenericFramebufferConsoleImpl::hide_cursor();
}
void BootFramebufferConsole::show_cursor()
{
VERIFY(m_lock.is_locked());
GenericFramebufferConsoleImpl::show_cursor();
}
u8* BootFramebufferConsole::framebuffer_data()
{
VERIFY(m_lock.is_locked());
VERIFY(m_framebuffer_data);
return m_framebuffer_data;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Forward.h>
namespace Kernel::Graphics {
class BootFramebufferConsole : public GenericFramebufferConsoleImpl {
public:
virtual void clear(size_t x, size_t y, size_t length) override;
virtual void write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical = false) override;
using GenericFramebufferConsoleImpl::write;
virtual void enable() override;
virtual void disable() override;
virtual void flush(size_t, size_t, size_t, size_t) override { }
virtual void set_resolution(size_t, size_t, size_t) override { }
u8* unsafe_framebuffer_data() { return m_framebuffer_data; }
BootFramebufferConsole(PhysicalAddress framebuffer_addr, size_t width, size_t height, size_t pitch);
private:
virtual void set_cursor(size_t x, size_t y) override;
virtual void hide_cursor() override;
virtual void show_cursor() override;
protected:
virtual void clear_glyph(size_t x, size_t y) override;
virtual u8* framebuffer_data() override;
OwnPtr<Memory::Region> m_framebuffer;
u8* m_framebuffer_data {};
mutable Spinlock<LockRank::None> m_lock {};
};
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/AtomicRefCounted.h>
#include <AK/Types.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
namespace Kernel::Graphics {
class Console : public AtomicRefCounted<Console> {
public:
// Stanadard VGA text mode colors
enum Color : u8 {
Black = 0,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
LightGray,
DarkGray,
BrightBlue,
BrightGreen,
BrightCyan,
BrightRed,
BrightMagenta,
Yellow,
White,
};
public:
size_t width() const { return m_width; }
size_t height() const { return m_height; }
size_t pitch() const { return bytes_per_base_glyph() * width(); }
virtual size_t max_column() const { return m_width; }
virtual size_t max_row() const { return m_height; }
virtual size_t bytes_per_base_glyph() const = 0;
virtual size_t chars_per_line() const = 0;
virtual void enable() = 0;
virtual void disable() = 0;
virtual bool is_hardware_paged_capable() const = 0;
virtual bool has_hardware_cursor() const = 0;
virtual void set_cursor(size_t x, size_t y) = 0;
virtual void clear(size_t x, size_t y, size_t length) = 0;
virtual void write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical = false) = 0;
virtual void write(size_t x, size_t y, char ch, bool critical = false) = 0;
virtual void write(char ch, bool critical = false) = 0;
virtual void flush(size_t x, size_t y, size_t width, size_t height) = 0;
virtual ~Console() = default;
protected:
virtual void hide_cursor() = 0;
virtual void show_cursor() = 0;
Console(size_t width, size_t height)
: m_width(width)
, m_height(height)
{
m_enabled.store(true);
}
Atomic<bool> m_enabled;
Color m_default_foreground_color { Color::White };
Color m_default_background_color { Color::Black };
size_t m_width;
size_t m_height;
mutable size_t m_x { 0 };
mutable size_t m_y { 0 };
};
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/TTY/ConsoleManagement.h>
namespace Kernel::Graphics {
NonnullLockRefPtr<ContiguousFramebufferConsole> ContiguousFramebufferConsole::initialize(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch)
{
return adopt_lock_ref(*new ContiguousFramebufferConsole(framebuffer_address, width, height, pitch));
}
ContiguousFramebufferConsole::ContiguousFramebufferConsole(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch)
: GenericFramebufferConsole(width, height, pitch)
, m_framebuffer_address(framebuffer_address)
{
set_resolution(width, height, pitch);
}
void ContiguousFramebufferConsole::set_resolution(size_t width, size_t height, size_t pitch)
{
m_width = width;
m_height = height;
m_pitch = pitch;
size_t size = Memory::page_round_up(pitch * height).release_value_but_fixme_should_propagate_errors();
dbgln("Framebuffer Console: taking {} bytes", size);
auto region_or_error = MM.allocate_kernel_region(m_framebuffer_address, size, "Framebuffer Console"sv, Memory::Region::Access::ReadWrite, Memory::Region::Cacheable::Yes);
VERIFY(!region_or_error.is_error());
m_framebuffer_region = region_or_error.release_value();
// Just to start cleanly, we clean the entire framebuffer
memset(m_framebuffer_region->vaddr().as_ptr(), 0, pitch * height);
ConsoleManagement::the().resolution_was_changed();
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
namespace Kernel::Graphics {
class ContiguousFramebufferConsole final : public GenericFramebufferConsole {
public:
static NonnullLockRefPtr<ContiguousFramebufferConsole> initialize(PhysicalAddress, size_t width, size_t height, size_t pitch);
virtual void set_resolution(size_t width, size_t height, size_t pitch) override;
virtual void flush(size_t, size_t, size_t, size_t) override { }
private:
virtual u8* framebuffer_data() override
{
return m_framebuffer_region->vaddr().as_ptr();
}
OwnPtr<Memory::Region> m_framebuffer_region;
ContiguousFramebufferConsole(PhysicalAddress, size_t width, size_t height, size_t pitch);
PhysicalAddress m_framebuffer_address;
};
}

View file

@ -0,0 +1,379 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/TTY/ConsoleManagement.h>
namespace Kernel::Graphics {
// Note: This bitmap was generated from CathodeRegular10.font
constexpr unsigned char const font_cathode_8x16[128][16] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0000 (nul)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0001
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0002
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0003
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0004
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0005
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0006
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0007
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0008
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0009
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0010
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0011
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0012
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0013
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0014
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0015
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0016
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0017
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0018
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0019
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 ( )
{ 0x00, 0x00, 0x30, 0x78, 0x78, 0x78, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00 }, // U+0021 (!)
{ 0x00, 0x00, 0x6C, 0x6C, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0022 (")
{ 0x00, 0x00, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00 }, // U+0023 (#)
{ 0x00, 0x00, 0x30, 0x30, 0x78, 0xCC, 0xCC, 0x60, 0x18, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x00, 0x00 }, // U+0024 ($)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC2, 0xC4, 0x08, 0x10, 0x20, 0x46, 0x86, 0x00, 0x00, 0x00, 0x00 }, // U+0025 (%)
{ 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x36, 0x5C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00 }, // U+0026 (&)
{ 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0027 (')
{ 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x00 }, // U+0028 (()
{ 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00 }, // U+0029 ())
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x30, 0xFC, 0x30, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+002A (*)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+002B (+)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00 }, // U+002C (,)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+002D (-)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00 }, // U+002E (.)
{ 0x00, 0x00, 0x02, 0x06, 0x04, 0x0C, 0x18, 0x10, 0x30, 0x60, 0x40, 0xC0, 0x80, 0x00, 0x00, 0x00 }, // U+002F (/)
{ 0x00, 0x00, 0x7C, 0xC6, 0xCE, 0xCE, 0xDE, 0xD6, 0xF6, 0xE6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0030 (0)
{ 0x00, 0x00, 0x30, 0x70, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, 0x00, 0x00 }, // U+0031 (1)
{ 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00 }, // U+0032 (2)
{ 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x04, 0x38, 0x04, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0033 (3)
{ 0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00 }, // U+0034 (4)
{ 0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0035 (5)
{ 0x00, 0x00, 0x1C, 0x30, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0036 (6)
{ 0x00, 0x00, 0xFE, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00 }, // U+0037 (7)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0038 (8)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x0C, 0x18, 0x70, 0x00, 0x00, 0x00 }, // U+0039 (9)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00 }, // U+003A (:)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60, 0x00, 0x00 }, // U+003B (;)
{ 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xE0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00 }, // U+003C (<)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+003D (=)
{ 0x00, 0x00, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x0E, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x00, 0x00, 0x00 }, // U+003E (>)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00 }, // U+003F (?)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00 }, // U+0040 (@)
{ 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+0041 (A)
{ 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00 }, // U+0042 (B)
{ 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00 }, // U+0043 (C)
{ 0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00 }, // U+0044 (D)
{ 0x00, 0x00, 0xFE, 0x66, 0x62, 0x60, 0x68, 0x78, 0x68, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00 }, // U+0045 (E)
{ 0x00, 0x00, 0xFE, 0x66, 0x62, 0x60, 0x64, 0x7C, 0x64, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00 }, // U+0046 (F)
{ 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, 0xC6, 0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00 }, // U+0047 (G)
{ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+0048 (H)
{ 0x00, 0x00, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00 }, // U+0049 (I)
{ 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00 }, // U+004A (J)
{ 0x00, 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x68, 0x78, 0x68, 0x6C, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00 }, // U+004B (K)
{ 0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00 }, // U+004C (L)
{ 0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+004D (M)
{ 0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+004E (N)
{ 0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00 }, // U+004F (O)
{ 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00 }, // U+0050 (P)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x0E, 0x00, 0x00 }, // U+0051 (Q)
{ 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0xF6, 0x00, 0x00, 0x00 }, // U+0052 (R)
{ 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0053 (S)
{ 0x00, 0x00, 0xFC, 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00 }, // U+0054 (T)
{ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0055 (U)
{ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00 }, // U+0056 (V)
{ 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xFE, 0x7C, 0x6C, 0x00, 0x00, 0x00 }, // U+0057 (W)
{ 0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+0058 (X)
{ 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00 }, // U+0059 (Y)
{ 0x00, 0x00, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30, 0x60, 0x40, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00 }, // U+005A (Z)
{ 0x00, 0x00, 0xFC, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFC, 0x00, 0x00, 0x00 }, // U+005B ([)
{ 0x00, 0x00, 0x80, 0xC0, 0x40, 0x60, 0x30, 0x10, 0x18, 0x0C, 0x04, 0x06, 0x02, 0x00, 0x00, 0x00 }, // U+005C (\)
{ 0x00, 0x00, 0xFC, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xFC, 0x00, 0x00, 0x00 }, // U+005D (])
{ 0x00, 0x00, 0x20, 0x70, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+005E (^)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00 }, // U+005F (_)
{ 0x00, 0x00, 0x60, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0060 (`)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00 }, // U+0061 (a)
{ 0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00 }, // U+0062 (b)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0063 (c)
{ 0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00 }, // U+0064 (d)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0065 (e)
{ 0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00 }, // U+0066 (f)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00, 0x00 }, // U+0067 (g)
{ 0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00 }, // U+0068 (h)
{ 0x00, 0x00, 0x30, 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00 }, // U+0069 (i)
{ 0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00 }, // U+006A (j)
{ 0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x68, 0x70, 0x68, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00 }, // U+006B (k)
{ 0x00, 0x00, 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00 }, // U+006C (l)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xD6, 0xD6, 0xD6, 0xC6, 0xC6, 0x00, 0x00, 0x00 }, // U+006D (m)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00 }, // U+006E (n)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+006F (o)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, 0x00 }, // U+0070 (p)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00, 0x00 }, // U+0071 (q)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x76, 0x66, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00 }, // U+0072 (r)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x40, 0x70, 0x1C, 0x04, 0xC6, 0x7C, 0x00, 0x00, 0x00 }, // U+0073 (s)
{ 0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00 }, // U+0074 (t)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00 }, // U+0075 (u)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, 0x00, 0x00 }, // U+0076 (v)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00 }, // U+0077 (w)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00 }, // U+0078 (x)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x0C, 0xF8, 0x00, 0x00 }, // U+0079 (y)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x0C, 0x18, 0x30, 0x60, 0x66, 0xFE, 0x00, 0x00, 0x00 }, // U+007A (z)
{ 0x00, 0x00, 0x1E, 0x30, 0x30, 0x30, 0x30, 0xE0, 0x30, 0x30, 0x30, 0x30, 0x1E, 0x00, 0x00, 0x00 }, // U+007B ({)
{ 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00 }, // U+007C (|)
{ 0x00, 0x00, 0xF0, 0x18, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x18, 0xF0, 0x00, 0x00, 0x00 }, // U+007D (})
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+007E (~)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // U+007F
};
// FIXME: This assumes 32 bit BGR (Blue-Green-Red) palette
enum BGRColor : u32 {
Black = 0,
Blue = 0x0000FF,
Green = 0x00FF00,
Cyan = 0x0000FFFF,
Red = 0xFF0000,
Magenta = 0x00FF00FF,
Brown = 0x00964B00,
LightGray = 0x00D3D3D3,
DarkGray = 0x00A9A9A9,
BrightBlue = 0x0ADD8E6,
BrightGreen = 0x0090EE90,
BrightCyan = 0x00E0FFFF,
BrightRed = 0x00D70A53,
BrightMagenta = 0x00F984E5,
Yellow = 0x00FFE135,
White = 0x00FFFFFF,
};
static inline BGRColor convert_standard_color_to_bgr_color(Console::Color color)
{
switch (color) {
case Console::Color::Black:
return BGRColor::Black;
case Console::Color::Red:
return BGRColor::Red;
case Console::Color::Brown:
return BGRColor::Brown;
case Console::Color::Blue:
return BGRColor::Blue;
case Console::Color::Magenta:
return BGRColor::Magenta;
case Console::Color::Green:
return BGRColor::Green;
case Console::Color::Cyan:
return BGRColor::Cyan;
case Console::Color::LightGray:
return BGRColor::LightGray;
case Console::Color::DarkGray:
return BGRColor::DarkGray;
case Console::Color::BrightRed:
return BGRColor::BrightRed;
case Console::Color::BrightGreen:
return BGRColor::BrightGreen;
case Console::Color::Yellow:
return BGRColor::Yellow;
case Console::Color::BrightBlue:
return BGRColor::BrightBlue;
case Console::Color::BrightMagenta:
return BGRColor::BrightMagenta;
case Console::Color::BrightCyan:
return BGRColor::BrightCyan;
case Console::Color::White:
return BGRColor::White;
default:
VERIFY_NOT_REACHED();
}
}
size_t GenericFramebufferConsoleImpl::bytes_per_base_glyph() const
{
// FIXME: We assume we have 32 bit bpp framebuffer.
return 8 * 32;
}
size_t GenericFramebufferConsoleImpl::chars_per_line() const
{
return max_column();
}
void GenericFramebufferConsoleImpl::set_cursor(size_t x, size_t y)
{
hide_cursor();
m_x = x;
m_y = y;
show_cursor();
}
GenericFramebufferConsoleImpl::FramebufferOffset GenericFramebufferConsoleImpl::framebuffer_offset(size_t x, size_t y)
{
return { (&framebuffer_data()[x * sizeof(u32) * (m_glyph_columns + m_glyph_spacing) + y * m_glyph_rows * framebuffer_pitch()]) };
}
void GenericFramebufferConsoleImpl::hide_cursor()
{
auto offset_in_framebuffer = framebuffer_offset(m_x, m_y);
offset_in_framebuffer.bytes += framebuffer_pitch() * (m_glyph_rows - 1);
for (size_t glyph_column = 0; glyph_column < m_glyph_columns; glyph_column++)
offset_in_framebuffer.pixels[glyph_column] = m_cursor_overriden_pixels[glyph_column];
}
void GenericFramebufferConsoleImpl::show_cursor()
{
auto offset_in_framebuffer = framebuffer_offset(m_x, m_y);
offset_in_framebuffer.bytes += framebuffer_pitch() * (m_glyph_rows - 1);
for (size_t glyph_column = 0; glyph_column < m_glyph_columns; glyph_column++) {
m_cursor_overriden_pixels[glyph_column] = offset_in_framebuffer.pixels[glyph_column];
memset(offset_in_framebuffer.pixels + glyph_column, 0xff, sizeof(u32));
}
}
void GenericFramebufferConsoleImpl::clear(size_t x, size_t y, size_t length)
{
if (x == 0 && length == max_column()) {
// If we need to clear the entire row, just clean it with quick memset :)
auto offset_in_framebuffer = framebuffer_offset(x, y);
for (size_t glyph_row = 0; glyph_row < m_glyph_rows; glyph_row++) {
memset(offset_in_framebuffer.pixels, 0, framebuffer_pitch());
offset_in_framebuffer.bytes += framebuffer_pitch();
}
flush(0, m_glyph_rows * y, (m_glyph_columns + m_glyph_spacing) * length, 1);
return;
}
for (size_t index = 0; index < length; index++) {
if (x >= max_column()) {
x = 0;
y++;
if (y >= max_row())
y = 0;
}
clear_glyph(x, y);
}
}
void GenericFramebufferConsoleImpl::clear_glyph(size_t x, size_t y)
{
auto offset_in_framebuffer = framebuffer_offset(x, y);
for (size_t glyph_row = 0; glyph_row < m_glyph_rows; glyph_row++) {
memset(offset_in_framebuffer.pixels, 0, (m_glyph_columns + m_glyph_spacing) * sizeof(u32));
offset_in_framebuffer.bytes += framebuffer_pitch();
}
flush_glyph(x, y);
}
void GenericFramebufferConsoleImpl::enable()
{
memset(framebuffer_data(), 0, height() * framebuffer_pitch());
m_enabled.store(true);
}
void GenericFramebufferConsoleImpl::disable()
{
m_enabled.store(false);
}
void GenericFramebufferConsoleImpl::write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical)
{
if (!m_enabled.load())
return;
// If we are in critical printing mode, we need to handle new lines here
// because there's no other responsible object to do that in the print call path
if (critical && (ch == '\r' || ch == '\n')) {
m_x = 0;
m_y += 1;
if (m_y >= max_row())
m_y = 0;
return;
}
if ((int)ch < 0x20 || (int)ch == 0x7f) {
// FIXME: There's no point in printing empty glyphs...
// Maybe try to add these special glyphs and print them.
return;
}
clear_glyph(x, y);
auto bitmap = font_cathode_8x16[(int)ch];
auto offset_in_framebuffer = framebuffer_offset(x, y);
BGRColor foreground_color = convert_standard_color_to_bgr_color(foreground);
BGRColor background_color = convert_standard_color_to_bgr_color(background);
for (size_t glyph_row = 0; glyph_row < m_glyph_rows; glyph_row++) {
for (size_t glyph_column = m_glyph_columns; glyph_column > 0; glyph_column--) {
bool pixel_set = bitmap[glyph_row] & (1 << glyph_column);
offset_in_framebuffer.pixels[m_glyph_columns - glyph_column] = pixel_set ? foreground_color : background_color;
}
for (size_t spacing_column = 0; spacing_column < m_glyph_spacing; spacing_column++)
offset_in_framebuffer.pixels[m_glyph_columns + spacing_column] = background_color;
offset_in_framebuffer.bytes += framebuffer_pitch();
}
flush_glyph(x, y);
m_x = x + 1;
if (m_x >= max_column()) {
m_x = 0;
m_y = y + 1;
if (m_y >= max_row())
m_y = 0;
}
}
void GenericFramebufferConsoleImpl::flush_glyph(size_t x, size_t y)
{
flush((m_glyph_columns + m_glyph_spacing) * x, m_glyph_rows * y, m_glyph_columns + m_glyph_spacing, m_glyph_rows);
}
void GenericFramebufferConsoleImpl::write(size_t x, size_t y, char ch, bool critical)
{
write(x, y, ch, m_default_background_color, m_default_foreground_color, critical);
}
void GenericFramebufferConsoleImpl::write(char ch, bool critical)
{
write(m_x, m_y, ch, m_default_background_color, m_default_foreground_color, critical);
}
void GenericFramebufferConsole::clear(size_t x, size_t y, size_t length)
{
SpinlockLocker lock(m_lock);
GenericFramebufferConsoleImpl::clear(x, y, length);
}
void GenericFramebufferConsole::clear_glyph(size_t x, size_t y)
{
VERIFY(m_lock.is_locked());
GenericFramebufferConsoleImpl::clear_glyph(x, y);
}
void GenericFramebufferConsole::enable()
{
SpinlockLocker lock(m_lock);
GenericFramebufferConsoleImpl::enable();
}
void GenericFramebufferConsole::disable()
{
SpinlockLocker lock(m_lock);
GenericFramebufferConsoleImpl::disable();
}
void GenericFramebufferConsole::write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical)
{
SpinlockLocker lock(m_lock);
GenericFramebufferConsoleImpl::write(x, y, ch, background, foreground, critical);
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Devices/GPU/Console/Console.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel::Graphics {
class GenericFramebufferConsoleImpl : public Console {
public:
virtual size_t bytes_per_base_glyph() const override;
virtual size_t chars_per_line() const override;
virtual size_t max_column() const override { return m_width / (m_glyph_columns + m_glyph_spacing); }
virtual size_t max_row() const override { return m_height / m_glyph_rows; }
virtual bool is_hardware_paged_capable() const override { return false; }
virtual bool has_hardware_cursor() const override { return false; }
virtual void set_cursor(size_t x, size_t y) override;
virtual void clear(size_t x, size_t y, size_t length) override;
virtual void write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical = false) override;
virtual void write(size_t x, size_t y, char ch, bool critical = false) override;
virtual void write(char ch, bool critical = false) override;
virtual void enable() override;
virtual void disable() override;
virtual void set_resolution(size_t width, size_t height, size_t pitch) = 0;
protected:
virtual void hide_cursor() override;
virtual void show_cursor() override;
GenericFramebufferConsoleImpl(size_t width, size_t height, size_t pitch)
: Console(width, height)
, m_pitch(pitch)
{
m_cursor_overriden_pixels.fill(0);
}
virtual u8* framebuffer_data() = 0;
size_t framebuffer_pitch() const { return m_pitch; }
virtual void clear_glyph(size_t x, size_t y);
union FramebufferOffset {
u8* bytes;
u32* pixels;
};
FramebufferOffset framebuffer_offset(size_t x, size_t y);
void flush_glyph(size_t x, size_t y);
size_t const m_glyph_spacing { 1 };
size_t const m_glyph_columns { 8 };
size_t const m_glyph_rows { 16 };
Array<u32, 8> m_cursor_overriden_pixels;
size_t m_pitch;
};
class GenericFramebufferConsole : public GenericFramebufferConsoleImpl {
public:
virtual void clear(size_t x, size_t y, size_t length) override;
virtual void write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical = false) override;
virtual void enable() override;
virtual void disable() override;
protected:
GenericFramebufferConsole(size_t width, size_t height, size_t pitch)
: GenericFramebufferConsoleImpl(width, height, pitch)
{
}
virtual void clear_glyph(size_t x, size_t y) override;
mutable Spinlock<LockRank::None> m_lock {};
};
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Console/VGATextModeConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Sections.h>
namespace Kernel::Graphics {
UNMAP_AFTER_INIT NonnullLockRefPtr<VGATextModeConsole> VGATextModeConsole::initialize()
{
auto vga_window_size = MUST(Memory::page_round_up(0xc0000 - 0xa0000));
auto vga_window_region = MUST(MM.allocate_kernel_region(PhysicalAddress(0xa0000), vga_window_size, "VGA Display"sv, Memory::Region::Access::ReadWrite));
return adopt_lock_ref(*new (nothrow) VGATextModeConsole(move(vga_window_region)));
}
UNMAP_AFTER_INIT VGATextModeConsole::VGATextModeConsole(NonnullOwnPtr<Memory::Region> vga_window_region)
: Console(80, 25)
, m_vga_window_region(move(vga_window_region))
, m_current_vga_window(m_vga_window_region->vaddr().offset(0x18000).as_ptr())
{
for (size_t index = 0; index < height(); index++) {
clear_vga_row(index);
}
dbgln("VGA Text mode console initialized!");
}
enum VGAColor : u8 {
Black = 0,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
LightGray,
DarkGray,
BrightBlue,
BrightGreen,
BrightCyan,
BrightRed,
BrightMagenta,
Yellow,
White,
};
[[maybe_unused]] static inline VGAColor convert_standard_color_to_vga_color(Console::Color color)
{
switch (color) {
case Console::Color::Black:
return VGAColor::Black;
case Console::Color::Red:
return VGAColor::Red;
case Console::Color::Brown:
return VGAColor::Brown;
case Console::Color::Blue:
return VGAColor::Blue;
case Console::Color::Magenta:
return VGAColor::Magenta;
case Console::Color::Green:
return VGAColor::Green;
case Console::Color::Cyan:
return VGAColor::Cyan;
case Console::Color::LightGray:
return VGAColor::LightGray;
case Console::Color::DarkGray:
return VGAColor::DarkGray;
case Console::Color::BrightRed:
return VGAColor::BrightRed;
case Console::Color::BrightGreen:
return VGAColor::BrightGreen;
case Console::Color::Yellow:
return VGAColor::Yellow;
case Console::Color::BrightBlue:
return VGAColor::BrightBlue;
case Console::Color::BrightMagenta:
return VGAColor::BrightMagenta;
case Console::Color::BrightCyan:
return VGAColor::BrightCyan;
case Console::Color::White:
return VGAColor::White;
default:
VERIFY_NOT_REACHED();
}
}
void VGATextModeConsole::set_cursor(size_t x, size_t y)
{
SpinlockLocker lock(m_vga_lock);
GraphicsManagement::the().set_vga_text_mode_cursor(width(), x, y);
m_x = x;
m_y = y;
}
void VGATextModeConsole::hide_cursor()
{
SpinlockLocker lock(m_vga_lock);
GraphicsManagement::the().disable_vga_text_mode_console_cursor();
}
void VGATextModeConsole::show_cursor()
{
set_cursor(m_x, m_y);
}
void VGATextModeConsole::clear(size_t x, size_t y, size_t length)
{
SpinlockLocker lock(m_vga_lock);
auto* buf = (u16*)m_current_vga_window.offset((x * 2) + (y * width() * 2)).as_ptr();
for (size_t index = 0; index < length; index++) {
buf[index] = 0x0720;
}
}
void VGATextModeConsole::write(size_t x, size_t y, char ch, bool critical)
{
write(x, y, ch, m_default_background_color, m_default_foreground_color, critical);
}
void VGATextModeConsole::write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical)
{
SpinlockLocker lock(m_vga_lock);
// If we are in critical printing mode, we need to handle new lines here
// because there's no other responsible object to do that in the print call path
if (critical && (ch == '\r' || ch == '\n')) {
// Disable hardware VGA cursor
GraphicsManagement::the().disable_vga_text_mode_console_cursor();
m_x = 0;
m_y += 1;
if (m_y >= max_row())
m_y = 0;
return;
}
auto* buf = (u16*)m_current_vga_window.offset((x * 2) + (y * width() * 2)).as_ptr();
*buf = foreground << 8 | background << 12 | ch;
m_x = x + 1;
if (m_x >= max_column()) {
m_x = 0;
m_y = y + 1;
if (m_y >= max_row())
m_y = 0;
}
}
void VGATextModeConsole::clear_vga_row(u16 row)
{
clear(0, row, width());
}
void VGATextModeConsole::write(char ch, bool critical)
{
write(m_x, m_y, ch, critical);
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Devices/GPU/Console/Console.h>
#include <Kernel/Locking/Spinlock.h>
namespace Kernel::Graphics {
class VGATextModeConsole final : public Console {
public:
static NonnullLockRefPtr<VGATextModeConsole> initialize();
virtual size_t chars_per_line() const override { return width(); };
virtual bool has_hardware_cursor() const override { return true; }
virtual bool is_hardware_paged_capable() const override { return true; }
virtual size_t bytes_per_base_glyph() const override { return 2; }
virtual void set_cursor(size_t x, size_t y) override;
virtual void clear(size_t x, size_t y, size_t length) override;
virtual void write(size_t x, size_t y, char ch, bool critical = false) override;
virtual void write(size_t x, size_t y, char ch, Color background, Color foreground, bool critical = false) override;
virtual void write(char ch, bool critical = false) override;
virtual void flush(size_t, size_t, size_t, size_t) override { }
virtual void enable() override { }
virtual void disable() override { }
private:
virtual void hide_cursor() override;
virtual void show_cursor() override;
void clear_vga_row(u16 row);
explicit VGATextModeConsole(NonnullOwnPtr<Memory::Region>);
mutable Spinlock<LockRank::None> m_vga_lock {};
NonnullOwnPtr<Memory::Region> m_vga_window_region;
VirtualAddress m_current_vga_window;
};
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel::Graphics {
// Note: Address 0x50 is expected to be the DDC2 (EDID) i2c address.
static constexpr u8 ddc2_i2c_address = 0x50;
}

View file

@ -0,0 +1,518 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/API/Ioctl.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/FileSystem/SysFS/Subsystems/DeviceIdentifiers/CharacterDevicesDirectory.h>
#include <Kernel/FileSystem/SysFS/Subsystems/Devices/Graphics/DisplayConnector/DeviceDirectory.h>
#include <Kernel/FileSystem/SysFS/Subsystems/Devices/Graphics/DisplayConnector/Directory.h>
#include <Kernel/Memory/MemoryManager.h>
namespace Kernel {
DisplayConnector::DisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool enable_write_combine_optimization)
: CharacterDevice(226, GraphicsManagement::the().allocate_minor_device_number())
, m_enable_write_combine_optimization(enable_write_combine_optimization)
, m_framebuffer_at_arbitrary_physical_range(false)
, m_framebuffer_address(framebuffer_address)
, m_framebuffer_resource_size(framebuffer_resource_size)
{
}
DisplayConnector::DisplayConnector(size_t framebuffer_resource_size, bool enable_write_combine_optimization)
: CharacterDevice(226, GraphicsManagement::the().allocate_minor_device_number())
, m_enable_write_combine_optimization(enable_write_combine_optimization)
, m_framebuffer_at_arbitrary_physical_range(true)
, m_framebuffer_address({})
, m_framebuffer_resource_size(framebuffer_resource_size)
{
}
ErrorOr<NonnullLockRefPtr<Memory::VMObject>> DisplayConnector::vmobject_for_mmap(Process&, Memory::VirtualRange const&, u64& offset, bool)
{
VERIFY(m_shared_framebuffer_vmobject);
if (offset != 0)
return Error::from_errno(ENOTSUP);
return *m_shared_framebuffer_vmobject;
}
ErrorOr<size_t> DisplayConnector::read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t)
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<size_t> DisplayConnector::write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t)
{
return Error::from_errno(ENOTIMPL);
}
void DisplayConnector::will_be_destroyed()
{
GraphicsManagement::the().detach_display_connector({}, *this);
// NOTE: We check if m_symlink_sysfs_component is not null, because if we failed
// at some point in DisplayConnector::after_inserting(), then that method will tear down
// the object internal members safely, so we don't want to do it again here.
if (m_symlink_sysfs_component) {
before_will_be_destroyed_remove_symlink_from_device_identifier_directory();
m_symlink_sysfs_component.clear();
}
// NOTE: We check if m_sysfs_device_directory is not null, because if we failed
// at some point in DisplayConnector::after_inserting(), then that method will tear down
// the object internal members safely, so we don't want to do it again here.
if (m_sysfs_device_directory) {
SysFSDisplayConnectorsDirectory::the().unplug({}, *m_sysfs_device_directory);
m_sysfs_device_directory.clear();
}
before_will_be_destroyed_remove_from_device_management();
}
ErrorOr<void> DisplayConnector::allocate_framebuffer_resources(size_t rounded_size)
{
VERIFY((rounded_size % PAGE_SIZE) == 0);
if (!m_framebuffer_at_arbitrary_physical_range) {
VERIFY(m_framebuffer_address.value().page_base() == m_framebuffer_address.value());
m_shared_framebuffer_vmobject = TRY(Memory::SharedFramebufferVMObject::try_create_for_physical_range(m_framebuffer_address.value(), rounded_size));
m_framebuffer_region = TRY(MM.allocate_kernel_region(m_framebuffer_address.value().page_base(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite));
} else {
m_shared_framebuffer_vmobject = TRY(Memory::SharedFramebufferVMObject::try_create_at_arbitrary_physical_range(rounded_size));
m_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(m_shared_framebuffer_vmobject->real_writes_framebuffer_vmobject(), rounded_size, "Framebuffer"sv, Memory::Region::Access::ReadWrite));
}
m_framebuffer_data = m_framebuffer_region->vaddr().as_ptr();
m_fake_writes_framebuffer_region = TRY(MM.allocate_kernel_region_with_vmobject(m_shared_framebuffer_vmobject->fake_writes_framebuffer_vmobject(), rounded_size, "Fake Writes Framebuffer"sv, Memory::Region::Access::ReadWrite));
return {};
}
ErrorOr<void> DisplayConnector::after_inserting()
{
ArmedScopeGuard clean_from_device_management([&] {
before_will_be_destroyed_remove_from_device_management();
});
auto sysfs_display_connector_device_directory = DisplayConnectorSysFSDirectory::create(SysFSDisplayConnectorsDirectory::the(), *this);
m_sysfs_device_directory = sysfs_display_connector_device_directory;
SysFSDisplayConnectorsDirectory::the().plug({}, *sysfs_display_connector_device_directory);
ArmedScopeGuard clean_from_sysfs_display_connector_device_directory([&] {
SysFSDisplayConnectorsDirectory::the().unplug({}, *m_sysfs_device_directory);
m_sysfs_device_directory.clear();
});
VERIFY(!m_symlink_sysfs_component);
auto sys_fs_component = TRY(SysFSSymbolicLinkDeviceComponent::try_create(SysFSCharacterDevicesDirectory::the(), *this, *m_sysfs_device_directory));
m_symlink_sysfs_component = sys_fs_component;
after_inserting_add_symlink_to_device_identifier_directory();
ArmedScopeGuard clean_symlink_to_device_identifier_directory([&] {
VERIFY(m_symlink_sysfs_component);
before_will_be_destroyed_remove_symlink_from_device_identifier_directory();
m_symlink_sysfs_component.clear();
});
if (auto result_or_error = Memory::page_round_up(m_framebuffer_resource_size); result_or_error.is_error()) {
// NOTE: The amount of framebuffer resource being specified is erroneous, then default to 16 MiB.
TRY(allocate_framebuffer_resources(16 * MiB));
m_framebuffer_resource_size = 16 * MiB;
} else {
if (auto allocation_result = allocate_framebuffer_resources(result_or_error.release_value()); allocation_result.is_error()) {
// NOTE: The amount of framebuffer resource being specified is too big, use 16 MiB just to get going.
TRY(allocate_framebuffer_resources(16 * MiB));
m_framebuffer_resource_size = 16 * MiB;
}
}
clean_from_device_management.disarm();
clean_from_sysfs_display_connector_device_directory.disarm();
clean_symlink_to_device_identifier_directory.disarm();
GraphicsManagement::the().attach_new_display_connector({}, *this);
if (m_enable_write_combine_optimization) {
[[maybe_unused]] auto result = m_framebuffer_region->set_write_combine(true);
}
after_inserting_add_to_device_management();
return {};
}
bool DisplayConnector::console_mode() const
{
VERIFY(m_control_lock.is_locked());
return m_console_mode;
}
void DisplayConnector::set_display_mode(Badge<GraphicsManagement>, DisplayMode mode)
{
SpinlockLocker locker(m_control_lock);
{
SpinlockLocker locker(m_modeset_lock);
[[maybe_unused]] auto result = set_y_offset(0);
}
m_console_mode = mode == DisplayMode::Console ? true : false;
if (m_console_mode) {
VERIFY(m_framebuffer_region->size() == m_fake_writes_framebuffer_region->size());
memcpy(m_fake_writes_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->size());
m_shared_framebuffer_vmobject->switch_to_fake_sink_framebuffer_writes({});
enable_console();
} else {
disable_console();
m_shared_framebuffer_vmobject->switch_to_real_framebuffer_writes({});
VERIFY(m_framebuffer_region->size() == m_fake_writes_framebuffer_region->size());
memcpy(m_framebuffer_region->vaddr().as_ptr(), m_fake_writes_framebuffer_region->vaddr().as_ptr(), m_framebuffer_region->size());
}
}
ErrorOr<void> DisplayConnector::initialize_edid_for_generic_monitor(Optional<Array<u8, 3>> possible_manufacturer_id_string)
{
u8 raw_manufacturer_id[2] = { 0x0, 0x0 };
if (possible_manufacturer_id_string.has_value()) {
Array<u8, 3> manufacturer_id_string = possible_manufacturer_id_string.release_value();
u8 byte1 = (((static_cast<u8>(manufacturer_id_string[0]) - '@') & 0x1f) << 2) | (((static_cast<u8>(manufacturer_id_string[1]) - '@') >> 3) & 3);
u8 byte2 = ((static_cast<u8>(manufacturer_id_string[2]) - '@') & 0x1f) | (((static_cast<u8>(manufacturer_id_string[1]) - '@') << 5) & 0xe0);
Array<u8, 2> manufacturer_id_string_packed_bytes = { byte1, byte2 };
raw_manufacturer_id[0] = manufacturer_id_string_packed_bytes[1];
raw_manufacturer_id[1] = manufacturer_id_string_packed_bytes[0];
}
Array<u8, 128> virtual_monitor_edid = {
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */
raw_manufacturer_id[1], raw_manufacturer_id[0], /* manufacturer */
0x00, 0x00, /* product code */
0x00, 0x00, 0x00, 0x00, /* serial number goes here */
0x01, /* week of manufacture */
0x00, /* year of manufacture */
0x01, 0x03, /* EDID version */
0x80, /* capabilities - digital */
0x00, /* horiz. res in cm, zero for projectors */
0x00, /* vert. res in cm */
0x78, /* display gamma (120 == 2.2). */
0xEE, /* features (standby, suspend, off, RGB, std */
/* colour space, preferred timing mode) */
0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
/* chromaticity for standard colour space. */
0x00, 0x00, 0x00, /* no default timings */
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01,
0x01, 0x01, 0x01, 0x01, /* no standard timings */
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x02, 0x02,
0x02, 0x02,
/* descriptor block 1 goes below */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* descriptor block 2, monitor ranges */
0x00, 0x00, 0x00, 0xFD, 0x00,
0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20,
0x20, 0x20,
/* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
0x20,
/* descriptor block 3, monitor name */
0x00, 0x00, 0x00, 0xFC, 0x00,
'G', 'e', 'n', 'e', 'r', 'i', 'c', 'S', 'c', 'r', 'e', 'e', 'n',
/* descriptor block 4: dummy data */
0x00, 0x00, 0x00, 0x10, 0x00,
0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20,
0x00, /* number of extensions */
0x00 /* checksum goes here */
};
// Note: Fix checksum to avoid warnings about checksum mismatch.
size_t checksum = 0;
// Note: Read all 127 bytes to add them to the checksum. Byte 128 is zeroed so
// we could technically add it to the sum result, but it could lead to an error if it contained
// a non-zero value, so we are not using it.
for (size_t index = 0; index < sizeof(virtual_monitor_edid) - 1; index++)
checksum += virtual_monitor_edid[index];
virtual_monitor_edid[127] = 0x100 - checksum;
set_edid_bytes(virtual_monitor_edid);
return {};
}
void DisplayConnector::set_edid_bytes(Array<u8, 128> const& edid_bytes, bool might_be_invalid)
{
memcpy((u8*)m_edid_bytes, edid_bytes.data(), sizeof(m_edid_bytes));
if (auto parsed_edid = EDID::Parser::from_bytes({ m_edid_bytes, sizeof(m_edid_bytes) }); !parsed_edid.is_error()) {
m_edid_parser = parsed_edid.release_value();
m_edid_valid = true;
} else {
if (!might_be_invalid) {
dmesgln("DisplayConnector: Print offending EDID");
for (size_t x = 0; x < 128; x = x + 16) {
dmesgln("{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
m_edid_bytes[x], m_edid_bytes[x + 1], m_edid_bytes[x + 2], m_edid_bytes[x + 3],
m_edid_bytes[x + 4], m_edid_bytes[x + 5], m_edid_bytes[x + 6], m_edid_bytes[x + 7],
m_edid_bytes[x + 8], m_edid_bytes[x + 9], m_edid_bytes[x + 10], m_edid_bytes[x + 11],
m_edid_bytes[x + 12], m_edid_bytes[x + 13], m_edid_bytes[x + 14], m_edid_bytes[x + 15]);
}
dmesgln("DisplayConnector: Parsing EDID failed: {}", parsed_edid.error());
}
}
}
ErrorOr<void> DisplayConnector::flush_rectangle(size_t, FBRect const&)
{
return Error::from_errno(ENOTSUP);
}
DisplayConnector::ModeSetting DisplayConnector::current_mode_setting() const
{
SpinlockLocker locker(m_modeset_lock);
return m_current_mode_setting;
}
ErrorOr<ByteBuffer> DisplayConnector::get_edid() const
{
if (!m_edid_valid)
return Error::from_errno(ENODEV);
return ByteBuffer::copy(m_edid_bytes, sizeof(m_edid_bytes));
}
struct GraphicsIOCtlChecker {
unsigned ioctl_number;
StringView name;
bool requires_ownership { false };
};
static constexpr GraphicsIOCtlChecker s_checkers[] = {
{ GRAPHICS_IOCTL_GET_PROPERTIES, "GRAPHICS_IOCTL_GET_PROPERTIES"sv, false },
{ GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER, "GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER"sv, true },
{ GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER, "GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER"sv, false },
{ GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS, "GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS"sv, true },
{ GRAPHICS_IOCTL_FLUSH_HEAD, "GRAPHICS_IOCTL_FLUSH_HEAD"sv, true },
{ GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING"sv, true },
{ GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING"sv, false },
{ GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING, "GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING"sv, true },
{ GRAPHICS_IOCTL_SET_RESPONSIBLE, "GRAPHICS_IOCTL_SET_RESPONSIBLE"sv, false },
{ GRAPHICS_IOCTL_UNSET_RESPONSIBLE, "GRAPHICS_IOCTL_UNSET_RESPONSIBLE"sv, true },
};
static StringView ioctl_to_stringview(unsigned request)
{
for (auto& checker : s_checkers) {
if (checker.ioctl_number == request)
return checker.name;
}
return "unknown"sv;
}
ErrorOr<bool> DisplayConnector::ioctl_requires_ownership(unsigned request) const
{
for (auto& checker : s_checkers) {
if (checker.ioctl_number == request)
return checker.requires_ownership;
}
// Note: In case of unknown ioctl, return EINVAL.
return Error::from_errno(EINVAL);
}
ErrorOr<void> DisplayConnector::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg)
{
TRY(Process::current().require_promise(Pledge::video));
// Note: We only allow to set responsibility on a DisplayConnector,
// get the current ModeSetting or the hardware framebuffer properties without the
// need of having an established responsibility on a DisplayConnector.
auto needs_ownership = TRY(ioctl_requires_ownership(request));
if (needs_ownership) {
auto process = m_responsible_process.strong_ref();
if (!process || process.ptr() != &Process::current()) {
dbgln("DisplayConnector::ioctl: {} requires ownership over the device", ioctl_to_stringview(request));
return Error::from_errno(EPERM);
}
}
switch (request) {
case GRAPHICS_IOCTL_SET_RESPONSIBLE: {
SpinlockLocker locker(m_responsible_process_lock);
auto process = m_responsible_process.strong_ref();
// Note: If there's already a process being responsible, just return an error.
// We could technically return 0 if the requesting process was already
// set to be responsible for this DisplayConnector, but it services
// no good purpose and should be considered a bug if this happens anyway.
if (process)
return Error::from_errno(EPERM);
m_responsible_process = Process::current();
return {};
}
case GRAPHICS_IOCTL_UNSET_RESPONSIBLE: {
SpinlockLocker locker(m_responsible_process_lock);
auto process = m_responsible_process.strong_ref();
if (!process)
return Error::from_errno(ESRCH);
if (process.ptr() != &Process::current())
return Error::from_errno(EPERM);
m_responsible_process.clear();
return {};
}
case GRAPHICS_IOCTL_GET_PROPERTIES: {
VERIFY(m_shared_framebuffer_vmobject);
auto user_properties = static_ptr_cast<GraphicsConnectorProperties*>(arg);
GraphicsConnectorProperties properties {};
properties.flushing_support = flush_support();
properties.doublebuffer_support = double_framebuffering_capable();
properties.partial_flushing_support = partial_flush_support();
properties.refresh_rate_support = refresh_rate_support();
properties.max_buffer_bytes = m_shared_framebuffer_vmobject->size();
return copy_to_user(user_properties, &properties);
}
case GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING: {
auto user_head_mode_setting = static_ptr_cast<GraphicsHeadModeSetting*>(arg);
GraphicsHeadModeSetting head_mode_setting {};
TRY(copy_from_user(&head_mode_setting, user_head_mode_setting));
{
SpinlockLocker control_locker(m_control_lock);
head_mode_setting.horizontal_stride = m_current_mode_setting.horizontal_stride;
head_mode_setting.pixel_clock_in_khz = m_current_mode_setting.pixel_clock_in_khz;
head_mode_setting.horizontal_active = m_current_mode_setting.horizontal_active;
head_mode_setting.horizontal_front_porch_pixels = m_current_mode_setting.horizontal_front_porch_pixels;
head_mode_setting.horizontal_sync_time_pixels = m_current_mode_setting.horizontal_sync_time_pixels;
head_mode_setting.horizontal_blank_pixels = m_current_mode_setting.horizontal_blank_pixels;
head_mode_setting.vertical_active = m_current_mode_setting.vertical_active;
head_mode_setting.vertical_front_porch_lines = m_current_mode_setting.vertical_front_porch_lines;
head_mode_setting.vertical_sync_time_lines = m_current_mode_setting.vertical_sync_time_lines;
head_mode_setting.vertical_blank_lines = m_current_mode_setting.vertical_blank_lines;
head_mode_setting.horizontal_offset = m_current_mode_setting.horizontal_offset;
head_mode_setting.vertical_offset = m_current_mode_setting.vertical_offset;
}
return copy_to_user(user_head_mode_setting, &head_mode_setting);
}
case GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING: {
auto user_mode_setting = static_ptr_cast<GraphicsHeadModeSetting const*>(arg);
auto head_mode_setting = TRY(copy_typed_from_user(user_mode_setting));
if (head_mode_setting.horizontal_stride < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.pixel_clock_in_khz < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.horizontal_active < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.horizontal_front_porch_pixels < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.horizontal_sync_time_pixels < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.horizontal_blank_pixels < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.vertical_active < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.vertical_front_porch_lines < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.vertical_sync_time_lines < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.vertical_blank_lines < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.horizontal_offset < 0)
return Error::from_errno(EINVAL);
if (head_mode_setting.vertical_offset < 0)
return Error::from_errno(EINVAL);
{
SpinlockLocker control_locker(m_control_lock);
ModeSetting requested_mode_setting;
requested_mode_setting.horizontal_stride = 0;
requested_mode_setting.pixel_clock_in_khz = head_mode_setting.pixel_clock_in_khz;
requested_mode_setting.horizontal_active = head_mode_setting.horizontal_active;
requested_mode_setting.horizontal_front_porch_pixels = head_mode_setting.horizontal_front_porch_pixels;
requested_mode_setting.horizontal_sync_time_pixels = head_mode_setting.horizontal_sync_time_pixels;
requested_mode_setting.horizontal_blank_pixels = head_mode_setting.horizontal_blank_pixels;
requested_mode_setting.vertical_active = head_mode_setting.vertical_active;
requested_mode_setting.vertical_front_porch_lines = head_mode_setting.vertical_front_porch_lines;
requested_mode_setting.vertical_sync_time_lines = head_mode_setting.vertical_sync_time_lines;
requested_mode_setting.vertical_blank_lines = head_mode_setting.vertical_blank_lines;
requested_mode_setting.horizontal_offset = head_mode_setting.horizontal_offset;
requested_mode_setting.vertical_offset = head_mode_setting.vertical_offset;
TRY(set_mode_setting(requested_mode_setting));
}
return {};
}
case GRAPHICS_IOCTL_SET_SAFE_HEAD_MODE_SETTING: {
SpinlockLocker control_locker(m_control_lock);
TRY(set_safe_mode_setting());
return {};
}
case GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER: {
// FIXME: We silently ignore the request if we are in console mode.
// WindowServer is not ready yet to handle errors such as EBUSY currently.
SpinlockLocker control_locker(m_control_lock);
if (console_mode()) {
return {};
}
auto user_head_vertical_buffer_offset = static_ptr_cast<GraphicsHeadVerticalOffset const*>(arg);
auto head_vertical_buffer_offset = TRY(copy_typed_from_user(user_head_vertical_buffer_offset));
SpinlockLocker locker(m_modeset_lock);
if (head_vertical_buffer_offset.offsetted < 0 || head_vertical_buffer_offset.offsetted > 1)
return Error::from_errno(EINVAL);
TRY(set_y_offset(head_vertical_buffer_offset.offsetted == 0 ? 0 : m_current_mode_setting.vertical_active));
if (head_vertical_buffer_offset.offsetted == 0)
m_vertical_offsetted = false;
else
m_vertical_offsetted = true;
return {};
}
case GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER: {
auto user_head_vertical_buffer_offset = static_ptr_cast<GraphicsHeadVerticalOffset*>(arg);
GraphicsHeadVerticalOffset head_vertical_buffer_offset {};
TRY(copy_from_user(&head_vertical_buffer_offset, user_head_vertical_buffer_offset));
head_vertical_buffer_offset.offsetted = m_vertical_offsetted;
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);
auto user_flush_rects = static_ptr_cast<FBFlushRects const*>(arg);
auto flush_rects = TRY(copy_typed_from_user(user_flush_rects));
if (Checked<unsigned>::multiplication_would_overflow(flush_rects.count, sizeof(FBRect)))
return Error::from_errno(EFAULT);
if (flush_rects.count > 0) {
for (unsigned i = 0; i < flush_rects.count; i++) {
FBRect user_dirty_rect;
TRY(copy_from_user(&user_dirty_rect, &flush_rects.rects[i]));
{
SpinlockLocker control_locker(m_control_lock);
if (console_mode()) {
return {};
}
TRY(flush_rectangle(flush_rects.buffer_index, user_dirty_rect));
}
}
}
return {};
};
case GRAPHICS_IOCTL_FLUSH_HEAD: {
// FIXME: We silently ignore the request if we are in console mode.
// WindowServer is not ready yet to handle errors such as EBUSY currently.
MutexLocker locker(m_flushing_lock);
SpinlockLocker control_locker(m_control_lock);
if (console_mode()) {
return {};
}
if (!flush_support())
return Error::from_errno(ENOTSUP);
TRY(flush_first_surface());
return {};
}
}
// Note: We already verify that the IOCTL is supported and not unknown in
// the call to the ioctl_requires_ownership method, so if we reached this
// section of the code, this is bug.
VERIFY_NOT_REACHED();
}
}

View file

@ -0,0 +1,174 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/API/Ioctl.h>
#include <Kernel/Devices/CharacterDevice.h>
#include <Kernel/Memory/SharedFramebufferVMObject.h>
#include <LibEDID/EDID.h>
namespace Kernel {
class GraphicsManagement;
class DisplayConnector : public CharacterDevice {
friend class GraphicsManagement;
friend class DeviceManagement;
public:
struct ModeSetting {
size_t horizontal_blanking_start() const
{
return horizontal_active;
}
size_t horizontal_sync_start() const
{
return horizontal_active + horizontal_front_porch_pixels;
}
size_t horizontal_sync_end() const
{
return horizontal_active + horizontal_front_porch_pixels + horizontal_sync_time_pixels;
}
size_t horizontal_total() const
{
return horizontal_active + horizontal_blank_pixels;
}
size_t vertical_blanking_start() const
{
return vertical_active;
}
size_t vertical_sync_start() const
{
return vertical_active + vertical_front_porch_lines;
}
size_t vertical_sync_end() const
{
return vertical_active + vertical_front_porch_lines + vertical_sync_time_lines;
}
size_t vertical_total() const
{
return vertical_active + vertical_blank_lines;
}
size_t horizontal_stride; // Note: This is commonly known as "pitch"
size_t pixel_clock_in_khz;
size_t horizontal_active;
size_t horizontal_front_porch_pixels;
size_t horizontal_sync_time_pixels;
size_t horizontal_blank_pixels;
size_t vertical_active;
size_t vertical_front_porch_lines;
size_t vertical_sync_time_lines;
size_t vertical_blank_lines;
size_t horizontal_offset; // Note: This is commonly known as "x offset"
size_t vertical_offset; // Note: This is commonly known as "y offset"
};
public:
enum class DisplayMode {
Graphical,
Console,
};
public:
virtual ~DisplayConnector() = default;
virtual bool mutable_mode_setting_capable() const = 0;
virtual bool double_framebuffering_capable() const = 0;
virtual bool flush_support() const = 0;
virtual bool partial_flush_support() const = 0;
// Note: This can indicate to userland if the underlying hardware requires
// a defined refresh rate being supplied when modesetting the screen resolution.
// Paravirtualized hardware don't need such setting and can safely ignore this.
virtual bool refresh_rate_support() const = 0;
bool console_mode() const;
ErrorOr<ByteBuffer> get_edid() const;
virtual ErrorOr<void> set_mode_setting(ModeSetting const&) = 0;
virtual ErrorOr<void> set_safe_mode_setting() = 0;
ModeSetting current_mode_setting() const;
virtual ErrorOr<void> set_y_offset(size_t y) = 0;
virtual ErrorOr<void> unblank() = 0;
void set_display_mode(Badge<GraphicsManagement>, DisplayMode);
Memory::Region const& framebuffer_region() const { return *m_framebuffer_region; }
protected:
void set_edid_bytes(Array<u8, 128> const& edid_bytes, bool might_be_invalid = false);
DisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool enable_write_combine_optimization);
DisplayConnector(size_t framebuffer_resource_size, bool enable_write_combine_optimization);
virtual void enable_console() = 0;
virtual void disable_console() = 0;
virtual ErrorOr<void> flush_first_surface() = 0;
virtual ErrorOr<void> flush_rectangle(size_t buffer_index, FBRect const& rect);
ErrorOr<void> initialize_edid_for_generic_monitor(Optional<Array<u8, 3>> manufacturer_id_string);
mutable Spinlock<LockRank::None> m_control_lock {};
mutable Mutex m_flushing_lock;
bool m_console_mode { false };
bool m_vertical_offsetted { false };
mutable Spinlock<LockRank::None> m_modeset_lock {};
ModeSetting m_current_mode_setting {};
Optional<EDID::Parser> m_edid_parser;
EDID::Parser::RawBytes m_edid_bytes {};
bool m_edid_valid { false };
u8* framebuffer_data() { return m_framebuffer_data; }
private:
// ^File
virtual bool is_seekable() const override { return true; }
virtual bool can_read(OpenFileDescription const&, u64) const final override { return true; }
virtual bool can_write(OpenFileDescription const&, u64) const final override { return true; }
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override final;
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override final;
virtual ErrorOr<NonnullLockRefPtr<Memory::VMObject>> vmobject_for_mmap(Process&, Memory::VirtualRange const&, u64&, bool) override final;
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override final;
virtual StringView class_name() const override final { return "DisplayConnector"sv; }
DisplayConnector& operator=(DisplayConnector const&) = delete;
DisplayConnector& operator=(DisplayConnector&&) = delete;
DisplayConnector(DisplayConnector&&) = delete;
virtual void will_be_destroyed() override;
virtual ErrorOr<void> after_inserting() override;
ErrorOr<void> allocate_framebuffer_resources(size_t rounded_size);
ErrorOr<bool> ioctl_requires_ownership(unsigned request) const;
OwnPtr<Memory::Region> m_framebuffer_region;
OwnPtr<Memory::Region> m_fake_writes_framebuffer_region;
u8* m_framebuffer_data {};
bool const m_enable_write_combine_optimization { false };
bool const m_framebuffer_at_arbitrary_physical_range { false };
protected:
Optional<PhysicalAddress> const m_framebuffer_address;
size_t m_framebuffer_resource_size;
private:
LockRefPtr<Memory::SharedFramebufferVMObject> m_shared_framebuffer_vmobject;
LockWeakPtr<Process> m_responsible_process;
Spinlock<LockRank::None> m_responsible_process_lock {};
IntrusiveListNode<DisplayConnector, LockRefPtr<DisplayConnector>> m_list_node;
};
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Generic/DisplayConnector.h>
#include <Kernel/Devices/GPU/Management.h>
namespace Kernel {
NonnullLockRefPtr<GenericDisplayConnector> GenericDisplayConnector::must_create_with_preset_resolution(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch)
{
auto device_or_error = DeviceManagement::try_create_device<GenericDisplayConnector>(framebuffer_address, width, height, pitch);
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;
}
GenericDisplayConnector::GenericDisplayConnector(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch)
: DisplayConnector(framebuffer_address, height * pitch, true)
{
m_current_mode_setting.horizontal_active = width;
m_current_mode_setting.vertical_active = height;
m_current_mode_setting.horizontal_stride = pitch;
}
ErrorOr<void> GenericDisplayConnector::create_attached_framebuffer_console()
{
auto width = m_current_mode_setting.horizontal_active;
auto height = m_current_mode_setting.vertical_active;
auto pitch = m_current_mode_setting.horizontal_stride;
m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), width, height, pitch);
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
void GenericDisplayConnector::enable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_framebuffer_console);
m_framebuffer_console->enable();
}
void GenericDisplayConnector::disable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_framebuffer_console);
m_framebuffer_console->disable();
}
ErrorOr<void> GenericDisplayConnector::flush_first_surface()
{
return Error::from_errno(ENOTSUP);
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Try.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class GenericDisplayConnector
: public DisplayConnector {
friend class DeviceManagement;
public:
static NonnullLockRefPtr<GenericDisplayConnector> must_create_with_preset_resolution(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch);
protected:
ErrorOr<void> create_attached_framebuffer_console();
GenericDisplayConnector(PhysicalAddress framebuffer_address, size_t width, size_t height, size_t pitch);
virtual bool mutable_mode_setting_capable() const override final { return false; }
virtual bool double_framebuffering_capable() const override { return false; }
virtual ErrorOr<void> set_mode_setting(ModeSetting const&) override { return Error::from_errno(ENOTSUP); }
virtual ErrorOr<void> set_safe_mode_setting() override { return {}; }
virtual ErrorOr<void> set_y_offset(size_t) override { return Error::from_errno(ENOTSUP); }
virtual ErrorOr<void> unblank() override { return Error::from_errno(ENOTSUP); }
virtual bool partial_flush_support() const override final { return false; }
virtual bool flush_support() const override final { return false; }
// Note: This is "possibly" a paravirtualized hardware, but since we don't know, we assume there's no refresh rate...
// We rely on the BIOS and/or the bootloader to initialize the hardware for us, so we don't really care about
// the specific implementation and settings that were chosen with the given hardware as long as we just
// have a dummy framebuffer to work with.
virtual bool refresh_rate_support() const override final { return false; }
virtual ErrorOr<void> flush_first_surface() override final;
virtual void enable_console() override final;
virtual void disable_console() override final;
LockRefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console;
};
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Bus/PCI/Definitions.h>
#include <Kernel/Devices/BlockDevice.h>
#include <Kernel/Library/LockWeakable.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
class GenericGraphicsAdapter
: public AtomicRefCounted<GenericGraphicsAdapter>
, public LockWeakable<GenericGraphicsAdapter> {
public:
virtual ~GenericGraphicsAdapter() = default;
protected:
GenericGraphicsAdapter() = default;
};
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
enum class GMBusStatus {
TransactionCompletion,
HardwareReady
};
enum GMBusCycle {
Wait = 1,
Stop = 4,
};
ErrorOr<NonnullOwnPtr<GMBusConnector>> GMBusConnector::create_with_physical_address(PhysicalAddress gmbus_start_address)
{
auto registers_mapping = TRY(map_typed<GMBusRegisters volatile>(gmbus_start_address, sizeof(GMBusRegisters), Memory::Region::Access::ReadWrite));
return adopt_nonnull_own_or_enomem(new (nothrow) GMBusConnector(move(registers_mapping)));
}
GMBusConnector::GMBusConnector(Memory::TypedMapping<GMBusRegisters volatile> registers_mapping)
: m_gmbus_registers(move(registers_mapping))
{
set_default_rate();
set_pin_pair(PinPair::DedicatedAnalog);
}
bool GMBusConnector::wait_for(GMBusStatus desired_status, size_t milliseconds_timeout)
{
VERIFY(m_access_lock.is_locked());
size_t milliseconds_passed = 0;
while (1) {
if (milliseconds_timeout < milliseconds_passed)
return false;
full_memory_barrier();
u32 status = m_gmbus_registers->status;
full_memory_barrier();
VERIFY(!(status & (1 << 10))); // error happened
switch (desired_status) {
case GMBusStatus::HardwareReady:
if (status & (1 << 11))
return true;
break;
case GMBusStatus::TransactionCompletion:
if (status & (1 << 14))
return true;
break;
default:
VERIFY_NOT_REACHED();
}
microseconds_delay(1000);
milliseconds_passed++;
}
}
ErrorOr<void> GMBusConnector::write(unsigned address, u32 data)
{
VERIFY(address < 256);
SpinlockLocker locker(m_access_lock);
full_memory_barrier();
m_gmbus_registers->data = data;
full_memory_barrier();
m_gmbus_registers->command = ((address << 1) | (1 << 16) | (GMBusCycle::Wait << 25) | (1 << 30));
full_memory_barrier();
if (!wait_for(GMBusStatus::TransactionCompletion, 250))
return Error::from_errno(EBUSY);
return {};
}
void GMBusConnector::set_default_rate()
{
// FIXME: Verify GMBUS Rate Select is set only when GMBUS is idle
SpinlockLocker locker(m_access_lock);
// Set the rate to 100KHz
m_gmbus_registers->clock = m_gmbus_registers->clock & ~(0b111 << 8);
}
void GMBusConnector::set_pin_pair(PinPair pin_pair)
{
// FIXME: Verify GMBUS is idle
SpinlockLocker locker(m_access_lock);
m_gmbus_registers->clock = (m_gmbus_registers->clock & (~0b111)) | (pin_pair & 0b111);
}
ErrorOr<void> GMBusConnector::read(unsigned address, u8* buf, size_t length)
{
VERIFY(address < 256);
SpinlockLocker locker(m_access_lock);
size_t nread = 0;
auto read_set = [&] {
full_memory_barrier();
u32 data = m_gmbus_registers->data;
full_memory_barrier();
for (size_t index = 0; index < 4; index++) {
if (nread == length)
break;
buf[nread] = (data >> (8 * index)) & 0xFF;
nread++;
}
};
full_memory_barrier();
m_gmbus_registers->command = (1 | (address << 1) | (length << 16) | (GMBusCycle::Wait << 25) | (1 << 30));
full_memory_barrier();
while (nread < length) {
if (!wait_for(GMBusStatus::HardwareReady, 250))
return Error::from_errno(EBUSY);
read_set();
}
if (!wait_for(GMBusStatus::TransactionCompletion, 250))
return Error::from_errno(EBUSY);
return {};
}
}

View file

@ -0,0 +1,53 @@
/*
* 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 <AK/Types.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
struct [[gnu::packed]] GMBusRegisters {
u32 clock;
u32 command;
u32 status;
u32 data;
};
enum class GMBusStatus;
class GMBusConnector {
public:
enum PinPair : u8 {
None = 0,
DedicatedControl = 1,
DedicatedAnalog = 0b10,
IntegratedDigital = 0b11,
sDVO = 0b101,
Dconnector = 0b111,
};
public:
static ErrorOr<NonnullOwnPtr<GMBusConnector>> create_with_physical_address(PhysicalAddress gmbus_start_address);
ErrorOr<void> write(unsigned address, u32 data);
ErrorOr<void> read(unsigned address, u8* buf, size_t length);
void set_default_rate();
private:
void set_pin_pair(PinPair pin_pair);
bool wait_for(GMBusStatus desired_status, size_t milliseconds_timeout);
explicit GMBusConnector(Memory::TypedMapping<GMBusRegisters volatile>);
Spinlock<LockRank::None> m_access_lock;
Memory::TypedMapping<GMBusRegisters volatile> m_gmbus_registers;
};
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel::IntelGraphics {
enum class Generation {
Gen4,
Gen9,
};
struct PLLSettings;
struct PLLParameterLimit {
size_t min, max;
};
struct PLLMaxSettings {
PLLParameterLimit dot_clock, vco, n, m, m1, m2, p, p1, p2;
};
struct PLLSettings {
bool is_valid() const { return (n != 0 && m1 != 0 && m2 != 0 && p1 != 0 && p2 != 0); }
u64 compute_dot_clock(u64 refclock) const
{
return (refclock * (5 * m1 + m2) / n) / (p1 * p2);
}
u64 compute_vco(u64 refclock) const
{
return refclock * (5 * m1 + m2) / n;
}
u64 compute_m() const
{
return 5 * m1 + m2;
}
u64 compute_p() const
{
return p1 * p2;
}
u64 n { 0 };
u64 m1 { 0 };
u64 m2 { 0 };
u64 p1 { 0 };
u64 p2 { 0 };
};
}

View file

@ -0,0 +1,278 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Debug.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Intel/DisplayConnectorGroup.h>
#include <Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/AnalogDisplayTranscoder.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/PLL.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Memory/Region.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
ErrorOr<NonnullLockRefPtr<IntelDisplayConnectorGroup>> IntelDisplayConnectorGroup::try_create(Badge<IntelNativeGraphicsAdapter>, IntelGraphics::Generation generation, MMIORegion const& first_region, MMIORegion const& second_region)
{
auto registers_region = TRY(MM.allocate_kernel_region(first_region.pci_bar_paddr, first_region.pci_bar_space_length, "Intel Native Graphics Registers"sv, Memory::Region::Access::ReadWrite));
// NOTE: 0x5100 is the offset of the start of the GMBus registers
auto gmbus_connector = TRY(GMBusConnector::create_with_physical_address(first_region.pci_bar_paddr.offset(0x5100)));
auto connector_group = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) IntelDisplayConnectorGroup(generation, move(gmbus_connector), move(registers_region), first_region, second_region)));
TRY(connector_group->initialize_connectors());
return connector_group;
}
IntelDisplayConnectorGroup::IntelDisplayConnectorGroup(IntelGraphics::Generation generation, NonnullOwnPtr<GMBusConnector> gmbus_connector, NonnullOwnPtr<Memory::Region> registers_region, MMIORegion const& first_region, MMIORegion const& second_region)
: m_mmio_first_region(first_region)
, m_mmio_second_region(second_region)
, m_assigned_mmio_registers_region(m_mmio_first_region)
, m_generation(generation)
, m_registers_region(move(registers_region))
, m_gmbus_connector(move(gmbus_connector))
{
}
ErrorOr<void> IntelDisplayConnectorGroup::initialize_gen4_connectors()
{
// NOTE: Just assume we will need one Gen4 "transcoder"
// NOTE: Main block of registers starting at HorizontalTotalA register (0x60000)
auto transcoder_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x60000);
// NOTE: Main block of Pipe registers starting at PipeA_DSL register (0x70000)
auto pipe_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x70000);
// NOTE: DPLL registers starting at DPLLDivisorA0 register (0x6040)
auto dpll_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x6040);
// NOTE: DPLL A control registers starting at 0x6014 (DPLL A Control register),
// DPLL A Multiplier is at 0x601C, between them (at 0x6018) there is the DPLL B Control register.
auto dpll_control_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x6014);
m_transcoders[0] = TRY(IntelAnalogDisplayTranscoder::create_with_physical_addresses(transcoder_registers_paddr, pipe_registers_paddr, dpll_registers_paddr, dpll_control_registers_paddr));
m_planes[0] = TRY(IntelG33DisplayPlane::create_with_physical_address(m_mmio_first_region.pci_bar_paddr.offset(0x70180)));
Array<u8, 128> crt_edid_bytes {};
{
SpinlockLocker control_lock(m_control_lock);
TRY(m_gmbus_connector->write(Graphics::ddc2_i2c_address, 0));
TRY(m_gmbus_connector->read(Graphics::ddc2_i2c_address, crt_edid_bytes.data(), crt_edid_bytes.size()));
}
m_connectors[0] = TRY(IntelNativeDisplayConnector::try_create_with_display_connector_group(*this, IntelNativeDisplayConnector::ConnectorIndex::PortA, IntelNativeDisplayConnector::Type::Analog, m_mmio_second_region.pci_bar_paddr, m_mmio_second_region.pci_bar_space_length));
m_connectors[0]->set_edid_bytes({}, crt_edid_bytes);
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::initialize_connectors()
{
// NOTE: Intel Graphics Generation 4 is pretty ancient beast, and we should not
// assume we can find a VBT for it. Just initialize the (assumed) CRT connector and be done with it.
if (m_generation == IntelGraphics::Generation::Gen4) {
TRY(initialize_gen4_connectors());
} else {
VERIFY_NOT_REACHED();
}
for (size_t connector_index = 0; connector_index < m_connectors.size(); connector_index++) {
if (!m_connectors[connector_index])
continue;
if (!m_connectors[connector_index]->m_edid_valid)
continue;
TRY(m_connectors[connector_index]->set_safe_mode_setting());
TRY(m_connectors[connector_index]->create_attached_framebuffer_console({}));
}
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::set_safe_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector& connector)
{
VERIFY(connector.m_modeset_lock.is_locked());
if (!connector.m_edid_parser.has_value())
return Error::from_errno(ENOTSUP);
if (!connector.m_edid_parser.value().detailed_timing(0).has_value())
return Error::from_errno(ENOTSUP);
auto details = connector.m_edid_parser.value().detailed_timing(0).release_value();
DisplayConnector::ModeSetting modesetting {
// Note: We assume that we always use 32 bit framebuffers.
.horizontal_stride = details.horizontal_addressable_pixels() * sizeof(u32),
.pixel_clock_in_khz = details.pixel_clock_khz(),
.horizontal_active = details.horizontal_addressable_pixels(),
.horizontal_front_porch_pixels = details.horizontal_front_porch_pixels(),
.horizontal_sync_time_pixels = details.horizontal_sync_pulse_width_pixels(),
.horizontal_blank_pixels = details.horizontal_blanking_pixels(),
.vertical_active = details.vertical_addressable_lines(),
.vertical_front_porch_lines = details.vertical_front_porch_lines(),
.vertical_sync_time_lines = details.vertical_sync_pulse_width_lines(),
.vertical_blank_lines = details.vertical_blanking_lines(),
.horizontal_offset = 0,
.vertical_offset = 0,
};
return set_mode_setting(connector, modesetting);
}
ErrorOr<void> IntelDisplayConnectorGroup::set_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
return set_mode_setting(connector, mode_setting);
}
ErrorOr<void> IntelDisplayConnectorGroup::set_mode_setting(IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(connector.m_modeset_lock.is_locked());
VERIFY(to_underlying(connector.connector_index()) < m_connectors.size());
VERIFY(&connector == m_connectors[to_underlying(connector.connector_index())].ptr());
DisplayConnector::ModeSetting actual_mode_setting = mode_setting;
actual_mode_setting.horizontal_stride = actual_mode_setting.horizontal_active * sizeof(u32);
VERIFY(actual_mode_setting.horizontal_stride != 0);
if (m_generation == IntelGraphics::Generation::Gen4) {
TRY(set_gen4_mode_setting(connector, actual_mode_setting));
} else {
VERIFY_NOT_REACHED();
}
connector.m_current_mode_setting = actual_mode_setting;
if (!connector.m_framebuffer_console.is_null())
static_cast<Graphics::GenericFramebufferConsoleImpl*>(connector.m_framebuffer_console.ptr())->set_resolution(actual_mode_setting.horizontal_active, actual_mode_setting.vertical_active, actual_mode_setting.horizontal_stride);
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::set_gen4_mode_setting(IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(connector.m_modeset_lock.is_locked());
SpinlockLocker control_lock(m_control_lock);
SpinlockLocker modeset_lock(m_modeset_lock);
if (!set_crt_resolution(mode_setting))
return Error::from_errno(ENOTSUP);
return {};
}
void IntelDisplayConnectorGroup::enable_vga_plane()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
}
StringView IntelDisplayConnectorGroup::convert_analog_output_register_to_string(AnalogOutputRegisterOffset index) const
{
switch (index) {
case AnalogOutputRegisterOffset::AnalogDisplayPort:
return "AnalogDisplayPort"sv;
case AnalogOutputRegisterOffset::VGADisplayPlaneControl:
return "VGADisplayPlaneControl"sv;
default:
VERIFY_NOT_REACHED();
}
}
void IntelDisplayConnectorGroup::write_to_general_register(RegisterOffset offset, u32 value)
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(offset.value()).as_ptr();
*reg = value;
}
u32 IntelDisplayConnectorGroup::read_from_general_register(RegisterOffset offset) const
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(offset.value()).as_ptr();
u32 value = *reg;
return value;
}
void IntelDisplayConnectorGroup::write_to_analog_output_register(AnalogOutputRegisterOffset index, u32 value)
{
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector:: Write to {} value of {:x}", convert_analog_output_register_to_string(index), value);
write_to_general_register(to_underlying(index), value);
}
u32 IntelDisplayConnectorGroup::read_from_analog_output_register(AnalogOutputRegisterOffset index) const
{
u32 value = read_from_general_register(to_underlying(index));
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector: Read from {} value of {:x}", convert_analog_output_register_to_string(index), value);
return value;
}
static size_t compute_dac_multiplier(size_t pixel_clock_in_khz)
{
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel native graphics: Pixel clock is {} KHz", pixel_clock_in_khz);
VERIFY(pixel_clock_in_khz >= 25000);
if (pixel_clock_in_khz >= 100000) {
return 1;
} else if (pixel_clock_in_khz >= 50000) {
return 2;
} else {
return 4;
}
}
bool IntelDisplayConnectorGroup::set_crt_resolution(DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
// Note: Just in case we still allow access to VGA IO ports, disable it now.
GraphicsManagement::the().disable_vga_emulation_access_permanently();
auto dac_multiplier = compute_dac_multiplier(mode_setting.pixel_clock_in_khz);
auto pll_settings = create_pll_settings(m_generation, (1000 * mode_setting.pixel_clock_in_khz * dac_multiplier), 96'000'000);
if (!pll_settings.has_value())
return false;
auto settings = pll_settings.value();
disable_dac_output();
MUST(m_planes[0]->disable({}));
MUST(m_transcoders[0]->disable_pipe({}));
MUST(m_transcoders[0]->disable_dpll({}));
disable_vga_emulation();
dbgln_if(INTEL_GRAPHICS_DEBUG, "PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2);
MUST(m_transcoders[0]->set_dpll_settings({}, settings, dac_multiplier));
MUST(m_transcoders[0]->disable_dpll({}));
MUST(m_transcoders[0]->enable_dpll_without_vga({}));
MUST(m_transcoders[0]->set_mode_setting_timings({}, mode_setting));
VERIFY(!m_transcoders[0]->pipe_enabled({}));
MUST(m_transcoders[0]->enable_pipe({}));
MUST(m_planes[0]->set_aperture_base({}, m_mmio_second_region.pci_bar_paddr));
MUST(m_planes[0]->set_pipe({}, IntelDisplayPlane::PipeSelect::PipeA));
MUST(m_planes[0]->set_horizontal_stride({}, mode_setting.horizontal_active * 4));
MUST(m_planes[0]->set_horizontal_active_pixels_count({}, mode_setting.horizontal_active));
// Note: This doesn't affect anything on the plane settings for Gen4, but we still
// do it for the sake of "completeness".
MUST(m_planes[0]->set_vertical_active_pixels_count({}, mode_setting.vertical_active));
MUST(m_planes[0]->enable({}));
enable_dac_output();
return true;
}
void IntelDisplayConnectorGroup::disable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::AnalogDisplayPort, 0b11 << 10);
}
void IntelDisplayConnectorGroup::enable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::AnalogDisplayPort, (1 << 31));
}
void IntelDisplayConnectorGroup::disable_vga_emulation()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::VGADisplayPlaneControl, (1 << 31));
read_from_analog_output_register(AnalogOutputRegisterOffset::VGADisplayPlaneControl);
}
}

View file

@ -0,0 +1,97 @@
/*
* 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/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.h>
#include <Kernel/Devices/GPU/Intel/Definitions.h>
#include <Kernel/Devices/GPU/Intel/NativeDisplayConnector.h>
#include <Kernel/Devices/GPU/Intel/Plane/DisplayPlane.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Memory/TypedMapping.h>
#include <LibEDID/EDID.h>
namespace Kernel {
class IntelNativeGraphicsAdapter;
class IntelDisplayConnectorGroup : public RefCounted<IntelDisplayConnectorGroup> {
friend class IntelNativeGraphicsAdapter;
public:
struct MMIORegion {
enum class BARAssigned {
BAR0,
BAR2,
};
BARAssigned pci_bar_assigned;
PhysicalAddress pci_bar_paddr;
size_t pci_bar_space_length;
};
private:
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, RegisterOffset);
enum class AnalogOutputRegisterOffset {
AnalogDisplayPort = 0x61100,
VGADisplayPlaneControl = 0x71400,
};
public:
static ErrorOr<NonnullLockRefPtr<IntelDisplayConnectorGroup>> try_create(Badge<IntelNativeGraphicsAdapter>, IntelGraphics::Generation, MMIORegion const&, MMIORegion const&);
ErrorOr<void> set_safe_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector&);
ErrorOr<void> set_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
private:
IntelDisplayConnectorGroup(IntelGraphics::Generation generation, NonnullOwnPtr<GMBusConnector>, NonnullOwnPtr<Memory::Region> registers_region, MMIORegion const&, MMIORegion const&);
ErrorOr<void> set_mode_setting(IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
StringView convert_analog_output_register_to_string(AnalogOutputRegisterOffset index) const;
void write_to_analog_output_register(AnalogOutputRegisterOffset, u32 value);
u32 read_from_analog_output_register(AnalogOutputRegisterOffset) const;
void write_to_general_register(RegisterOffset offset, u32 value);
u32 read_from_general_register(RegisterOffset offset) const;
// DisplayConnector initialization related methods
ErrorOr<void> initialize_connectors();
ErrorOr<void> initialize_gen4_connectors();
// General Modesetting methods
ErrorOr<void> set_gen4_mode_setting(IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
bool set_crt_resolution(DisplayConnector::ModeSetting const&);
void disable_vga_emulation();
void enable_vga_plane();
void disable_dac_output();
void enable_dac_output();
Spinlock<LockRank::None> m_control_lock;
Spinlock<LockRank::None> m_modeset_lock;
mutable Spinlock<LockRank::None> m_registers_lock;
// Note: The linux driver specifies an enum of possible ports and there is only
// 9 ports (PORT_{A-I}). PORT_TC{1-6} are mapped to PORT_{D-I}.
Array<LockRefPtr<IntelNativeDisplayConnector>, 9> m_connectors;
Array<OwnPtr<IntelDisplayTranscoder>, 5> m_transcoders;
Array<OwnPtr<IntelDisplayPlane>, 3> m_planes;
const MMIORegion m_mmio_first_region;
const MMIORegion m_mmio_second_region;
MMIORegion const& m_assigned_mmio_registers_region;
const IntelGraphics::Generation m_generation;
NonnullOwnPtr<Memory::Region> m_registers_region;
NonnullOwnPtr<GMBusConnector> m_gmbus_connector;
};
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Debug.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Intel/DisplayConnectorGroup.h>
#include <Kernel/Devices/GPU/Intel/NativeDisplayConnector.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Memory/Region.h>
namespace Kernel {
ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> IntelNativeDisplayConnector::try_create_with_display_connector_group(IntelDisplayConnectorGroup const& parent_connector_group, ConnectorIndex connector_index, Type type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
{
return TRY(DeviceManagement::try_create_device<IntelNativeDisplayConnector>(parent_connector_group, connector_index, type, framebuffer_address, framebuffer_resource_size));
}
ErrorOr<void> IntelNativeDisplayConnector::create_attached_framebuffer_console(Badge<IntelDisplayConnectorGroup>)
{
size_t width = 0;
size_t height = 0;
size_t pitch = 0;
{
SpinlockLocker control_locker(m_control_lock);
SpinlockLocker mode_set_locker(m_modeset_lock);
width = m_current_mode_setting.horizontal_active;
height = m_current_mode_setting.vertical_active;
pitch = m_current_mode_setting.horizontal_stride;
}
m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), width, height, pitch);
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
IntelNativeDisplayConnector::IntelNativeDisplayConnector(IntelDisplayConnectorGroup const& parent_connector_group, ConnectorIndex connector_index, Type type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, true)
, m_type(type)
, m_connector_index(connector_index)
, m_parent_connector_group(parent_connector_group)
{
}
void IntelNativeDisplayConnector::set_edid_bytes(Badge<IntelDisplayConnectorGroup>, Array<u8, 128> const& raw_bytes)
{
// Note: The provided EDID might be invalid (because there's no attached monitor)
// Therefore, set might_be_invalid to true to indicate that.
DisplayConnector::set_edid_bytes(raw_bytes, true);
}
ErrorOr<void> IntelNativeDisplayConnector::set_y_offset(size_t)
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> IntelNativeDisplayConnector::unblank()
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> IntelNativeDisplayConnector::set_safe_mode_setting()
{
SpinlockLocker locker(m_modeset_lock);
return m_parent_connector_group->set_safe_mode_setting({}, *this);
}
void IntelNativeDisplayConnector::enable_console()
{
VERIFY(m_control_lock.is_locked());
if (m_framebuffer_console)
m_framebuffer_console->enable();
}
void IntelNativeDisplayConnector::disable_console()
{
VERIFY(m_control_lock.is_locked());
if (m_framebuffer_console)
m_framebuffer_console->disable();
}
ErrorOr<void> IntelNativeDisplayConnector::flush_first_surface()
{
return Error::from_errno(ENOTSUP);
}
ErrorOr<void> IntelNativeDisplayConnector::set_mode_setting(DisplayConnector::ModeSetting const&)
{
return Error::from_errno(ENOTIMPL);
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Try.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/Definitions.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelNativeDisplayConnector final
: public DisplayConnector {
friend class IntelDisplayConnectorGroup;
friend class DeviceManagement;
public:
enum class Type {
Invalid,
Analog,
DVO,
LVDS,
TVOut,
HDMI,
DisplayPort,
EmbeddedDisplayPort,
};
enum class ConnectorIndex : size_t {
PortA = 0,
PortB = 1,
PortC = 2,
PortD = 3,
PortE = 4,
PortF = 5,
PortH = 6,
PortG = 7,
PortI = 8,
};
static ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> try_create_with_display_connector_group(IntelDisplayConnectorGroup const&, ConnectorIndex, Type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
void set_edid_bytes(Badge<IntelDisplayConnectorGroup>, Array<u8, 128> const& edid_bytes);
ErrorOr<void> create_attached_framebuffer_console(Badge<IntelDisplayConnectorGroup>);
ConnectorIndex connector_index() const { return m_connector_index; }
private:
// ^DisplayConnector
// FIXME: Implement modesetting capabilities in runtime from userland...
virtual bool mutable_mode_setting_capable() const override { return false; }
// FIXME: Implement double buffering capabilities in runtime from userland...
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 ErrorOr<void> flush_first_surface() override final;
virtual void enable_console() override;
virtual void disable_console() override;
virtual bool partial_flush_support() const override { return false; }
virtual bool flush_support() const override { return false; }
// Note: Paravirtualized hardware doesn't require a defined refresh rate for modesetting.
virtual bool refresh_rate_support() const override { return true; }
IntelNativeDisplayConnector(IntelDisplayConnectorGroup const&, ConnectorIndex connector_index, Type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
Type const m_type { Type::Analog };
ConnectorIndex const m_connector_index { 0 };
NonnullLockRefPtr<IntelDisplayConnectorGroup> m_parent_connector_group;
LockRefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console;
};
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Definitions.h>
#include <Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
static constexpr u16 supported_models[] {
0x29c2, // Intel G35 Adapter
};
static bool is_supported_model(u16 device_id)
{
for (auto& id : supported_models) {
if (id == device_id)
return true;
}
return false;
}
ErrorOr<bool> IntelNativeGraphicsAdapter::probe(PCI::DeviceIdentifier const& pci_device_identifier)
{
return is_supported_model(pci_device_identifier.hardware_id().device_id);
}
ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> IntelNativeGraphicsAdapter::create(PCI::DeviceIdentifier const& pci_device_identifier)
{
auto adapter = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) IntelNativeGraphicsAdapter(pci_device_identifier)));
TRY(adapter->initialize_adapter());
return adapter;
}
ErrorOr<void> IntelNativeGraphicsAdapter::initialize_adapter()
{
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Native Graphics Adapter @ {}", device_identifier().address());
auto bar0_space_size = PCI::get_BAR_space_size(device_identifier(), PCI::HeaderType0BaseRegister::BAR0);
auto bar2_space_size = PCI::get_BAR_space_size(device_identifier(), PCI::HeaderType0BaseRegister::BAR2);
dmesgln_pci(*this, "MMIO @ {}, space size is {:x} bytes", PhysicalAddress(PCI::get_BAR0(device_identifier())), bar0_space_size);
dmesgln_pci(*this, "framebuffer @ {}", PhysicalAddress(PCI::get_BAR2(device_identifier())));
using MMIORegion = IntelDisplayConnectorGroup::MMIORegion;
MMIORegion first_region { MMIORegion::BARAssigned::BAR0, PhysicalAddress(PCI::get_BAR0(device_identifier()) & PCI::bar_address_mask), bar0_space_size };
MMIORegion second_region { MMIORegion::BARAssigned::BAR2, PhysicalAddress(PCI::get_BAR2(device_identifier()) & PCI::bar_address_mask), bar2_space_size };
PCI::enable_bus_mastering(device_identifier());
PCI::enable_io_space(device_identifier());
PCI::enable_memory_space(device_identifier());
switch (device_identifier().hardware_id().device_id) {
case 0x29c2:
m_connector_group = TRY(IntelDisplayConnectorGroup::try_create({}, IntelGraphics::Generation::Gen4, first_region, second_region));
return {};
default:
return Error::from_errno(ENODEV);
}
}
IntelNativeGraphicsAdapter::IntelNativeGraphicsAdapter(PCI::DeviceIdentifier const& pci_device_identifier)
: GenericGraphicsAdapter()
, PCI::Device(const_cast<PCI::DeviceIdentifier&>(pci_device_identifier))
{
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Devices/GPU/Definitions.h>
#include <Kernel/Devices/GPU/Intel/DisplayConnectorGroup.h>
#include <Kernel/Devices/GPU/Intel/NativeDisplayConnector.h>
#include <Kernel/Memory/PhysicalAddress.h>
#include <LibEDID/EDID.h>
namespace Kernel {
class IntelNativeGraphicsAdapter final
: public GenericGraphicsAdapter
, public PCI::Device {
public:
static ErrorOr<bool> probe(PCI::DeviceIdentifier const&);
static ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> create(PCI::DeviceIdentifier const&);
virtual ~IntelNativeGraphicsAdapter() = default;
virtual StringView device_name() const override { return "IntelNativeGraphicsAdapter"sv; }
private:
ErrorOr<void> initialize_adapter();
explicit IntelNativeGraphicsAdapter(PCI::DeviceIdentifier const&);
LockRefPtr<IntelDisplayConnectorGroup> m_connector_group;
};
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Intel/Plane/DisplayPlane.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
IntelDisplayPlane::IntelDisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> plane_registers_mapping)
: m_plane_registers(move(plane_registers_mapping))
{
}
IntelDisplayPlane::ShadowRegisters IntelDisplayPlane::shadow_registers() const
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers;
}
ErrorOr<void> IntelDisplayPlane::set_horizontal_active_pixels_count(Badge<IntelDisplayConnectorGroup>, size_t horizontal_active_pixels_count)
{
SpinlockLocker locker(m_access_lock);
m_horizontal_active_pixels_count = horizontal_active_pixels_count;
return {};
}
ErrorOr<void> IntelDisplayPlane::set_vertical_active_pixels_count(Badge<IntelDisplayConnectorGroup>, size_t vertical_active_pixels_count)
{
SpinlockLocker locker(m_access_lock);
m_vertical_active_pixels_count = vertical_active_pixels_count;
return {};
}
ErrorOr<void> IntelDisplayPlane::set_horizontal_stride(Badge<IntelDisplayConnectorGroup>, size_t horizontal_stride)
{
SpinlockLocker locker(m_access_lock);
m_horizontal_stride = horizontal_stride;
return {};
}
ErrorOr<void> IntelDisplayPlane::set_aperture_base(Badge<IntelDisplayConnectorGroup>, PhysicalAddress aperture_start)
{
SpinlockLocker locker(m_access_lock);
m_aperture_start.set(aperture_start.get());
return {};
}
ErrorOr<void> IntelDisplayPlane::set_pipe(Badge<IntelDisplayConnectorGroup>, PipeSelect pipe_select)
{
SpinlockLocker locker(m_access_lock);
m_pipe_select = pipe_select;
return {};
}
bool IntelDisplayPlane::is_enabled(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers.control & (1 << 31);
}
ErrorOr<void> IntelDisplayPlane::disable(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
// Note: We use the shadow register so we don't have the already set
// settings being lost.
m_shadow_registers.control &= ~(1 << 31);
m_plane_registers->control = m_shadow_registers.control;
return {};
}
}

View file

@ -0,0 +1,77 @@
/*
* 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 <AK/Types.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/Intel/Definitions.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelDisplayPlane {
public:
enum class PipeSelect {
PipeA,
PipeB,
PipeC,
PipeD,
};
// Note: This is used to "cache" all the registers we wrote to, because
// we might not be able to read them directly from hardware later.
struct ShadowRegisters {
u32 control;
u32 linear_offset;
u32 stride;
u32 surface_base;
};
public:
static ErrorOr<NonnullOwnPtr<IntelDisplayPlane>> create_with_physical_address(PhysicalAddress plane_registers_start_address);
ErrorOr<void> set_horizontal_active_pixels_count(Badge<IntelDisplayConnectorGroup>, size_t horizontal_active_pixels_count);
ErrorOr<void> set_vertical_active_pixels_count(Badge<IntelDisplayConnectorGroup>, size_t vertical_active_pixels_count);
ErrorOr<void> set_horizontal_stride(Badge<IntelDisplayConnectorGroup>, size_t horizontal_stride);
ErrorOr<void> set_aperture_base(Badge<IntelDisplayConnectorGroup>, PhysicalAddress aperture_start);
ErrorOr<void> set_pipe(Badge<IntelDisplayConnectorGroup>, PipeSelect);
virtual ErrorOr<void> enable(Badge<IntelDisplayConnectorGroup>) = 0;
bool is_enabled(Badge<IntelDisplayConnectorGroup>);
ErrorOr<void> disable(Badge<IntelDisplayConnectorGroup>);
ShadowRegisters shadow_registers() const;
virtual ~IntelDisplayPlane() = default;
protected:
struct [[gnu::packed]] PlaneRegisters {
u32 control;
u32 linear_offset;
u32 stride;
u8 padding[24]; // Note: This might contain other registers, don't touch them.
u32 surface_base;
};
explicit IntelDisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> registers_mapping);
mutable Spinlock<LockRank::None> m_access_lock;
ShadowRegisters m_shadow_registers {};
Memory::TypedMapping<PlaneRegisters volatile> m_plane_registers;
// Note: The PipeSelect value is used only in planes until Skylake graphics.
PipeSelect m_pipe_select { PipeSelect::PipeA };
PhysicalAddress m_aperture_start;
size_t m_horizontal_stride { 0 };
size_t m_horizontal_active_pixels_count { 0 };
size_t m_vertical_active_pixels_count { 0 };
};
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
ErrorOr<NonnullOwnPtr<IntelG33DisplayPlane>> IntelG33DisplayPlane::create_with_physical_address(PhysicalAddress plane_registers_start_address)
{
auto registers_mapping = TRY(Memory::map_typed<PlaneRegisters volatile>(plane_registers_start_address, sizeof(PlaneRegisters), Memory::Region::Access::ReadWrite));
return adopt_nonnull_own_or_enomem(new (nothrow) IntelG33DisplayPlane(move(registers_mapping)));
}
IntelG33DisplayPlane::IntelG33DisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> registers_mapping)
: IntelDisplayPlane(move(registers_mapping))
{
}
ErrorOr<void> IntelG33DisplayPlane::enable(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
VERIFY(((m_horizontal_active_pixels_count * 4) % 64 == 0));
VERIFY(((m_horizontal_stride) % 64 == 0));
u32 control_value = 0;
switch (m_pipe_select) {
case PipeSelect::PipeA:
control_value |= (0b00 << 24);
break;
case PipeSelect::PipeB:
control_value |= (0b01 << 24);
break;
case PipeSelect::PipeC:
control_value |= (0b10 << 24);
break;
case PipeSelect::PipeD:
control_value |= (0b11 << 24);
break;
}
// Note: Set the plane to work with 32 bit BGRX (Ignore Alpha channel).
// Note: Bit 31 is set to turn on the plane.
control_value |= (0b0110 << 26) | (1 << 31);
m_plane_registers->stride = m_horizontal_stride;
m_shadow_registers.stride = m_horizontal_stride;
m_plane_registers->linear_offset = 0;
m_shadow_registers.linear_offset = 0;
m_plane_registers->surface_base = m_aperture_start.get();
m_shadow_registers.surface_base = m_aperture_start.get();
m_plane_registers->control = control_value;
m_shadow_registers.control = control_value;
return {};
}
}

View file

@ -0,0 +1,26 @@
/*
* 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 <AK/Types.h>
#include <Kernel/Devices/GPU/Intel/Plane/DisplayPlane.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelG33DisplayPlane final : public IntelDisplayPlane {
public:
static ErrorOr<NonnullOwnPtr<IntelG33DisplayPlane>> create_with_physical_address(PhysicalAddress plane_registers_start_address);
virtual ErrorOr<void> enable(Badge<IntelDisplayConnectorGroup>) override;
private:
explicit IntelG33DisplayPlane(Memory::TypedMapping<volatile IntelDisplayPlane::PlaneRegisters> plane_registers_mapping);
};
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/AnalogDisplayTranscoder.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
ErrorOr<NonnullOwnPtr<IntelAnalogDisplayTranscoder>> IntelAnalogDisplayTranscoder::create_with_physical_addresses(PhysicalAddress transcoder_registers_start_address,
PhysicalAddress pipe_registers_start_address, PhysicalAddress dpll_registers_start_address, PhysicalAddress dpll_multiplier_register_start_address)
{
auto transcoder_registers_mapping = TRY(Memory::map_typed<TranscoderRegisters volatile>(transcoder_registers_start_address, sizeof(IntelDisplayTranscoder::TranscoderRegisters), Memory::Region::Access::ReadWrite));
auto pipe_registers_mapping = TRY(Memory::map_typed<PipeRegisters volatile>(pipe_registers_start_address, sizeof(IntelDisplayTranscoder::PipeRegisters), Memory::Region::Access::ReadWrite));
auto dpll_registers_mapping = TRY(Memory::map_typed<DPLLRegisters volatile>(dpll_registers_start_address, sizeof(DPLLRegisters), Memory::Region::Access::ReadWrite));
auto dpll_control_mapping = TRY(Memory::map_typed<DPLLControlRegisters volatile>(dpll_multiplier_register_start_address, sizeof(DPLLControlRegisters), Memory::Region::Access::ReadWrite));
return adopt_nonnull_own_or_enomem(new (nothrow) IntelAnalogDisplayTranscoder(move(transcoder_registers_mapping), move(pipe_registers_mapping), move(dpll_registers_mapping), move(dpll_control_mapping)));
}
IntelAnalogDisplayTranscoder::IntelAnalogDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile> transcoder_registers_mapping,
Memory::TypedMapping<PipeRegisters volatile> pipe_registers_mapping, Memory::TypedMapping<DPLLRegisters volatile> dpll_registers_mapping, Memory::TypedMapping<DPLLControlRegisters volatile> dpll_control_registers)
: IntelDisplayTranscoder(move(transcoder_registers_mapping), move(pipe_registers_mapping))
, m_dpll_registers(move(dpll_registers_mapping))
, m_dpll_control_registers(move(dpll_control_registers))
{
}
ErrorOr<void> IntelAnalogDisplayTranscoder::set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier)
{
SpinlockLocker locker(m_access_lock);
u32 value = (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16);
m_dpll_registers->divisor_a0 = value;
m_dpll_registers->divisor_a1 = value;
m_shadow_registers.dpll_divisor_a0 = value;
m_shadow_registers.dpll_divisor_a1 = value;
// Note: We don't set the DAC multiplier now but reserve it for later usage (e.g. when enabling the DPLL)
m_shadow_registers.dpll_reserved_dac_multiplier = dac_multiplier;
// Note: We don't set the DPLL P1 now but reserve it for later usage (e.g. when enabling the DPLL)
m_shadow_registers.dpll_p1 = settings.p1;
return {};
}
ErrorOr<void> IntelAnalogDisplayTranscoder::enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
// Explanation for Gen4 DPLL control bits:
// 1. 0b0110 in bits 9 to 12 - use clock phase 6 (Default)
// 2. bits 24,25 - set to 0b00 to ensure FPA0/FPA1 (DPLL A Divisor 0, 1) divide by 10 (used for DAC modes under 270 MHz)
// 3. bit 26 - set to 0b1 to ensure mode select to DAC mode
// 4. bit 28 - set to 0b1 to disable VGA mode
// 5. bit 31 - enable DPLL VCO (DPLL enabled and operational)
u32 control_value = (6 << 9) | (m_shadow_registers.dpll_p1) << 16 | (1 << 26) | (1 << 28) | (1 << 31);
m_dpll_control_registers->control = control_value;
m_shadow_registers.dpll_control = control_value;
// Explanation for Gen4 DPLL multiplier bits:
// 1. 0b0110 in bits 9 to 12 - use clock phase 6 (Default)
// 2. bits 24,25 - set to 0b00 to ensure FPA0/FPA1 (DPLL A Divisor 0, 1) divide by 10 (used for DAC modes under 270 MHz)
// 3. bit 26 - set to 0b1 to ensure mode select to DAC mode
// 4. bit 28 - set to 0b1 to disable VGA mode
// 5. bit 31 - enable DPLL VCO (DPLL enabled and operational)
u32 dac_multiplier_value = (m_shadow_registers.dpll_reserved_dac_multiplier - 1) | ((m_shadow_registers.dpll_reserved_dac_multiplier - 1) << 8);
m_dpll_control_registers->multiplier = dac_multiplier_value;
m_shadow_registers.dpll_raw_dac_multiplier = dac_multiplier_value;
// The specification says we should wait (at least) about 150 microseconds
// after enabling the DPLL to allow the clock to stabilize
microseconds_delay(200);
for (size_t milliseconds_elapsed = 0; milliseconds_elapsed < 5; milliseconds_elapsed++) {
u32 control_value = m_dpll_control_registers->control;
if (control_value & (1 << 31))
return {};
}
return Error::from_errno(EBUSY);
}
ErrorOr<void> IntelAnalogDisplayTranscoder::disable_dpll(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
m_dpll_control_registers->control = 0;
m_shadow_registers.dpll_control = 0;
return {};
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 <AK/Types.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelAnalogDisplayTranscoder final : public IntelDisplayTranscoder {
public:
static ErrorOr<NonnullOwnPtr<IntelAnalogDisplayTranscoder>> create_with_physical_addresses(PhysicalAddress transcoder_registers_start_address,
PhysicalAddress pipe_registers_start_address, PhysicalAddress dpll_registers_start_address, PhysicalAddress dpll_control_registers_start_address);
virtual ErrorOr<void> set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier) override;
virtual ErrorOr<void> enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>) override;
virtual ErrorOr<void> disable_dpll(Badge<IntelDisplayConnectorGroup>) override;
private:
struct [[gnu::packed]] DPLLRegisters {
u32 divisor_a0;
u32 divisor_a1;
};
struct [[gnu::packed]] DPLLControlRegisters {
u32 control;
u32 padding; // On Gen4, this is the control register of DPLL B, don't touch this
u32 multiplier;
};
IntelAnalogDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile>, Memory::TypedMapping<PipeRegisters volatile>, Memory::TypedMapping<DPLLRegisters volatile>, Memory::TypedMapping<DPLLControlRegisters volatile>);
Memory::TypedMapping<DPLLRegisters volatile> m_dpll_registers;
Memory::TypedMapping<DPLLControlRegisters volatile> m_dpll_control_registers;
};
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.h>
#include <Kernel/Memory/PhysicalAddress.h>
namespace Kernel {
IntelDisplayTranscoder::IntelDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile> registers_mapping, Memory::TypedMapping<PipeRegisters volatile> pipe_registers_mapping)
: m_transcoder_registers(move(registers_mapping))
, m_pipe_registers(move(pipe_registers_mapping))
{
}
IntelDisplayTranscoder::ShadowRegisters IntelDisplayTranscoder::current_registers_state() const
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers;
}
ErrorOr<void> IntelDisplayTranscoder::set_mode_setting_timings(Badge<IntelDisplayConnectorGroup>, DisplayConnector::ModeSetting const& mode_setting)
{
SpinlockLocker locker(m_access_lock);
dbgln_if(INTEL_GRAPHICS_DEBUG, "htotal - {}, {}", (mode_setting.horizontal_active - 1), (mode_setting.horizontal_total() - 1));
m_shadow_registers.horizontal_total = ((mode_setting.horizontal_active - 1) | (mode_setting.horizontal_total() - 1) << 16);
m_transcoder_registers->horizontal_total = ((mode_setting.horizontal_active - 1) | (mode_setting.horizontal_total() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "hblank - {}, {}", (mode_setting.horizontal_blanking_start() - 1), (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1));
m_shadow_registers.horizontal_blank = ((mode_setting.horizontal_blanking_start() - 1) | (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1) << 16);
m_transcoder_registers->horizontal_blank = ((mode_setting.horizontal_blanking_start() - 1) | (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "hsync - {}, {}", (mode_setting.horizontal_sync_start() - 1), (mode_setting.horizontal_sync_end() - 1));
m_shadow_registers.horizontal_sync = ((mode_setting.horizontal_sync_start() - 1) | (mode_setting.horizontal_sync_end() - 1) << 16);
m_transcoder_registers->horizontal_sync = ((mode_setting.horizontal_sync_start() - 1) | (mode_setting.horizontal_sync_end() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vtotal - {}, {}", (mode_setting.vertical_active - 1), (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1));
m_shadow_registers.vertical_total = ((mode_setting.vertical_active - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
m_transcoder_registers->vertical_total = ((mode_setting.vertical_active - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vblank - {}, {}", (mode_setting.vertical_blanking_start() - 1), (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1));
m_shadow_registers.vertical_blank = ((mode_setting.vertical_blanking_start() - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
m_transcoder_registers->vertical_blank = ((mode_setting.vertical_blanking_start() - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vsync - {}, {}", (mode_setting.vertical_sync_start() - 1), (mode_setting.vertical_sync_end() - 1));
m_shadow_registers.vertical_sync = ((mode_setting.vertical_sync_start() - 1) | (mode_setting.vertical_sync_end() - 1) << 16);
m_transcoder_registers->vertical_sync = ((mode_setting.vertical_sync_start() - 1) | (mode_setting.vertical_sync_end() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "sourceSize - {}, {}", (mode_setting.vertical_active - 1), (mode_setting.horizontal_active - 1));
m_shadow_registers.pipe_source = ((mode_setting.vertical_active - 1) | (mode_setting.horizontal_active - 1) << 16);
m_transcoder_registers->pipe_source = ((mode_setting.vertical_active - 1) | (mode_setting.horizontal_active - 1) << 16);
return {};
}
ErrorOr<void> IntelDisplayTranscoder::disable_pipe(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
m_pipe_registers->pipe_configuration = 0;
m_shadow_registers.pipe_conf = 0;
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe");
size_t milliseconds_elapsed = 0;
while (milliseconds_elapsed < 100) {
u32 value = m_pipe_registers->pipe_configuration;
if (!(value & (1 << 30)))
return {};
microseconds_delay(1000);
milliseconds_elapsed++;
}
return Error::from_errno(EBUSY);
}
ErrorOr<void> IntelDisplayTranscoder::enable_pipe(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
u32 value = m_pipe_registers->pipe_configuration;
// Note: Just verify these are not already enabled...
if ((value & (1 << 30)) && (value & (1 << 31)))
return {};
// Note: Set the pipe configuration register with these bits:
// 1. Bit 31 - to enable the Pipe
// 2. Bit 24 - to enable Gamma Unit Mode to 10 bit Gamma mode.
// 3. Bits 21-23 are set to zero to indicate Progressive mode (non Interlaced mode)
// 4. Bits 18 and 19 are set to zero to indicate Normal operations of assigned
// Cursor and Display planes.
m_pipe_registers->pipe_configuration = (1 << 31) | (1 << 24);
m_shadow_registers.pipe_conf = (1 << 31) | (1 << 24);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Enabling Pipe");
size_t milliseconds_elapsed = 0;
while (milliseconds_elapsed < 100) {
u32 value = m_pipe_registers->pipe_configuration;
if ((value & (1 << 30)))
return {};
microseconds_delay(1000);
milliseconds_elapsed++;
}
// FIXME: Seems like my video card is buggy and doesn't set the enabled bit (bit 30)!!
return {};
}
bool IntelDisplayTranscoder::pipe_enabled(Badge<IntelDisplayConnectorGroup>) const
{
SpinlockLocker locker(m_access_lock);
u32 value = m_pipe_registers->pipe_configuration;
return (value & (1 << 30));
}
}

View file

@ -0,0 +1,118 @@
/*
* 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 <AK/Types.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/Intel/Definitions.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelDisplayTranscoder {
public:
// Note: This is used to "cache" all the registers we wrote to, because
// we might not be able to read them directly from hardware later.
struct ShadowRegisters {
u32 horizontal_total;
u32 horizontal_blank;
u32 horizontal_sync;
u32 vertical_total;
u32 vertical_blank;
u32 vertical_sync;
u32 exit_line;
u32 pipe_source;
u32 pipe_border_color_pattern;
u32 reserved;
u32 vsync_shift;
u32 pipe_mult;
u32 dpll_reserved_dac_multiplier;
u32 dpll_raw_dac_multiplier;
u32 dpll_divisor_a0;
u32 dpll_divisor_a1;
u32 dpll_p1;
u32 dpll_control;
u32 m1_value;
u32 n1_value;
u32 m2_value;
u32 n2_value;
u32 m1_link;
u32 n1_link;
u32 m2_link;
u32 n2_link;
u32 pipe_conf;
};
ErrorOr<void> set_mode_setting_timings(Badge<IntelDisplayConnectorGroup>, DisplayConnector::ModeSetting const&);
virtual ErrorOr<void> set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier) = 0;
virtual ErrorOr<void> enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>) = 0;
virtual ErrorOr<void> disable_dpll(Badge<IntelDisplayConnectorGroup>) = 0;
ErrorOr<void> disable_pipe(Badge<IntelDisplayConnectorGroup>);
ErrorOr<void> enable_pipe(Badge<IntelDisplayConnectorGroup>);
bool pipe_enabled(Badge<IntelDisplayConnectorGroup>) const;
ShadowRegisters current_registers_state() const;
virtual ~IntelDisplayTranscoder() = default;
protected:
struct [[gnu::packed]] TranscoderRegisters {
u32 horizontal_total;
u32 horizontal_blank;
u32 horizontal_sync;
u32 vertical_total;
u32 vertical_blank;
u32 vertical_sync;
u32 exit_line;
u32 pipe_source;
u32 pipe_border_color_pattern;
u32 reserved;
u32 vsync_shift;
u32 pipe_mult;
u32 m1_value;
u32 n1_value;
u32 m2_value;
u32 n2_value;
u32 m1_link;
u32 n1_link;
u32 m2_link;
u32 n2_link;
};
struct [[gnu::packed]] PipeRegisters {
u32 pipe_display_scan_line;
u32 pipe_display_scan_line_count_range_compare;
u32 pipe_configuration;
u32 reserved;
u32 pipe_gamma_correction_max_red;
u32 pipe_gamma_correction_max_green;
u32 pipe_gamma_correction_max_blue;
u32 reserved2[2];
u32 pipe_display_status;
u32 reserved3[2];
u32 display_arbitration_control;
u32 display_fifo_watermark_control1;
u32 display_fifo_watermark_control2;
u32 display_fifo_watermark_control3;
u32 pipe_frame_count_high;
// Note: The specification calls this "Pipe Frame Count Low and Pixel Count"
u32 pipe_frame_count_low;
};
IntelDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile>, Memory::TypedMapping<PipeRegisters volatile>);
mutable Spinlock<LockRank::None> m_access_lock;
ShadowRegisters m_shadow_registers {};
Memory::TypedMapping<TranscoderRegisters volatile> m_transcoder_registers;
Memory::TypedMapping<PipeRegisters volatile> m_pipe_registers;
};
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2023, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <Kernel/Debug.h>
#include <Kernel/Devices/GPU/Intel/Transcoder/PLL.h>
namespace Kernel::IntelGraphics {
static constexpr PLLMaxSettings g35limits {
{ 20'000'000, 400'000'000 }, // values in Hz, dot_clock
{ 1'400'000'000, 2'800'000'000 }, // values in Hz, VCO
{ 3, 8 }, // n
{ 70, 120 }, // m
{ 10, 20 }, // m1
{ 5, 9 }, // m2
{ 5, 80 }, // p
{ 1, 8 }, // p1
{ 5, 10 } // p2
};
PLLMaxSettings const& pll_max_settings_for_generation(Generation generation)
{
switch (generation) {
case Generation::Gen4:
return g35limits;
default:
VERIFY_NOT_REACHED();
}
}
static size_t find_absolute_difference(u64 target_frequency, u64 checked_frequency)
{
if (target_frequency >= checked_frequency)
return target_frequency - checked_frequency;
return checked_frequency - target_frequency;
}
Optional<PLLSettings> create_pll_settings(Generation generation, u64 target_frequency, u64 reference_clock)
{
PLLSettings settings {};
PLLSettings best_settings {};
auto& limits = pll_max_settings_for_generation(generation);
// FIXME: Is this correct for all Intel Native graphics cards?
settings.p2 = 10;
dbgln_if(INTEL_GRAPHICS_DEBUG, "Check PLL settings for ref clock of {} Hz, for target of {} Hz", reference_clock, target_frequency);
u64 best_difference = 0xffffffff;
for (settings.n = limits.n.min; settings.n <= limits.n.max; ++settings.n) {
for (settings.m1 = limits.m1.max; settings.m1 >= limits.m1.min; --settings.m1) {
for (settings.m2 = limits.m2.max; settings.m2 >= limits.m2.min; --settings.m2) {
for (settings.p1 = limits.p1.max; settings.p1 >= limits.p1.min; --settings.p1) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "Check PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2);
if (!check_pll_settings(settings, reference_clock, limits))
continue;
auto current_dot_clock = settings.compute_dot_clock(reference_clock);
if (current_dot_clock == target_frequency)
return settings;
auto difference = find_absolute_difference(target_frequency, current_dot_clock);
if (difference < best_difference && (current_dot_clock > target_frequency)) {
best_settings = settings;
best_difference = difference;
}
}
}
}
}
if (best_settings.is_valid())
return best_settings;
return {};
}
bool check_pll_settings(PLLSettings const& settings, size_t reference_clock, PLLMaxSettings const& limits)
{
if (settings.n < limits.n.min || settings.n > limits.n.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "N is invalid {}", settings.n);
return false;
}
if (settings.m1 < limits.m1.min || settings.m1 > limits.m1.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "m1 is invalid {}", settings.m1);
return false;
}
if (settings.m2 < limits.m2.min || settings.m2 > limits.m2.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "m2 is invalid {}", settings.m2);
return false;
}
if (settings.p1 < limits.p1.min || settings.p1 > limits.p1.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "p1 is invalid {}", settings.p1);
return false;
}
if (settings.m1 <= settings.m2) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "m2 is invalid {} as it is bigger than m1 {}", settings.m2, settings.m1);
return false;
}
auto m = settings.compute_m();
auto p = settings.compute_p();
if (m < limits.m.min || m > limits.m.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "m invalid {}", m);
return false;
}
if (p < limits.p.min || p > limits.p.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "p invalid {}", p);
return false;
}
auto dot = settings.compute_dot_clock(reference_clock);
auto vco = settings.compute_vco(reference_clock);
if (dot < limits.dot_clock.min || dot > limits.dot_clock.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "Dot clock invalid {}", dot);
return false;
}
if (vco < limits.vco.min || vco > limits.vco.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "VCO clock invalid {}", vco);
return false;
}
return true;
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2023, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <Kernel/Devices/GPU/Intel/Definitions.h>
namespace Kernel::IntelGraphics {
PLLMaxSettings const& pll_max_settings_for_generation(Generation);
Optional<PLLSettings> create_pll_settings(Generation, u64 target_frequency, u64 reference_clock);
bool check_pll_settings(PLLSettings const& settings, size_t reference_clock, PLLMaxSettings const& limits);
}

View file

@ -0,0 +1,276 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Singleton.h>
#include <Kernel/Arch/Delay.h>
#if ARCH(X86_64)
# include <Kernel/Arch/x86_64/Hypervisor/BochsDisplayConnector.h>
#endif
#include <Kernel/Boot/CommandLine.h>
#include <Kernel/Boot/Multiboot.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/GPU/Bochs/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h>
#include <Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VMWare/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
#include <Kernel/Memory/AnonymousVMObject.h>
#include <Kernel/Sections.h>
namespace Kernel {
static Singleton<GraphicsManagement> s_the;
extern Atomic<Graphics::Console*> g_boot_console;
GraphicsManagement& GraphicsManagement::the()
{
return *s_the;
}
bool GraphicsManagement::is_initialized()
{
return s_the.is_initialized();
}
UNMAP_AFTER_INIT GraphicsManagement::GraphicsManagement()
{
}
void GraphicsManagement::disable_vga_emulation_access_permanently()
{
#if ARCH(X86_64)
if (!m_vga_arbiter)
return;
m_vga_arbiter->disable_vga_emulation_access_permanently({});
#endif
}
void GraphicsManagement::enable_vga_text_mode_console_cursor()
{
#if ARCH(X86_64)
if (!m_vga_arbiter)
return;
m_vga_arbiter->enable_vga_text_mode_console_cursor({});
#endif
}
void GraphicsManagement::disable_vga_text_mode_console_cursor()
{
#if ARCH(X86_64)
if (!m_vga_arbiter)
return;
m_vga_arbiter->disable_vga_text_mode_console_cursor({});
#endif
}
void GraphicsManagement::set_vga_text_mode_cursor([[maybe_unused]] size_t console_width, [[maybe_unused]] size_t x, [[maybe_unused]] size_t y)
{
#if ARCH(X86_64)
if (!m_vga_arbiter)
return;
m_vga_arbiter->set_vga_text_mode_cursor({}, console_width, x, y);
#endif
}
void GraphicsManagement::deactivate_graphical_mode()
{
return m_display_connector_nodes.with([&](auto& display_connectors) {
for (auto& connector : display_connectors)
connector.set_display_mode({}, DisplayConnector::DisplayMode::Console);
});
}
void GraphicsManagement::activate_graphical_mode()
{
return m_display_connector_nodes.with([&](auto& display_connectors) {
for (auto& connector : display_connectors)
connector.set_display_mode({}, DisplayConnector::DisplayMode::Graphical);
});
}
void GraphicsManagement::attach_new_display_connector(Badge<DisplayConnector>, DisplayConnector& connector)
{
return m_display_connector_nodes.with([&](auto& display_connectors) {
display_connectors.append(connector);
});
}
void GraphicsManagement::detach_display_connector(Badge<DisplayConnector>, DisplayConnector& connector)
{
return m_display_connector_nodes.with([&](auto& display_connectors) {
display_connectors.remove(connector);
});
}
static inline bool is_vga_compatible_pci_device(PCI::DeviceIdentifier const& device_identifier)
{
// Note: Check for Display Controller, VGA Compatible Controller or
// Unclassified, VGA-Compatible Unclassified Device
auto is_display_controller_vga_compatible = device_identifier.class_code().value() == 0x3 && device_identifier.subclass_code().value() == 0x0;
auto is_general_pci_vga_compatible = device_identifier.class_code().value() == 0x0 && device_identifier.subclass_code().value() == 0x1;
return is_display_controller_vga_compatible || is_general_pci_vga_compatible;
}
static inline bool is_display_controller_pci_device(PCI::DeviceIdentifier const& device_identifier)
{
return device_identifier.class_code().value() == 0x3;
}
struct PCIGraphicsDriverInitializer {
ErrorOr<bool> (*probe)(PCI::DeviceIdentifier const&) = nullptr;
ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> (*create)(PCI::DeviceIdentifier const&) = nullptr;
};
static constexpr PCIGraphicsDriverInitializer s_initializers[] = {
{ IntelNativeGraphicsAdapter::probe, IntelNativeGraphicsAdapter::create },
{ BochsGraphicsAdapter::probe, BochsGraphicsAdapter::create },
{ VirtIOGraphicsAdapter::probe, VirtIOGraphicsAdapter::create },
{ VMWareGraphicsAdapter::probe, VMWareGraphicsAdapter::create },
};
UNMAP_AFTER_INIT ErrorOr<void> GraphicsManagement::determine_and_initialize_graphics_device(PCI::DeviceIdentifier const& device_identifier)
{
VERIFY(is_vga_compatible_pci_device(device_identifier) || is_display_controller_pci_device(device_identifier));
for (auto& initializer : s_initializers) {
auto initializer_probe_found_driver_match_or_error = initializer.probe(device_identifier);
if (initializer_probe_found_driver_match_or_error.is_error()) {
dmesgln("Graphics: Failed to probe device {}, due to {}", device_identifier.address(), initializer_probe_found_driver_match_or_error.error());
continue;
}
auto initializer_probe_found_driver_match = initializer_probe_found_driver_match_or_error.release_value();
if (initializer_probe_found_driver_match) {
auto adapter = TRY(initializer.create(device_identifier));
TRY(m_graphics_devices.try_append(*adapter));
return {};
}
}
return {};
}
UNMAP_AFTER_INIT void GraphicsManagement::initialize_preset_resolution_generic_display_connector()
{
VERIFY(!multiboot_framebuffer_addr.is_null());
VERIFY(multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB);
dmesgln("Graphics: Using a preset resolution from the bootloader, without knowing the PCI device");
m_preset_resolution_generic_display_connector = GenericDisplayConnector::must_create_with_preset_resolution(
multiboot_framebuffer_addr,
multiboot_framebuffer_width,
multiboot_framebuffer_height,
multiboot_framebuffer_pitch);
}
UNMAP_AFTER_INIT bool GraphicsManagement::initialize()
{
/* Explanation on the flow here:
*
* If the user chose to disable graphics support entirely, then all we can do
* is to set up a plain old VGA text console and exit this function.
* Otherwise, we either try to find a device that we natively support so
* we can initialize it, and in case we don't find any device to initialize,
* we try to initialize a simple DisplayConnector to support a pre-initialized
* framebuffer.
*
* Note: If the user disabled PCI access, the kernel behaves like it's running
* on a pure ISA PC machine and therefore the kernel will try to initialize
* a variant that is suitable for ISA VGA handling, and not PCI adapters.
*/
ScopeGuard assign_console_on_initialization_exit([this] {
if (!m_console) {
// If no graphics driver was instantiated and we had a bootloader provided
// framebuffer console we can simply re-use it.
if (auto* boot_console = g_boot_console.load()) {
m_console = *boot_console;
boot_console->unref(); // Drop the leaked reference from Kernel::init()
}
}
});
#if ARCH(X86_64)
m_vga_arbiter = VGAIOArbiter::must_create({});
#endif
auto graphics_subsystem_mode = kernel_command_line().graphics_subsystem_mode();
if (graphics_subsystem_mode == CommandLine::GraphicsSubsystemMode::Disabled) {
VERIFY(!m_console);
return true;
}
// Note: Don't try to initialize an ISA Bochs VGA adapter if PCI hardware is
// present but the user decided to disable its usage nevertheless.
// Otherwise we risk using the Bochs VBE driver on a wrong physical address
// for the framebuffer.
if (PCI::Access::is_hardware_disabled() && !(graphics_subsystem_mode == CommandLine::GraphicsSubsystemMode::Limited && !multiboot_framebuffer_addr.is_null() && multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB)) {
#if ARCH(X86_64)
auto vga_isa_bochs_display_connector = BochsDisplayConnector::try_create_for_vga_isa_connector();
if (vga_isa_bochs_display_connector) {
dmesgln("Graphics: Using a Bochs ISA VGA compatible adapter");
MUST(vga_isa_bochs_display_connector->set_safe_mode_setting());
m_platform_board_specific_display_connector = vga_isa_bochs_display_connector;
dmesgln("Graphics: Invoking manual blanking with VGA ISA ports");
m_vga_arbiter->unblank_screen({});
return true;
}
#endif
}
if (graphics_subsystem_mode == CommandLine::GraphicsSubsystemMode::Limited && !multiboot_framebuffer_addr.is_null() && multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB) {
initialize_preset_resolution_generic_display_connector();
return true;
}
#if ARCH(X86_64)
if (PCI::Access::is_disabled()) {
dmesgln("Graphics: Using an assumed-to-exist ISA VGA compatible generic adapter");
return true;
}
MUST(PCI::enumerate([&](PCI::DeviceIdentifier const& device_identifier) {
// Note: Each graphics controller will try to set its native screen resolution
// upon creation. Later on, if we don't want to have framebuffer devices, a
// framebuffer console will take the control instead.
if (!is_vga_compatible_pci_device(device_identifier) && !is_display_controller_pci_device(device_identifier))
return;
if (auto result = determine_and_initialize_graphics_device(device_identifier); result.is_error())
dbgln("Failed to initialize device {}, due to {}", device_identifier.address(), result.error());
}));
#endif
// Note: If we failed to find any graphics device to be used natively, but the
// bootloader prepared a framebuffer for us to use, then just create a DisplayConnector
// for it so the user can still use the system in graphics mode.
// Prekernel sets the framebuffer address to 0 if MULTIBOOT_INFO_FRAMEBUFFER_INFO
// is not present, as there is likely never a valid framebuffer at this physical address.
// Note: We only support RGB framebuffers. Any other format besides RGBX (and RGBA) or BGRX (and BGRA) is obsolete
// and is not useful for us.
if (m_graphics_devices.is_empty() && !multiboot_framebuffer_addr.is_null() && multiboot_framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB) {
initialize_preset_resolution_generic_display_connector();
return true;
}
if (m_graphics_devices.is_empty()) {
dbgln("No graphics adapter was initialized.");
return false;
}
return true;
}
void GraphicsManagement::set_console(Graphics::Console& console)
{
m_console = console;
if (auto* boot_console = g_boot_console.exchange(nullptr)) {
// Disable the initial boot framebuffer console permanently
boot_console->disable();
// TODO: Even though we swapped the pointer and disabled the console
// we technically can't safely destroy it as other CPUs might still
// try to use it. Once we solve this problem we can drop the reference
// that we intentionally leaked in Kernel::init().
}
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/Platform.h>
#include <AK/Types.h>
#if ARCH(X86_64)
# include <Kernel/Arch/x86_64/VGA/IOArbiter.h>
#endif
#include <Kernel/Bus/PCI/Definitions.h>
#include <Kernel/Devices/GPU/Console/Console.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/Generic/DisplayConnector.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
#include <Kernel/Library/NonnullLockRefPtr.h>
#include <Kernel/Memory/Region.h>
namespace Kernel {
class GraphicsManagement {
public:
static GraphicsManagement& the();
static bool is_initialized();
bool initialize();
unsigned allocate_minor_device_number() { return m_current_minor_number++; };
GraphicsManagement();
void attach_new_display_connector(Badge<DisplayConnector>, DisplayConnector&);
void detach_display_connector(Badge<DisplayConnector>, DisplayConnector&);
void set_vga_text_mode_cursor(size_t console_width, size_t x, size_t y);
void disable_vga_text_mode_console_cursor();
void disable_vga_emulation_access_permanently();
LockRefPtr<Graphics::Console> console() const { return m_console; }
void set_console(Graphics::Console&);
void deactivate_graphical_mode();
void activate_graphical_mode();
private:
void enable_vga_text_mode_console_cursor();
ErrorOr<void> determine_and_initialize_graphics_device(PCI::DeviceIdentifier const&);
void initialize_preset_resolution_generic_display_connector();
Vector<NonnullLockRefPtr<GenericGraphicsAdapter>> m_graphics_devices;
LockRefPtr<Graphics::Console> m_console;
// Note: This is only used when booting with kernel commandline that includes "graphics_subsystem_mode=limited"
LockRefPtr<GenericDisplayConnector> m_preset_resolution_generic_display_connector;
LockRefPtr<DisplayConnector> m_platform_board_specific_display_connector;
unsigned m_current_minor_number { 0 };
SpinlockProtected<IntrusiveList<&DisplayConnector::m_list_node>, LockRank::None> m_display_connector_nodes {};
#if ARCH(X86_64)
OwnPtr<VGAIOArbiter> m_vga_arbiter;
#endif
};
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/VMWare/Console.h>
#include <Kernel/Tasks/WorkQueue.h>
namespace Kernel {
constexpr static AK::Duration refresh_interval = AK::Duration::from_milliseconds(16);
NonnullLockRefPtr<VMWareFramebufferConsole> VMWareFramebufferConsole::initialize(VMWareDisplayConnector& parent_display_connector)
{
auto current_resolution = parent_display_connector.current_mode_setting();
return adopt_lock_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()
{
auto refresh_timer = adopt_nonnull_ref_or_enomem(new (nothrow) Timer()).release_value_but_fixme_should_propagate_errors();
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();
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/VMWare/DisplayConnector.h>
#include <Kernel/Time/TimerQueue.h>
namespace Kernel {
class VMWareFramebufferConsole final : public Graphics::GenericFramebufferConsole {
public:
static NonnullLockRefPtr<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);
LockRefPtr<VMWareDisplayConnector> m_parent_display_connector;
bool m_dirty { false };
};
}

View file

@ -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[];
};
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VMWare/Console.h>
#include <Kernel/Devices/GPU/VMWare/DisplayConnector.h>
namespace Kernel {
NonnullLockRefPtr<VMWareDisplayConnector> VMWareDisplayConnector::must_create(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
{
auto connector = MUST(DeviceManagement::try_create_device<VMWareDisplayConnector>(parent_adapter, framebuffer_address, framebuffer_resource_size));
MUST(connector->create_attached_framebuffer_console());
MUST(connector->initialize_edid_for_generic_monitor(Array<u8, 3> { 'V', 'M', 'W' }));
return connector;
}
ErrorOr<void> VMWareDisplayConnector::create_attached_framebuffer_console()
{
m_framebuffer_console = VMWareFramebufferConsole::initialize(*this);
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
VMWareDisplayConnector::VMWareDisplayConnector(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, false)
, 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);
}
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));
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 {};
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Try.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/VMWare/GraphicsAdapter.h>
#include <Kernel/Library/LockRefPtr.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 NonnullLockRefPtr<VMWareDisplayConnector> must_create(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
private:
VMWareDisplayConnector(VMWareGraphicsAdapter const& parent_adapter, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
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<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:
NonnullLockRefPtr<VMWareGraphicsAdapter> m_parent_adapter;
LockRefPtr<VMWareFramebufferConsole> m_framebuffer_console;
};
}

View file

@ -0,0 +1,193 @@
/*
* 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/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VMWare/Definitions.h>
#include <Kernel/Devices/GPU/VMWare/DisplayConnector.h>
#include <Kernel/Devices/GPU/VMWare/GraphicsAdapter.h>
#include <Kernel/Library/IOWindow.h>
#include <Kernel/Memory/TypedMapping.h>
#include <Kernel/Sections.h>
namespace Kernel {
ErrorOr<bool> VMWareGraphicsAdapter::probe(PCI::DeviceIdentifier const& pci_device_identifier)
{
PCI::HardwareID id = pci_device_identifier.hardware_id();
// Note: We only support VMWare SVGA II adapter
return id.vendor_id == PCI::VendorID::VMWare && id.device_id == 0x0405;
}
ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> VMWareGraphicsAdapter::create(PCI::DeviceIdentifier const& pci_device_identifier)
{
auto registers_io_window = TRY(IOWindow::create_for_pci_device_bar(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR0));
auto adapter = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) VMWareGraphicsAdapter(pci_device_identifier, move(registers_io_window))));
TRY(adapter->initialize_adapter());
return adapter;
}
UNMAP_AFTER_INIT VMWareGraphicsAdapter::VMWareGraphicsAdapter(PCI::DeviceIdentifier const& pci_device_identifier, NonnullOwnPtr<IOWindow> registers_io_window)
: PCI::Device(const_cast<PCI::DeviceIdentifier&>(pci_device_identifier))
, m_registers_io_window(move(registers_io_window))
{
dbgln("VMWare SVGA @ {}, {}", pci_device_identifier.address(), m_registers_io_window);
}
u32 VMWareGraphicsAdapter::read_io_register(VMWareDisplayRegistersOffset register_offset) const
{
SpinlockLocker locker(m_io_access_lock);
m_registers_io_window->write32(0, to_underlying(register_offset));
return m_registers_io_window->read32_unaligned(1);
}
void VMWareGraphicsAdapter::write_io_register(VMWareDisplayRegistersOffset register_offset, u32 value)
{
SpinlockLocker locker(m_io_access_lock);
m_registers_io_window->write32(0, to_underlying(register_offset));
m_registers_io_window->write32_unaligned(1, 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 {}", device_identifier().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(device_identifier()) & PCI::bar_address_mask);
dbgln("VMWare SVGA @ {}: framebuffer size {} bytes, FIFO size {} bytes @ {}", device_identifier().address(), framebuffer_size, fifo_size, fifo_physical_address);
if (framebuffer_size < 0x100000 || fifo_size < 0x10000) {
dbgln("VMWare SVGA @ {}: invalid framebuffer or fifo size", device_identifier().address());
return Error::from_errno(ENOTSUP);
}
m_fifo_registers = TRY(Memory::map_typed<VMWareDisplayFIFORegisters volatile>(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);
auto bar1_space_size = PCI::get_BAR_space_size(device_identifier(), PCI::HeaderType0BaseRegister::BAR1);
m_display_connector = VMWareDisplayConnector::must_create(*this, PhysicalAddress(PCI::get_BAR1(device_identifier()) & PCI::bar_address_mask), bar1_space_size);
TRY(m_display_connector->set_safe_mode_setting());
return {};
}
}

View file

@ -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/Bus/PCI/Device.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
#include <Kernel/Devices/GPU/VMWare/Definitions.h>
#include <Kernel/Library/IOWindow.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/PhysicalAddress.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class GraphicsManagement;
class VMWareDisplayConnector;
class VMWareGraphicsAdapter final
: public GenericGraphicsAdapter
, public PCI::Device {
friend class GraphicsManagement;
public:
static ErrorOr<bool> probe(PCI::DeviceIdentifier const&);
static ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> create(PCI::DeviceIdentifier const&);
virtual ~VMWareGraphicsAdapter() = default;
virtual StringView device_name() const override { return "VMWareGraphicsAdapter"sv; }
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);
VMWareGraphicsAdapter(PCI::DeviceIdentifier const&, NonnullOwnPtr<IOWindow> registers_io_window);
Memory::TypedMapping<VMWareDisplayFIFORegisters volatile> m_fifo_registers;
LockRefPtr<VMWareDisplayConnector> m_display_connector;
mutable NonnullOwnPtr<IOWindow> m_registers_io_window;
mutable Spinlock<LockRank::None> m_io_access_lock {};
mutable RecursiveSpinlock<LockRank::None> m_operation_lock {};
};
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/VirtIO/Console.h>
#include <Kernel/TTY/ConsoleManagement.h>
#include <Kernel/Tasks/WorkQueue.h>
namespace Kernel::Graphics::VirtIOGPU {
constexpr static AK::Duration refresh_interval = AK::Duration::from_milliseconds(16);
NonnullLockRefPtr<Console> Console::initialize(VirtIODisplayConnector& parent_display_connector)
{
auto current_resolution = parent_display_connector.current_mode_setting();
return adopt_lock_ref(*new Console(parent_display_connector, current_resolution));
}
Console::Console(VirtIODisplayConnector 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)
{
// NOTE: Clear the framebuffer, in case it's left with some garbage.
memset(framebuffer_data(), 0, current_resolution.horizontal_stride * current_resolution.vertical_active);
enqueue_refresh_timer();
}
void Console::set_resolution(size_t width, size_t height, size_t pitch)
{
m_width = width;
m_height = height;
m_pitch = pitch;
// Just to start cleanly, we clean the entire framebuffer
memset(framebuffer_data(), 0, pitch * height);
ConsoleManagement::the().resolution_was_changed();
}
void Console::set_cursor(size_t x, size_t y)
{
GenericFramebufferConsole::hide_cursor();
m_x = x;
m_y = y;
GenericFramebufferConsole::show_cursor();
m_dirty = true;
}
void Console::hide_cursor()
{
GenericFramebufferConsole::hide_cursor();
m_dirty = true;
}
void Console::show_cursor()
{
GenericFramebufferConsole::show_cursor();
m_dirty = true;
}
void Console::flush(size_t, size_t, size_t, size_t)
{
m_dirty = true;
}
void Console::enqueue_refresh_timer()
{
auto refresh_timer = adopt_nonnull_ref_or_enomem(new (nothrow) Timer()).release_value_but_fixme_should_propagate_errors();
refresh_timer->setup(CLOCK_MONOTONIC, refresh_interval, [this]() {
if (m_enabled.load() && m_dirty) {
MUST(g_io_work->try_queue([this]() {
{
MutexLocker locker(m_parent_display_connector->m_flushing_lock);
MUST(m_parent_display_connector->flush_first_surface());
}
m_dirty = false;
}));
}
enqueue_refresh_timer();
});
TimerQueue::the().add_timer(move(refresh_timer));
}
void Console::enable()
{
// FIXME: Do we need some locking here to ensure the resolution doesn't change
// while we enable the console?
auto current_resolution = m_parent_display_connector->current_mode_setting();
m_width = current_resolution.horizontal_active;
m_height = current_resolution.vertical_active;
m_pitch = current_resolution.horizontal_stride;
GenericFramebufferConsole::enable();
m_dirty = true;
}
u8* Console::framebuffer_data()
{
return m_parent_display_connector->framebuffer_data();
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/VirtIO/DisplayConnector.h>
#include <Kernel/Time/TimerQueue.h>
namespace Kernel::Graphics::VirtIOGPU {
class Console final : public GenericFramebufferConsole {
public:
static NonnullLockRefPtr<Console> initialize(VirtIODisplayConnector& 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;
virtual void set_cursor(size_t x, size_t y) override;
private:
void enqueue_refresh_timer();
virtual u8* framebuffer_data() override;
virtual void hide_cursor() override;
virtual void show_cursor() override;
Console(VirtIODisplayConnector const& parent_display_connector, DisplayConnector::ModeSetting current_resolution);
NonnullLockRefPtr<VirtIODisplayConnector> m_parent_display_connector;
bool m_dirty { false };
};
}

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/API/VirGL.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VirtIO/Console.h>
#include <Kernel/Devices/GPU/VirtIO/DisplayConnector.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/Protocol.h>
#include <Kernel/Security/Random.h>
namespace Kernel {
NonnullLockRefPtr<VirtIODisplayConnector> VirtIODisplayConnector::must_create(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id)
{
auto device_or_error = DeviceManagement::try_create_device<VirtIODisplayConnector>(graphics_adapter, scanout_id);
VERIFY(!device_or_error.is_error());
auto connector = device_or_error.release_value();
return connector;
}
static_assert((MAX_VIRTIOGPU_RESOLUTION_WIDTH * MAX_VIRTIOGPU_RESOLUTION_HEIGHT * sizeof(u32) * 2) % PAGE_SIZE == 0);
VirtIODisplayConnector::VirtIODisplayConnector(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id)
: DisplayConnector((MAX_VIRTIOGPU_RESOLUTION_WIDTH * MAX_VIRTIOGPU_RESOLUTION_HEIGHT * sizeof(u32) * 2), false)
, m_graphics_adapter(graphics_adapter)
, m_scanout_id(scanout_id)
{
}
void VirtIODisplayConnector::initialize_console(Badge<VirtIOGraphicsAdapter>)
{
m_console = Kernel::Graphics::VirtIOGPU::Console::initialize(*this);
GraphicsManagement::the().set_console(*m_console);
}
void VirtIODisplayConnector::set_safe_mode_setting_after_initialization(Badge<VirtIOGraphicsAdapter>)
{
MUST(set_safe_mode_setting());
}
ErrorOr<void> VirtIODisplayConnector::set_mode_setting(ModeSetting const& mode_setting)
{
SpinlockLocker locker(m_modeset_lock);
if (mode_setting.horizontal_active > MAX_VIRTIOGPU_RESOLUTION_WIDTH || mode_setting.vertical_active > MAX_VIRTIOGPU_RESOLUTION_HEIGHT)
return Error::from_errno(ENOTSUP);
auto& info = m_display_info;
info.rect = {
.x = 0,
.y = 0,
.width = (u32)mode_setting.horizontal_active,
.height = (u32)mode_setting.vertical_active,
};
TRY(m_graphics_adapter->mode_set_resolution({}, *this, mode_setting.horizontal_active, mode_setting.vertical_active));
if (m_console)
m_console->set_resolution(info.rect.width, info.rect.height, info.rect.width * sizeof(u32));
DisplayConnector::ModeSetting mode_set {
.horizontal_stride = info.rect.width * sizeof(u32),
.pixel_clock_in_khz = 0, // Note: There's no pixel clock in paravirtualized hardware
.horizontal_active = info.rect.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 = info.rect.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 = mode_set;
m_display_info.enabled = 1;
return {};
}
ErrorOr<void> VirtIODisplayConnector::set_safe_mode_setting()
{
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> VirtIODisplayConnector::set_y_offset(size_t)
{
// NOTE (FIXME?): We don't do double buffering because when using double buffering,
// perfomance visually looks terrible (everything look sluggish) compared to not using it,
// so until we figure out why (and we might not figure this and double buffering is simply not needed)
// this happens, we simply don't support it.
return Error::from_errno(ENOTSUP);
}
ErrorOr<void> VirtIODisplayConnector::unblank()
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> VirtIODisplayConnector::flush_rectangle(size_t buffer_index, FBRect const& rect)
{
VERIFY(m_flushing_lock.is_locked());
if (!is_valid_buffer_index(buffer_index))
return Error::from_errno(EINVAL);
SpinlockLocker locker(m_graphics_adapter->operation_lock());
Graphics::VirtIOGPU::Protocol::Rect dirty_rect {
.x = rect.x,
.y = rect.y,
.width = rect.width,
.height = rect.height
};
TRY(m_graphics_adapter->transfer_framebuffer_data_to_host({}, *this, dirty_rect, true));
// Flushing directly to screen
TRY(flush_displayed_image(dirty_rect, true));
return {};
}
ErrorOr<void> VirtIODisplayConnector::flush_first_surface()
{
VERIFY(m_flushing_lock.is_locked());
SpinlockLocker locker(m_graphics_adapter->operation_lock());
Graphics::VirtIOGPU::Protocol::Rect dirty_rect {
.x = 0,
.y = 0,
.width = m_display_info.rect.width,
.height = m_display_info.rect.height
};
TRY(m_graphics_adapter->transfer_framebuffer_data_to_host({}, *this, dirty_rect, true));
// Flushing directly to screen
TRY(flush_displayed_image(dirty_rect, true));
return {};
}
void VirtIODisplayConnector::enable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_console);
m_console->enable();
}
void VirtIODisplayConnector::disable_console()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_console);
m_console->disable();
}
void VirtIODisplayConnector::set_edid_bytes(Badge<VirtIOGraphicsAdapter>, Array<u8, 128> const& edid_bytes)
{
DisplayConnector::set_edid_bytes(edid_bytes);
}
Graphics::VirtIOGPU::Protocol::DisplayInfoResponse::Display VirtIODisplayConnector::display_information(Badge<VirtIOGraphicsAdapter>) const
{
return m_display_info;
}
void VirtIODisplayConnector::clear_to_black()
{
size_t width = m_display_info.rect.width;
size_t height = m_display_info.rect.height;
u8* data = framebuffer_data();
for (size_t i = 0; i < width * height; ++i) {
data[4 * i + 0] = 0x00;
data[4 * i + 1] = 0x00;
data[4 * i + 2] = 0x00;
data[4 * i + 3] = 0xff;
}
}
ErrorOr<void> VirtIODisplayConnector::flush_displayed_image(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
{
VERIFY(m_graphics_adapter->operation_lock().is_locked());
TRY(m_graphics_adapter->flush_displayed_image({}, *this, dirty_rect, main_buffer));
return {};
}
void VirtIODisplayConnector::set_dirty_displayed_rect(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
{
VERIFY(m_graphics_adapter->operation_lock().is_locked());
m_graphics_adapter->set_dirty_displayed_rect({}, *this, dirty_rect, main_buffer);
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/BinaryBufferWriter.h>
#include <AK/DistinctNumeric.h>
#include <Kernel/Devices/CharacterDevice.h>
#include <Kernel/Devices/GPU/Console/Console.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/Protocol.h>
#include <Kernel/Memory/Region.h>
#include <LibEDID/EDID.h>
namespace Kernel::Graphics::VirtIOGPU {
class Console;
}
namespace Kernel {
class VirtIOGraphicsAdapter;
class VirtIODisplayConnector final : public DisplayConnector {
friend class Graphics::VirtIOGPU::Console;
friend class DeviceManagement;
public:
static NonnullLockRefPtr<VirtIODisplayConnector> must_create(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id);
void set_edid_bytes(Badge<VirtIOGraphicsAdapter>, Array<u8, 128> const& edid_bytes);
void set_safe_mode_setting_after_initialization(Badge<VirtIOGraphicsAdapter>);
Graphics::VirtIOGPU::ScanoutID scanout_id() const { return m_scanout_id; }
Graphics::VirtIOGPU::Protocol::DisplayInfoResponse::Display display_information(Badge<VirtIOGraphicsAdapter>) const;
void initialize_console(Badge<VirtIOGraphicsAdapter>);
private:
virtual bool mutable_mode_setting_capable() const override { return true; }
virtual bool double_framebuffering_capable() const override { return false; }
virtual bool partial_flush_support() const override { return true; }
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;
// Note: VirtIO hardware requires a constant refresh to keep the screen in sync to the user.
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<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;
static bool is_valid_buffer_index(size_t buffer_index)
{
return buffer_index == 0 || buffer_index == 1;
}
private:
VirtIODisplayConnector(VirtIOGraphicsAdapter& graphics_adapter, Graphics::VirtIOGPU::ScanoutID scanout_id);
ErrorOr<void> flush_displayed_image(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer);
void set_dirty_displayed_rect(Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer);
void clear_to_black();
// Member data
// Context used for kernel operations (e.g. flushing resources to scanout)
Graphics::VirtIOGPU::ContextID m_kernel_context_id;
NonnullLockRefPtr<VirtIOGraphicsAdapter> m_graphics_adapter;
LockRefPtr<Graphics::VirtIOGPU::Console> m_console;
Graphics::VirtIOGPU::Protocol::DisplayInfoResponse::Display m_display_info {};
Graphics::VirtIOGPU::ScanoutID m_scanout_id;
constexpr static size_t NUM_TRANSFER_REGION_PAGES = 256;
};
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/API/Ioctl.h>
#include <Kernel/API/VirGL.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VirtIO/Console.h>
#include <Kernel/Devices/GPU/VirtIO/GPU3DDevice.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/Protocol.h>
#include <Kernel/Security/Random.h>
namespace Kernel {
VirtIOGPU3DDevice::PerContextState::PerContextState(OpenFileDescription& description, Graphics::VirtIOGPU::ContextID context_id, OwnPtr<Memory::Region> transfer_buffer_region)
: m_context_id(context_id)
, m_transfer_buffer_region(move(transfer_buffer_region))
, m_attached_file_description(description)
{
}
ErrorOr<NonnullLockRefPtr<VirtIOGPU3DDevice>> VirtIOGPU3DDevice::try_create(VirtIOGraphicsAdapter& adapter)
{
// Setup memory transfer region
auto region_result = TRY(MM.allocate_kernel_region(
NUM_TRANSFER_REGION_PAGES * PAGE_SIZE,
"VIRGL3D kernel upload buffer"sv,
Memory::Region::Access::ReadWrite,
AllocationStrategy::AllocateNow));
auto kernel_context_id = TRY(adapter.create_context());
return TRY(DeviceManagement::try_create_device<VirtIOGPU3DDevice>(adapter, move(region_result), kernel_context_id));
}
VirtIOGPU3DDevice::VirtIOGPU3DDevice(VirtIOGraphicsAdapter const& graphics_adapter, NonnullOwnPtr<Memory::Region> transfer_buffer_region, Graphics::VirtIOGPU::ContextID kernel_context_id)
: CharacterDevice(28, 0)
, m_graphics_adapter(graphics_adapter)
, m_kernel_context_id(kernel_context_id)
, m_transfer_buffer_region(move(transfer_buffer_region))
{
}
void VirtIOGPU3DDevice::detach(OpenFileDescription& description)
{
m_context_state_list.with([&description](auto& list) {
for (auto& context : list) {
if (&context.description() == &description) {
context.m_list_node.remove();
// NOTE: We break here because there shouldn't be another context
// being attached to this OpenFileDescription.
break;
}
}
});
CharacterDevice::detach(description);
}
ErrorOr<void> VirtIOGPU3DDevice::ioctl(OpenFileDescription& description, unsigned request, Userspace<void*> arg)
{
auto get_context_for_description = [](ContextList& list, OpenFileDescription& description) -> ErrorOr<NonnullRefPtr<PerContextState>> {
for (auto& context : list) {
if (&context.description() == &description)
return context;
}
return EBADF;
};
// TODO: We really should have ioctls for destroying resources as well
switch (request) {
case VIRGL_IOCTL_CREATE_CONTEXT: {
return m_context_state_list.with([get_context_for_description, &description, this](auto& list) -> ErrorOr<void> {
if (auto result = get_context_for_description(list, description); !result.is_error())
return EEXIST;
SpinlockLocker locker(m_graphics_adapter->operation_lock());
// TODO: Delete the context if it fails to be set in m_context_state_lookup
auto context_id = TRY(m_graphics_adapter->create_context());
auto per_context_state = TRY(PerContextState::try_create(description, context_id));
list.append(per_context_state);
return {};
});
}
case VIRGL_IOCTL_TRANSFER_DATA: {
auto user_transfer_descriptor = static_ptr_cast<VirGLTransferDescriptor const*>(arg);
auto transfer_descriptor = TRY(copy_typed_from_user(user_transfer_descriptor));
if (Checked<size_t>::addition_would_overflow(transfer_descriptor.offset_in_region, transfer_descriptor.num_bytes)) {
return EOVERFLOW;
}
if (transfer_descriptor.offset_in_region + transfer_descriptor.num_bytes > NUM_TRANSFER_REGION_PAGES * PAGE_SIZE) {
return EOVERFLOW;
}
return m_context_state_list.with([get_context_for_description, &description, &transfer_descriptor](auto& list) -> ErrorOr<void> {
auto context = TRY(get_context_for_description(list, description));
auto& transfer_buffer_region = context->transfer_buffer_region();
if (transfer_descriptor.direction == VIRGL_DATA_DIR_GUEST_TO_HOST) {
auto target = transfer_buffer_region.vaddr().offset(transfer_descriptor.offset_in_region).as_ptr();
return copy_from_user(target, transfer_descriptor.data, transfer_descriptor.num_bytes);
} else if (transfer_descriptor.direction == VIRGL_DATA_DIR_HOST_TO_GUEST) {
auto source = transfer_buffer_region.vaddr().offset(transfer_descriptor.offset_in_region).as_ptr();
return copy_to_user(transfer_descriptor.data, source, transfer_descriptor.num_bytes);
} else {
return EINVAL;
}
});
}
case VIRGL_IOCTL_SUBMIT_CMD: {
auto user_command_buffer = static_ptr_cast<VirGLCommandBuffer const*>(arg);
auto command_buffer = TRY(copy_typed_from_user(user_command_buffer));
return m_context_state_list.with([get_context_for_description, &description, &command_buffer, this](auto& list) -> ErrorOr<void> {
auto context = TRY(get_context_for_description(list, description));
auto context_id = context->context_id();
SpinlockLocker locker(m_graphics_adapter->operation_lock());
TRY(m_graphics_adapter->submit_command_buffer(context_id, [&](Bytes buffer) {
auto num_bytes = command_buffer.num_elems * sizeof(u32);
VERIFY(num_bytes <= buffer.size());
MUST(copy_from_user(buffer.data(), command_buffer.data, num_bytes));
return num_bytes;
}));
return {};
});
}
case VIRGL_IOCTL_CREATE_RESOURCE: {
auto user_spec = static_ptr_cast<VirGL3DResourceSpec const*>(arg);
VirGL3DResourceSpec spec = TRY(copy_typed_from_user(user_spec));
Graphics::VirtIOGPU::Protocol::Resource3DSpecification const resource_spec = {
.target = static_cast<Graphics::VirtIOGPU::Protocol::Gallium::PipeTextureTarget>(spec.target),
.format = spec.format,
.bind = spec.bind,
.width = spec.width,
.height = spec.height,
.depth = spec.depth,
.array_size = spec.array_size,
.last_level = spec.last_level,
.nr_samples = spec.nr_samples,
.flags = spec.flags,
.padding = 0,
};
return m_context_state_list.with([get_context_for_description, &description, &resource_spec, &spec, &arg, this](auto& list) -> ErrorOr<void> {
auto context = TRY(get_context_for_description(list, description));
SpinlockLocker locker(m_graphics_adapter->operation_lock());
// FIXME: What would be an appropriate resource free-ing mechanism to use in case anything
// after this fails?
auto resource_id = TRY(m_graphics_adapter->create_3d_resource(resource_spec));
TRY(m_graphics_adapter->attach_resource_to_context(resource_id, context->context_id()));
TRY(m_graphics_adapter->ensure_backing_storage(resource_id, context->transfer_buffer_region(), 0, NUM_TRANSFER_REGION_PAGES * PAGE_SIZE));
spec.created_resource_id = resource_id.value();
// FIXME: We should delete the resource we just created if we fail to copy the resource id out
return copy_to_user(static_ptr_cast<VirGL3DResourceSpec*>(arg), &spec);
});
}
}
return EINVAL;
}
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/DistinctNumeric.h>
#include <AK/IntrusiveList.h>
#include <Kernel/Devices/CharacterDevice.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/VirtIO/Protocol.h>
#include <Kernel/Locking/SpinlockProtected.h>
namespace Kernel::Graphics::VirtIOGPU {
enum class VirGLCommand : u32 {
NOP = 0,
CREATE_OBJECT = 1,
BIND_OBJECT,
DESTROY_OBJECT,
SET_VIEWPORT_STATE,
SET_FRAMEBUFFER_STATE,
SET_VERTEX_BUFFERS,
CLEAR,
DRAW_VBO,
RESOURCE_INLINE_WRITE,
SET_SAMPLER_VIEWS,
SET_INDEX_BUFFER,
SET_CONSTANT_BUFFER,
SET_STENCIL_REF,
SET_BLEND_COLOR,
SET_SCISSOR_STATE,
BLIT,
RESOURCE_COPY_REGION,
BIND_SAMPLER_STATES,
BEGIN_QUERY,
END_QUERY,
GET_QUERY_RESULT,
SET_POLYGON_STIPPLE,
SET_CLIP_STATE,
SET_SAMPLE_MASK,
SET_STREAMOUT_TARGETS,
SET_RENDER_CONDITION,
SET_UNIFORM_BUFFER,
SET_SUB_CTX,
CREATE_SUB_CTX,
DESTROY_SUB_CTX,
BIND_SHADER,
SET_TESS_STATE,
SET_MIN_SAMPLES,
SET_SHADER_BUFFERS,
SET_SHADER_IMAGES,
MEMORY_BARRIER,
LAUNCH_GRID,
SET_FRAMEBUFFER_STATE_NO_ATTACH,
TEXTURE_BARRIER,
SET_ATOMIC_BUFFERS,
SET_DBG_FLAGS,
GET_QUERY_RESULT_QBO,
TRANSFER3D,
END_TRANSFERS,
COPY_TRANSFER3D,
SET_TWEAKS,
CLEAR_TEXTURE,
PIPE_RESOURCE_CREATE,
PIPE_RESOURCE_SET_TYPE,
GET_MEMORY_INFO,
SEND_STRING_MARKER,
MAX_COMMANDS
};
union ClearType {
struct {
u32 depth : 1;
u32 stencil : 1;
u32 color0 : 1;
u32 color1 : 1;
u32 color2 : 1;
u32 color3 : 1;
u32 color4 : 1;
u32 color5 : 1;
u32 color6 : 1;
u32 color7 : 1;
} flags;
u32 value;
};
}
namespace Kernel {
class VirtIOGraphicsAdapter;
class VirtIOGPU3DDevice : public CharacterDevice {
friend class DeviceManagement;
public:
static ErrorOr<NonnullLockRefPtr<VirtIOGPU3DDevice>> try_create(VirtIOGraphicsAdapter&);
private:
VirtIOGPU3DDevice(VirtIOGraphicsAdapter const& graphics_adapter, NonnullOwnPtr<Memory::Region> transfer_buffer_region, Graphics::VirtIOGPU::ContextID kernel_context_id);
class PerContextState final : public AtomicRefCounted<PerContextState> {
friend class VirtIOGPU3DDevice;
public:
static ErrorOr<NonnullRefPtr<PerContextState>> try_create(OpenFileDescription& description, Graphics::VirtIOGPU::ContextID context_id)
{
auto region_result = TRY(MM.allocate_kernel_region(
NUM_TRANSFER_REGION_PAGES * PAGE_SIZE,
"VIRGL3D userspace upload buffer"sv,
Memory::Region::Access::ReadWrite,
AllocationStrategy::AllocateNow));
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) PerContextState(description, context_id, move(region_result))));
}
Graphics::VirtIOGPU::ContextID context_id() { return m_context_id; }
Memory::Region& transfer_buffer_region() { return *m_transfer_buffer_region; }
OpenFileDescription& description() { return m_attached_file_description; }
private:
PerContextState() = delete;
PerContextState(OpenFileDescription&, Graphics::VirtIOGPU::ContextID context_id, OwnPtr<Memory::Region> transfer_buffer_region);
Graphics::VirtIOGPU::ContextID m_context_id;
OwnPtr<Memory::Region> m_transfer_buffer_region;
// NOTE: We clean this whole object when the file description is closed, therefore we need to hold
// a raw reference here instead of a strong reference pointer (e.g. RefPtr, which will make it
// possible to leak the attached OpenFileDescription for a context in this device).
OpenFileDescription& m_attached_file_description;
IntrusiveListNode<PerContextState, NonnullRefPtr<PerContextState>> m_list_node;
};
virtual bool can_read(OpenFileDescription const&, u64) const override { return true; }
virtual bool can_write(OpenFileDescription const&, u64) const override { return true; }
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override { return ENOTSUP; }
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override { return ENOTSUP; }
virtual StringView class_name() const override { return "virgl3d"sv; }
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override;
virtual void detach(OpenFileDescription&) override;
using ContextList = IntrusiveListRelaxedConst<&PerContextState::m_list_node>;
private:
NonnullLockRefPtr<VirtIOGraphicsAdapter> m_graphics_adapter;
// Context used for kernel operations (e.g. flushing resources to scanout)
Graphics::VirtIOGPU::ContextID m_kernel_context_id;
SpinlockProtected<ContextList, LockRank::None> m_context_state_list;
// Memory management for backing buffers
NonnullOwnPtr<Memory::Region> m_transfer_buffer_region;
constexpr static size_t NUM_TRANSFER_REGION_PAGES = 1024;
};
}

View file

@ -0,0 +1,557 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BinaryBufferWriter.h>
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/GPU/VirtIO/Console.h>
#include <Kernel/Devices/GPU/VirtIO/DisplayConnector.h>
#include <Kernel/Devices/GPU/VirtIO/GPU3DDevice.h>
#include <Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h>
namespace Kernel {
#define DEVICE_EVENTS_READ 0x0
#define DEVICE_EVENTS_CLEAR 0x4
#define DEVICE_NUM_SCANOUTS 0x8
ErrorOr<bool> VirtIOGraphicsAdapter::probe(PCI::DeviceIdentifier const& device_identifier)
{
return device_identifier.hardware_id().vendor_id == PCI::VendorID::VirtIO;
}
ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> VirtIOGraphicsAdapter::create(PCI::DeviceIdentifier const& device_identifier)
{
// Setup memory transfer region
auto scratch_space_region = TRY(MM.allocate_contiguous_kernel_region(
32 * PAGE_SIZE,
"VirtGPU Scratch Space"sv,
Memory::Region::Access::ReadWrite));
auto active_context_ids = TRY(Bitmap::create(VREND_MAX_CTX, false));
auto adapter = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) VirtIOGraphicsAdapter(device_identifier, move(active_context_ids), move(scratch_space_region))));
TRY(adapter->initialize_virtio_resources());
TRY(adapter->initialize_adapter());
return adapter;
}
ErrorOr<void> VirtIOGraphicsAdapter::initialize_adapter()
{
VERIFY(m_num_scanouts <= VIRTIO_GPU_MAX_SCANOUTS);
TRY(initialize_3d_device());
for (size_t index = 0; index < m_num_scanouts; index++) {
auto display_connector = VirtIODisplayConnector::must_create(*this, index);
m_scanouts[index].display_connector = display_connector;
TRY(query_and_set_edid(index, *display_connector));
display_connector->set_safe_mode_setting_after_initialization({});
display_connector->initialize_console({});
}
return {};
}
ErrorOr<void> VirtIOGraphicsAdapter::mode_set_resolution(Badge<VirtIODisplayConnector>, VirtIODisplayConnector& connector, size_t width, size_t height)
{
SpinlockLocker locker(m_operation_lock);
VERIFY(connector.scanout_id() < VIRTIO_GPU_MAX_SCANOUTS);
auto rounded_buffer_size = TRY(calculate_framebuffer_size(width, height));
TRY(attach_physical_range_to_framebuffer(connector, true, 0, rounded_buffer_size));
return {};
}
void VirtIOGraphicsAdapter::set_dirty_displayed_rect(Badge<VirtIODisplayConnector>, VirtIODisplayConnector& connector, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
{
VERIFY(m_operation_lock.is_locked());
VERIFY(connector.scanout_id() < VIRTIO_GPU_MAX_SCANOUTS);
Scanout::PhysicalBuffer& buffer = main_buffer ? m_scanouts[connector.scanout_id().value()].main_buffer : m_scanouts[connector.scanout_id().value()].back_buffer;
if (buffer.dirty_rect.width == 0 || buffer.dirty_rect.height == 0) {
buffer.dirty_rect = dirty_rect;
} else {
auto current_dirty_right = buffer.dirty_rect.x + buffer.dirty_rect.width;
auto current_dirty_bottom = buffer.dirty_rect.y + buffer.dirty_rect.height;
buffer.dirty_rect.x = min(buffer.dirty_rect.x, dirty_rect.x);
buffer.dirty_rect.y = min(buffer.dirty_rect.y, dirty_rect.y);
buffer.dirty_rect.width = max(current_dirty_right, dirty_rect.x + dirty_rect.width) - buffer.dirty_rect.x;
buffer.dirty_rect.height = max(current_dirty_bottom, dirty_rect.y + dirty_rect.height) - buffer.dirty_rect.y;
}
}
ErrorOr<void> VirtIOGraphicsAdapter::flush_displayed_image(Badge<VirtIODisplayConnector>, VirtIODisplayConnector& connector, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer)
{
VERIFY(m_operation_lock.is_locked());
VERIFY(connector.scanout_id() < VIRTIO_GPU_MAX_SCANOUTS);
Scanout::PhysicalBuffer& buffer = main_buffer ? m_scanouts[connector.scanout_id().value()].main_buffer : m_scanouts[connector.scanout_id().value()].back_buffer;
TRY(flush_displayed_image(buffer.resource_id, dirty_rect));
buffer.dirty_rect = {};
return {};
}
ErrorOr<void> VirtIOGraphicsAdapter::transfer_framebuffer_data_to_host(Badge<VirtIODisplayConnector>, VirtIODisplayConnector& connector, Graphics::VirtIOGPU::Protocol::Rect const& rect, bool main_buffer)
{
VERIFY(m_operation_lock.is_locked());
VERIFY(connector.scanout_id() < VIRTIO_GPU_MAX_SCANOUTS);
Scanout::PhysicalBuffer& buffer = main_buffer ? m_scanouts[connector.scanout_id().value()].main_buffer : m_scanouts[connector.scanout_id().value()].back_buffer;
TRY(transfer_framebuffer_data_to_host(connector.scanout_id(), buffer.resource_id, rect));
return {};
}
ErrorOr<void> VirtIOGraphicsAdapter::attach_physical_range_to_framebuffer(VirtIODisplayConnector& connector, bool main_buffer, size_t framebuffer_offset, size_t framebuffer_size)
{
VERIFY(m_operation_lock.is_locked());
Scanout::PhysicalBuffer& buffer = main_buffer ? m_scanouts[connector.scanout_id().value()].main_buffer : m_scanouts[connector.scanout_id().value()].back_buffer;
buffer.framebuffer_offset = framebuffer_offset;
// 1. Create BUFFER using VIRTIO_GPU_CMD_RESOURCE_CREATE_2D
if (buffer.resource_id.value() != 0) {
// FIXME: Do we need to remove the resource regardless of this condition?
// Do we need to remove it if any of the code below fails for some reason?
TRY(delete_resource(buffer.resource_id));
}
auto display_info = connector.display_information({});
buffer.resource_id = TRY(create_2d_resource(display_info.rect));
// 2. Attach backing storage using VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING
TRY(ensure_backing_storage(buffer.resource_id, connector.framebuffer_region(), buffer.framebuffer_offset, framebuffer_size));
// 3. Use VIRTIO_GPU_CMD_SET_SCANOUT to link the framebuffer to a display scanout.
TRY(set_scanout_resource(connector.scanout_id(), buffer.resource_id, display_info.rect));
// Make sure we constrain the existing dirty rect (if any)
if (buffer.dirty_rect.width != 0 || buffer.dirty_rect.height != 0) {
auto dirty_right = buffer.dirty_rect.x + buffer.dirty_rect.width;
auto dirty_bottom = buffer.dirty_rect.y + buffer.dirty_rect.height;
buffer.dirty_rect.width = min(dirty_right, display_info.rect.x + display_info.rect.width) - buffer.dirty_rect.x;
buffer.dirty_rect.height = min(dirty_bottom, display_info.rect.y + display_info.rect.height) - buffer.dirty_rect.y;
}
return {};
}
VirtIOGraphicsAdapter::VirtIOGraphicsAdapter(PCI::DeviceIdentifier const& device_identifier, Bitmap&& active_context_ids, NonnullOwnPtr<Memory::Region> scratch_space_region)
: VirtIO::Device(device_identifier)
, m_scratch_space(move(scratch_space_region))
{
m_active_context_ids.with([&](Bitmap& my_active_context_ids) {
my_active_context_ids = move(active_context_ids);
// Note: Context ID 0 is invalid, so mark it as in use.
my_active_context_ids.set(0, true);
});
}
ErrorOr<void> VirtIOGraphicsAdapter::initialize_virtio_resources()
{
TRY(VirtIO::Device::initialize_virtio_resources());
auto* config = TRY(get_config(VirtIO::ConfigurationType::Device));
m_device_configuration = config;
bool success = negotiate_features([&](u64 supported_features) {
u64 negotiated = 0;
if (is_feature_set(supported_features, VIRTIO_GPU_F_VIRGL)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: VirGL is available, enabling");
negotiated |= VIRTIO_GPU_F_VIRGL;
m_has_virgl_support = true;
}
if (is_feature_set(supported_features, VIRTIO_GPU_F_EDID))
negotiated |= VIRTIO_GPU_F_EDID;
return negotiated;
});
if (success) {
read_config_atomic([&]() {
m_num_scanouts = config_read32(*config, DEVICE_NUM_SCANOUTS);
});
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: num_scanouts: {}", m_num_scanouts);
success = setup_queues(2); // CONTROLQ + CURSORQ
}
if (!success)
return Error::from_errno(EIO);
finish_init();
return {};
}
bool VirtIOGraphicsAdapter::handle_device_config_change()
{
auto events = get_pending_events();
if (events & VIRTIO_GPU_EVENT_DISPLAY) {
// The host window was resized, in SerenityOS we completely ignore this event
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Ignoring virtio gpu display resize event");
clear_pending_events(VIRTIO_GPU_EVENT_DISPLAY);
}
if (events & ~VIRTIO_GPU_EVENT_DISPLAY) {
dbgln("VirtIO::GraphicsAdapter: Got unknown device config change event: {:#x}", events);
return false;
}
return true;
}
void VirtIOGraphicsAdapter::handle_queue_update(u16)
{
}
u32 VirtIOGraphicsAdapter::get_pending_events()
{
return config_read32(*m_device_configuration, DEVICE_EVENTS_READ);
}
void VirtIOGraphicsAdapter::clear_pending_events(u32 event_bitmask)
{
config_write32(*m_device_configuration, DEVICE_EVENTS_CLEAR, event_bitmask);
}
static void populate_virtio_gpu_request_header(Graphics::VirtIOGPU::Protocol::ControlHeader& header, Graphics::VirtIOGPU::Protocol::CommandType ctrl_type, u32 flags)
{
header.type = to_underlying(ctrl_type);
header.flags = flags;
header.fence_id = 0;
header.context_id = 0;
header.padding = 0;
}
ErrorOr<void> VirtIOGraphicsAdapter::query_and_set_edid(u32 scanout_id, VirtIODisplayConnector& display_connector)
{
SpinlockLocker locker(m_operation_lock);
if (!is_feature_accepted(VIRTIO_GPU_F_EDID))
return Error::from_errno(ENOTSUP);
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::GetEDID>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::GetEDIDResponse>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_GET_EDID, 0);
request.scanout_id = scanout_id;
request.padding = 0;
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.header.type != to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_EDID)) {
dmesgln("VirtIO::GraphicsAdapter: Failed to get EDID");
return Error::from_errno(ENOTSUP);
}
if (response.size == 0) {
dmesgln("VirtIO::GraphicsAdapter: Failed to get EDID, empty buffer");
return Error::from_errno(EIO);
}
Array<u8, 128> raw_edid;
memcpy(raw_edid.data(), response.edid, min(sizeof(raw_edid), response.size));
display_connector.set_edid_bytes({}, raw_edid);
return {};
}
ErrorOr<Graphics::VirtIOGPU::ResourceID> VirtIOGraphicsAdapter::create_2d_resource(Graphics::VirtIOGPU::Protocol::Rect rect)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceCreate2D>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, 0);
auto resource_id = allocate_resource_id();
request.resource_id = resource_id.value();
request.width = rect.width;
request.height = rect.height;
request.format = to_underlying(Graphics::VirtIOGPU::Protocol::TextureFormat::VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM);
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Allocated 2d resource with id {}", resource_id.value());
return resource_id;
}
return EIO;
}
ErrorOr<Graphics::VirtIOGPU::ResourceID> VirtIOGraphicsAdapter::create_3d_resource(Graphics::VirtIOGPU::Protocol::Resource3DSpecification const& resource_3d_specification)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceCreate3D>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, 0);
// FIXME: What would be an appropriate resource free-ing mechanism to use in case anything
// after this fails?
auto resource_id = allocate_resource_id();
request.resource_id = resource_id.value();
// TODO: Abstract this out a bit more
u32* start_of_copied_fields = &request.target;
// Validate that the sub copy from the resource_3d_specification to the offset of the request fits.
static_assert((sizeof(request) - offsetof(Graphics::VirtIOGPU::Protocol::ResourceCreate3D, target) == sizeof(resource_3d_specification)));
memcpy(start_of_copied_fields, &resource_3d_specification, sizeof(resource_3d_specification));
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Allocated 3d resource with id {}", resource_id.value());
return resource_id;
}
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::ensure_backing_storage(Graphics::VirtIOGPU::ResourceID resource_id, Memory::Region const& region, size_t buffer_offset, size_t buffer_length)
{
VERIFY(m_operation_lock.is_locked());
VERIFY(buffer_offset % PAGE_SIZE == 0);
VERIFY(buffer_length % PAGE_SIZE == 0);
auto first_page_index = buffer_offset / PAGE_SIZE;
size_t num_mem_regions = buffer_length / PAGE_SIZE;
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceAttachBacking>();
const size_t header_block_size = sizeof(request) + num_mem_regions * sizeof(Graphics::VirtIOGPU::Protocol::MemoryEntry);
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, 0);
request.resource_id = resource_id.value();
request.num_entries = num_mem_regions;
for (size_t i = 0; i < num_mem_regions; ++i) {
auto& memory_entry = writer.append_structure<Graphics::VirtIOGPU::Protocol::MemoryEntry>();
memory_entry.address = region.physical_page(first_page_index + i)->paddr().get();
memory_entry.length = PAGE_SIZE;
}
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), header_block_size, sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Allocated backing storage");
return {};
}
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::detach_backing_storage(Graphics::VirtIOGPU::ResourceID resource_id)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceDetachBacking>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING, 0);
request.resource_id = resource_id.value();
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Detached backing storage");
return {};
}
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::set_scanout_resource(Graphics::VirtIOGPU::ScanoutID scanout, Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect rect)
{
VERIFY(m_operation_lock.is_locked());
// We need to scope the request/response here so that we can query display information later on
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::SetScanOut>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_SET_SCANOUT, 0);
request.resource_id = resource_id.value();
request.scanout_id = scanout.value();
request.rect = rect;
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Set backing scanout");
return {};
}
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::transfer_framebuffer_data_to_host(Graphics::VirtIOGPU::ScanoutID scanout, Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::TransferToHost2D>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, 0);
request.offset = (dirty_rect.x + (dirty_rect.y * m_scanouts[scanout.value()].display_connector->display_information({}).rect.width)) * sizeof(u32);
request.resource_id = resource_id.value();
request.rect = dirty_rect;
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA))
return {};
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::flush_displayed_image(Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceFlush>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_FLUSH, 0);
request.resource_id = resource_id.value();
request.rect = dirty_rect;
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA))
return {};
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::synchronous_virtio_gpu_command(size_t microseconds_timeout, PhysicalAddress buffer_start, size_t request_size, size_t response_size)
{
VERIFY(m_operation_lock.is_locked());
VERIFY(microseconds_timeout > 10);
VERIFY(microseconds_timeout < 100000);
auto& queue = get_queue(CONTROLQ);
queue.disable_interrupts();
SpinlockLocker lock(queue.lock());
VirtIO::QueueChain chain { queue };
chain.add_buffer_to_chain(buffer_start, request_size, VirtIO::BufferType::DeviceReadable);
chain.add_buffer_to_chain(buffer_start.offset(request_size), response_size, VirtIO::BufferType::DeviceWritable);
supply_chain_and_notify(CONTROLQ, chain);
full_memory_barrier();
size_t current_time = 0;
ScopeGuard clear_used_buffers([&] {
queue.discard_used_buffers();
});
while (current_time < microseconds_timeout) {
if (queue.new_data_available())
return {};
microseconds_delay(1);
current_time++;
}
return Error::from_errno(EBUSY);
}
ErrorOr<void> VirtIOGraphicsAdapter::flush_dirty_rectangle(Graphics::VirtIOGPU::ScanoutID scanout_id, Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect)
{
VERIFY(m_operation_lock.is_locked());
TRY(transfer_framebuffer_data_to_host(scanout_id, resource_id, dirty_rect));
TRY(flush_displayed_image(resource_id, dirty_rect));
return {};
}
Graphics::VirtIOGPU::ResourceID VirtIOGraphicsAdapter::allocate_resource_id()
{
return m_resource_id_counter++;
}
ErrorOr<void> VirtIOGraphicsAdapter::delete_resource(Graphics::VirtIOGPU::ResourceID resource_id)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ResourceUnref>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_RESOURCE_UNREF, 0);
request.resource_id = resource_id.value();
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA))
return {};
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::initialize_3d_device()
{
if (m_has_virgl_support) {
SpinlockLocker locker(m_operation_lock);
m_3d_device = TRY(VirtIOGPU3DDevice::try_create(*this));
}
return {};
}
ErrorOr<Graphics::VirtIOGPU::ContextID> VirtIOGraphicsAdapter::create_context()
{
VERIFY(m_operation_lock.is_locked());
return m_active_context_ids.with([&](Bitmap& active_context_ids) -> ErrorOr<Graphics::VirtIOGPU::ContextID> {
auto maybe_available_id = active_context_ids.find_first_unset();
if (!maybe_available_id.has_value()) {
dmesgln("VirtIO::GraphicsAdapter: No available context IDs.");
return Error::from_errno(ENXIO);
}
auto new_context_id = static_cast<u32>(maybe_available_id.value());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ContextCreate>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
constexpr char const* region_name = "Serenity VirGL3D Context";
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_CTX_CREATE, 0);
request.header.context_id = new_context_id;
request.name_length = strlen(region_name);
memset(request.debug_name.data(), 0, 64);
VERIFY(request.name_length <= 64);
memcpy(request.debug_name.data(), region_name, request.name_length);
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA)) {
active_context_ids.set(maybe_available_id.value(), true);
return static_cast<Graphics::VirtIOGPU::ContextID>(new_context_id);
}
return Error::from_errno(EIO);
});
}
ErrorOr<void> VirtIOGraphicsAdapter::submit_command_buffer(Graphics::VirtIOGPU::ContextID context_id, Function<size_t(Bytes)> buffer_writer)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::CommandSubmit>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_SUBMIT_3D, 0);
request.header.context_id = context_id.value();
auto max_command_buffer_length = m_scratch_space->size() - sizeof(request) - sizeof(Graphics::VirtIOGPU::Protocol::ControlHeader);
// Truncate to nearest multiple of alignment, to ensure padding loop doesn't exhaust allocated space
max_command_buffer_length -= max_command_buffer_length % alignof(Graphics::VirtIOGPU::Protocol::ControlHeader);
Bytes command_buffer_buffer(m_scratch_space->vaddr().offset(sizeof(request)).as_ptr(), max_command_buffer_length);
request.size = buffer_writer(command_buffer_buffer);
writer.skip_bytes(request.size);
// The alignment of a ControlHeader may be a few words larger than the length of a command buffer, so
// we pad with no-ops until we reach the correct alignment
while (writer.current_offset() % alignof(Graphics::VirtIOGPU::Protocol::ControlHeader) != 0) {
VERIFY((writer.current_offset() % alignof(Graphics::VirtIOGPU::Protocol::ControlHeader)) % sizeof(u32) == 0);
writer.append_structure<u32>() = to_underlying(Graphics::VirtIOGPU::VirGLCommand::NOP);
request.size += 4;
}
dbgln_if(VIRTIO_DEBUG, "VirtIO::GraphicsAdapter: Sending command buffer of length {}", request.size);
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request) + request.size, sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA))
return {};
return EIO;
}
ErrorOr<void> VirtIOGraphicsAdapter::attach_resource_to_context(Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::ContextID context_id)
{
VERIFY(m_operation_lock.is_locked());
auto writer = create_scratchspace_writer();
auto& request = writer.append_structure<Graphics::VirtIOGPU::Protocol::ContextAttachResource>();
auto& response = writer.append_structure<Graphics::VirtIOGPU::Protocol::ControlHeader>();
populate_virtio_gpu_request_header(request.header, Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, 0);
request.header.context_id = context_id.value();
request.resource_id = resource_id.value();
TRY(synchronous_virtio_gpu_command(10000, start_of_scratch_space(), sizeof(request), sizeof(response)));
if (response.type == to_underlying(Graphics::VirtIOGPU::Protocol::CommandType::VIRTIO_GPU_RESP_OK_NODATA))
return {};
return EIO;
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/BinaryBufferWriter.h>
#include <AK/DistinctNumeric.h>
#include <Kernel/Bus/VirtIO/Device.h>
#include <Kernel/Bus/VirtIO/Queue.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
#include <Kernel/Devices/GPU/VirtIO/Protocol.h>
namespace Kernel {
#define VIRTIO_GPU_F_VIRGL (1 << 0)
#define VIRTIO_GPU_F_EDID (1 << 1)
#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
#define CONTROLQ 0
#define CURSORQ 1
#define MAX_VIRTIOGPU_RESOLUTION_WIDTH 3840
#define MAX_VIRTIOGPU_RESOLUTION_HEIGHT 2160
#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0)
class VirtIODisplayConnector;
class VirtIOGPU3DDevice;
class VirtIOGraphicsAdapter final
: public GenericGraphicsAdapter
, public VirtIO::Device {
friend class VirtIODisplayConnector;
friend class VirtIOGPU3DDevice;
public:
static ErrorOr<bool> probe(PCI::DeviceIdentifier const&);
static ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> create(PCI::DeviceIdentifier const&);
virtual ErrorOr<void> initialize_virtio_resources() override;
virtual StringView device_name() const override { return "VirtIOGraphicsAdapter"sv; }
ErrorOr<void> mode_set_resolution(Badge<VirtIODisplayConnector>, VirtIODisplayConnector&, size_t width, size_t height);
void set_dirty_displayed_rect(Badge<VirtIODisplayConnector>, VirtIODisplayConnector&, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer);
ErrorOr<void> flush_displayed_image(Badge<VirtIODisplayConnector>, VirtIODisplayConnector&, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect, bool main_buffer);
ErrorOr<void> transfer_framebuffer_data_to_host(Badge<VirtIODisplayConnector>, VirtIODisplayConnector&, Graphics::VirtIOGPU::Protocol::Rect const& rect, bool main_buffer);
private:
ErrorOr<void> attach_physical_range_to_framebuffer(VirtIODisplayConnector& connector, bool main_buffer, size_t framebuffer_offset, size_t framebuffer_size);
ErrorOr<void> initialize_3d_device();
ErrorOr<void> flush_dirty_rectangle(Graphics::VirtIOGPU::ScanoutID, Graphics::VirtIOGPU::ResourceID, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect);
struct Scanout {
struct PhysicalBuffer {
size_t framebuffer_offset { 0 };
Graphics::VirtIOGPU::Protocol::Rect dirty_rect {};
Graphics::VirtIOGPU::ResourceID resource_id { 0 };
};
LockRefPtr<VirtIODisplayConnector> display_connector;
PhysicalBuffer main_buffer;
PhysicalBuffer back_buffer;
};
VirtIOGraphicsAdapter(PCI::DeviceIdentifier const&, Bitmap&& active_context_ids, NonnullOwnPtr<Memory::Region> scratch_space_region);
ErrorOr<void> initialize_adapter();
virtual bool handle_device_config_change() override;
virtual void handle_queue_update(u16 queue_index) override;
u32 get_pending_events();
void clear_pending_events(u32 event_bitmask);
// 2D framebuffer stuff
static ErrorOr<FlatPtr> calculate_framebuffer_size(size_t width, size_t height)
{
// VirtIO resources can only map on page boundaries!
return Memory::page_round_up(sizeof(u32) * width * height);
}
// 3D Command stuff
ErrorOr<Graphics::VirtIOGPU::ContextID> create_context();
ErrorOr<void> attach_resource_to_context(Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::ContextID context_id);
ErrorOr<void> submit_command_buffer(Graphics::VirtIOGPU::ContextID, Function<size_t(Bytes)> buffer_writer);
Graphics::VirtIOGPU::Protocol::TextureFormat get_framebuffer_format() const { return Graphics::VirtIOGPU::Protocol::TextureFormat::VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM; }
auto& operation_lock() { return m_operation_lock; }
Graphics::VirtIOGPU::ResourceID allocate_resource_id();
PhysicalAddress start_of_scratch_space() const { return m_scratch_space->physical_page(0)->paddr(); }
AK::BinaryBufferWriter create_scratchspace_writer()
{
return { Bytes(m_scratch_space->vaddr().as_ptr(), m_scratch_space->size()) };
}
ErrorOr<void> synchronous_virtio_gpu_command(size_t microseconds_timeout, PhysicalAddress buffer_start, size_t request_size, size_t response_size);
ErrorOr<Graphics::VirtIOGPU::ResourceID> create_2d_resource(Graphics::VirtIOGPU::Protocol::Rect rect);
ErrorOr<Graphics::VirtIOGPU::ResourceID> create_3d_resource(Graphics::VirtIOGPU::Protocol::Resource3DSpecification const& resource_3d_specification);
ErrorOr<void> delete_resource(Graphics::VirtIOGPU::ResourceID resource_id);
ErrorOr<void> ensure_backing_storage(Graphics::VirtIOGPU::ResourceID resource_id, Memory::Region const& region, size_t buffer_offset, size_t buffer_length);
ErrorOr<void> detach_backing_storage(Graphics::VirtIOGPU::ResourceID resource_id);
ErrorOr<void> set_scanout_resource(Graphics::VirtIOGPU::ScanoutID scanout, Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect rect);
ErrorOr<void> transfer_framebuffer_data_to_host(Graphics::VirtIOGPU::ScanoutID scanout, Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect const& rect);
ErrorOr<void> flush_displayed_image(Graphics::VirtIOGPU::ResourceID resource_id, Graphics::VirtIOGPU::Protocol::Rect const& dirty_rect);
ErrorOr<void> query_and_set_edid(u32 scanout_id, VirtIODisplayConnector& display_connector);
size_t m_num_scanouts { 0 };
Scanout m_scanouts[VIRTIO_GPU_MAX_SCANOUTS];
VirtIO::Configuration const* m_device_configuration { nullptr };
// Note: Resource ID 0 is invalid, and we must not allocate 0 as the first resource ID.
Atomic<u32> m_resource_id_counter { 1 };
SpinlockProtected<Bitmap, LockRank::None> m_active_context_ids {};
LockRefPtr<VirtIOGPU3DDevice> m_3d_device;
bool m_has_virgl_support { false };
Spinlock<LockRank::None> m_operation_lock {};
NonnullOwnPtr<Memory::Region> m_scratch_space;
};
}

View file

@ -0,0 +1,334 @@
/*
* Copyright (c) 2021, Sahan Fernando <sahan.h.fernando@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#define VIRTIO_GPU_MAX_SCANOUTS 16
namespace Kernel::Graphics::VirtIOGPU {
AK_TYPEDEF_DISTINCT_ORDERED_ID(u32, ContextID);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u32, ResourceID);
AK_TYPEDEF_DISTINCT_ORDERED_ID(u32, ScanoutID);
};
#define VREND_MAX_CTX 64
#define VIRGL_BIND_DEPTH_STENCIL (1 << 0)
#define VIRGL_BIND_RENDER_TARGET (1 << 1)
#define VIRGL_BIND_SAMPLER_VIEW (1 << 3)
#define VIRGL_BIND_VERTEX_BUFFER (1 << 4)
#define VIRGL_BIND_INDEX_BUFFER (1 << 5)
#define VIRGL_BIND_CONSTANT_BUFFER (1 << 6)
#define VIRGL_BIND_DISPLAY_TARGET (1 << 7)
#define VIRGL_BIND_COMMAND_ARGS (1 << 8)
#define VIRGL_BIND_STREAM_OUTPUT (1 << 11)
#define VIRGL_BIND_SHADER_BUFFER (1 << 14)
#define VIRGL_BIND_QUERY_BUFFER (1 << 15)
#define VIRGL_BIND_CURSOR (1 << 16)
#define VIRGL_BIND_CUSTOM (1 << 17)
#define VIRGL_BIND_SCANOUT (1 << 18)
namespace Kernel::Graphics::VirtIOGPU::Protocol {
// Specification equivalent: enum virtio_gpu_ctrl_type
enum class CommandType : u32 {
/* 2d commands */
VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100,
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
VIRTIO_GPU_CMD_RESOURCE_UNREF,
VIRTIO_GPU_CMD_SET_SCANOUT,
VIRTIO_GPU_CMD_RESOURCE_FLUSH,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
VIRTIO_GPU_CMD_GET_CAPSET_INFO,
VIRTIO_GPU_CMD_GET_CAPSET,
VIRTIO_GPU_CMD_GET_EDID,
/* 3d commands */
VIRTIO_GPU_CMD_CTX_CREATE = 0x0200,
VIRTIO_GPU_CMD_CTX_DESTROY,
VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE,
VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE,
VIRTIO_GPU_CMD_RESOURCE_CREATE_3D,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D,
VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D,
VIRTIO_GPU_CMD_SUBMIT_3D,
VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB,
VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB,
/* cursor commands */
VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300,
VIRTIO_GPU_CMD_MOVE_CURSOR,
/* success responses */
VIRTIO_GPU_RESP_OK_NODATA = 0x1100,
VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
VIRTIO_GPU_RESP_OK_CAPSET_INFO,
VIRTIO_GPU_RESP_OK_CAPSET,
VIRTIO_GPU_RESP_OK_EDID,
/* error responses */
VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200,
VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
};
enum class ObjectType : u32 {
NONE,
BLEND,
RASTERIZER,
DSA,
SHADER,
VERTEX_ELEMENTS,
SAMPLER_VIEW,
SAMPLER_STATE,
SURFACE,
QUERY,
STREAMOUT_TARGET,
MSAA_SURFACE,
MAX_OBJECTS,
};
enum class PipeTextureTarget : u32 {
BUFFER = 0,
TEXTURE_1D,
TEXTURE_2D,
TEXTURE_3D,
TEXTURE_CUBE,
TEXTURE_RECT,
TEXTURE_1D_ARRAY,
TEXTURE_2D_ARRAY,
TEXTURE_CUBE_ARRAY,
MAX
};
enum class PipePrimitiveTypes : u32 {
POINTS = 0,
LINES,
LINE_LOOP,
LINE_STRIP,
TRIANGLES,
TRIANGLE_STRIP,
TRIANGLE_FAN,
QUADS,
QUAD_STRIP,
POLYGON,
LINES_ADJACENCY,
LINE_STRIP_ADJACENCY,
TRIANGLES_ADJACENCY,
TRIANGLE_STRIP_ADJACENCY,
PATCHES,
MAX
};
// Specification equivalent: struct virtio_gpu_ctrl_hdr
struct ControlHeader {
u32 type;
u32 flags;
u64 fence_id;
u32 context_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_rect
struct Rect {
u32 x;
u32 y;
u32 width;
u32 height;
};
// Specification equivalent: struct virtio_gpu_resp_display_info
struct DisplayInfoResponse {
ControlHeader header;
// Specification equivalent: struct virtio_gpu_display_one
struct Display {
Rect rect;
u32 enabled;
u32 flags;
} scanout_modes[VIRTIO_GPU_MAX_SCANOUTS];
};
// Specification equivalent: enum virtio_gpu_formats
enum class TextureFormat : u32 {
VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1,
VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2,
VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3,
VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4,
VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67,
VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68,
VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121,
VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134,
};
// Specification equivalent: struct virtio_gpu_resource_create_2d
struct ResourceCreate2D {
ControlHeader header;
u32 resource_id;
u32 format;
u32 width;
u32 height;
};
// Specification equivalent: struct virtio_gpu_resource_create_3d
struct ResourceCreate3D {
ControlHeader header;
u32 resource_id;
u32 target;
u32 format;
u32 bind;
u32 width;
u32 height;
u32 depth;
u32 array_size;
u32 last_level;
u32 nr_samples;
u32 flags;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_resource_unref
struct ResourceUnref {
ControlHeader header;
u32 resource_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_set_scanout
struct SetScanOut {
ControlHeader header;
Rect rect;
u32 scanout_id;
u32 resource_id;
};
// Specification equivalent: struct virtio_gpu_mem_entry
struct MemoryEntry {
u64 address;
u32 length;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_resource_attach_backing
struct ResourceAttachBacking {
ControlHeader header;
u32 resource_id;
u32 num_entries;
};
// Specification equivalent: struct virtio_gpu_resource_detach_backing
struct ResourceDetachBacking {
ControlHeader header;
u32 resource_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_transfer_to_host_2d
struct TransferToHost2D {
ControlHeader header;
Rect rect;
u64 offset;
u32 resource_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_resource_flush
struct ResourceFlush {
ControlHeader header;
Rect rect;
u32 resource_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_get_edid
struct GetEDID {
ControlHeader header;
u32 scanout_id;
u32 padding;
};
// Specification equivalent: struct virtio_gpu_resp_edid
struct GetEDIDResponse {
ControlHeader header;
u32 size;
u32 padding;
u8 edid[1024];
};
// No equivalent in specification
struct ContextCreate {
ControlHeader header;
u32 name_length;
u32 padding;
AK::Array<char, 64> debug_name;
};
static_assert(sizeof(ContextCreate::debug_name) == 64);
// No equivalent in specification
struct ContextAttachResource {
ControlHeader header;
u32 resource_id;
u32 padding;
};
// No equivalent in specification
struct CommandSubmit {
ControlHeader header;
u32 size;
u32 padding;
};
namespace Gallium {
enum class PipeTextureTarget : u32 {
BUFFER,
TEXTURE_1D,
TEXTURE_2D,
TEXTURE_3D,
TEXTURE_CUBE,
TEXTURE_RECT,
TEXTURE_1D_ARRAY,
TEXTURE_2D_ARRAY,
TEXTURE_CUBE_ARRAY,
MAX_TEXTURE_TYPES,
};
enum class ShaderType : u32 {
SHADER_VERTEX = 0,
SHADER_FRAGMENT,
SHADER_GEOMETRY,
SHADER_TESS_CTRL,
SHADER_TESS_EVAL,
SHADER_COMPUTE,
SHADER_TYPES
};
}
struct Resource3DSpecification {
Gallium::PipeTextureTarget target;
u32 format;
u32 bind;
u32 width;
u32 height;
u32 depth;
u32 array_size;
u32 last_level;
u32 nr_samples;
u32 flags;
u32 padding;
};
}