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:
parent
31a7dabf02
commit
9ee098b119
69 changed files with 167 additions and 167 deletions
68
Kernel/Devices/GPU/Bochs/Definitions.h
Normal file
68
Kernel/Devices/GPU/Bochs/Definitions.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
85
Kernel/Devices/GPU/Bochs/GraphicsAdapter.cpp
Normal file
85
Kernel/Devices/GPU/Bochs/GraphicsAdapter.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
39
Kernel/Devices/GPU/Bochs/GraphicsAdapter.h
Normal file
39
Kernel/Devices/GPU/Bochs/GraphicsAdapter.h
Normal 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;
|
||||
};
|
||||
}
|
183
Kernel/Devices/GPU/Bochs/QEMUDisplayConnector.cpp
Normal file
183
Kernel/Devices/GPU/Bochs/QEMUDisplayConnector.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
59
Kernel/Devices/GPU/Bochs/QEMUDisplayConnector.h
Normal file
59
Kernel/Devices/GPU/Bochs/QEMUDisplayConnector.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/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;
|
||||
};
|
||||
}
|
91
Kernel/Devices/GPU/Console/BootFramebufferConsole.cpp
Normal file
91
Kernel/Devices/GPU/Console/BootFramebufferConsole.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
45
Kernel/Devices/GPU/Console/BootFramebufferConsole.h
Normal file
45
Kernel/Devices/GPU/Console/BootFramebufferConsole.h
Normal 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 {};
|
||||
};
|
||||
|
||||
}
|
81
Kernel/Devices/GPU/Console/Console.h
Normal file
81
Kernel/Devices/GPU/Console/Console.h
Normal 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 };
|
||||
};
|
||||
}
|
42
Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.cpp
Normal file
42
Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
30
Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h
Normal file
30
Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
379
Kernel/Devices/GPU/Console/GenericFramebufferConsole.cpp
Normal file
379
Kernel/Devices/GPU/Console/GenericFramebufferConsole.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
87
Kernel/Devices/GPU/Console/GenericFramebufferConsole.h
Normal file
87
Kernel/Devices/GPU/Console/GenericFramebufferConsole.h
Normal 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 {};
|
||||
};
|
||||
|
||||
}
|
158
Kernel/Devices/GPU/Console/VGATextModeConsole.cpp
Normal file
158
Kernel/Devices/GPU/Console/VGATextModeConsole.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
47
Kernel/Devices/GPU/Console/VGATextModeConsole.h
Normal file
47
Kernel/Devices/GPU/Console/VGATextModeConsole.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
16
Kernel/Devices/GPU/Definitions.h
Normal file
16
Kernel/Devices/GPU/Definitions.h
Normal 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;
|
||||
|
||||
}
|
518
Kernel/Devices/GPU/DisplayConnector.cpp
Normal file
518
Kernel/Devices/GPU/DisplayConnector.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
174
Kernel/Devices/GPU/DisplayConnector.h
Normal file
174
Kernel/Devices/GPU/DisplayConnector.h
Normal 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;
|
||||
};
|
||||
}
|
62
Kernel/Devices/GPU/Generic/DisplayConnector.cpp
Normal file
62
Kernel/Devices/GPU/Generic/DisplayConnector.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
52
Kernel/Devices/GPU/Generic/DisplayConnector.h
Normal file
52
Kernel/Devices/GPU/Generic/DisplayConnector.h
Normal 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;
|
||||
};
|
||||
}
|
26
Kernel/Devices/GPU/GenericGraphicsAdapter.h
Normal file
26
Kernel/Devices/GPU/GenericGraphicsAdapter.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
123
Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.cpp
Normal file
123
Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
53
Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.h
Normal file
53
Kernel/Devices/GPU/Intel/Auxiliary/GMBusConnector.h
Normal 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;
|
||||
};
|
||||
}
|
55
Kernel/Devices/GPU/Intel/Definitions.h
Normal file
55
Kernel/Devices/GPU/Intel/Definitions.h
Normal 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 };
|
||||
};
|
||||
}
|
278
Kernel/Devices/GPU/Intel/DisplayConnectorGroup.cpp
Normal file
278
Kernel/Devices/GPU/Intel/DisplayConnectorGroup.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
97
Kernel/Devices/GPU/Intel/DisplayConnectorGroup.h
Normal file
97
Kernel/Devices/GPU/Intel/DisplayConnectorGroup.h
Normal 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;
|
||||
};
|
||||
}
|
96
Kernel/Devices/GPU/Intel/NativeDisplayConnector.cpp
Normal file
96
Kernel/Devices/GPU/Intel/NativeDisplayConnector.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
80
Kernel/Devices/GPU/Intel/NativeDisplayConnector.h
Normal file
80
Kernel/Devices/GPU/Intel/NativeDisplayConnector.h
Normal 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;
|
||||
};
|
||||
}
|
72
Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.cpp
Normal file
72
Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.cpp
Normal 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))
|
||||
{
|
||||
}
|
||||
|
||||
}
|
38
Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.h
Normal file
38
Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.h
Normal 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;
|
||||
};
|
||||
}
|
70
Kernel/Devices/GPU/Intel/Plane/DisplayPlane.cpp
Normal file
70
Kernel/Devices/GPU/Intel/Plane/DisplayPlane.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
77
Kernel/Devices/GPU/Intel/Plane/DisplayPlane.h
Normal file
77
Kernel/Devices/GPU/Intel/Plane/DisplayPlane.h
Normal 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 };
|
||||
};
|
||||
}
|
60
Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.cpp
Normal file
60
Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.cpp
Normal 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 {};
|
||||
}
|
||||
}
|
26
Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.h
Normal file
26
Kernel/Devices/GPU/Intel/Plane/G33DisplayPlane.h
Normal 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);
|
||||
};
|
||||
}
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
110
Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.cpp
Normal file
110
Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.cpp
Normal 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));
|
||||
}
|
||||
}
|
118
Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.h
Normal file
118
Kernel/Devices/GPU/Intel/Transcoder/DisplayTranscoder.h
Normal 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;
|
||||
};
|
||||
}
|
125
Kernel/Devices/GPU/Intel/Transcoder/PLL.cpp
Normal file
125
Kernel/Devices/GPU/Intel/Transcoder/PLL.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
18
Kernel/Devices/GPU/Intel/Transcoder/PLL.h
Normal file
18
Kernel/Devices/GPU/Intel/Transcoder/PLL.h
Normal 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);
|
||||
|
||||
}
|
276
Kernel/Devices/GPU/Management.cpp
Normal file
276
Kernel/Devices/GPU/Management.cpp
Normal 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().
|
||||
}
|
||||
}
|
||||
|
||||
}
|
72
Kernel/Devices/GPU/Management.h
Normal file
72
Kernel/Devices/GPU/Management.h
Normal 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
|
||||
};
|
||||
|
||||
}
|
69
Kernel/Devices/GPU/VMWare/Console.cpp
Normal file
69
Kernel/Devices/GPU/VMWare/Console.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
32
Kernel/Devices/GPU/VMWare/Console.h
Normal file
32
Kernel/Devices/GPU/VMWare/Console.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
61
Kernel/Devices/GPU/VMWare/Definitions.h
Normal file
61
Kernel/Devices/GPU/VMWare/Definitions.h
Normal 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[];
|
||||
};
|
||||
|
||||
}
|
133
Kernel/Devices/GPU/VMWare/DisplayConnector.cpp
Normal file
133
Kernel/Devices/GPU/VMWare/DisplayConnector.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
54
Kernel/Devices/GPU/VMWare/DisplayConnector.h
Normal file
54
Kernel/Devices/GPU/VMWare/DisplayConnector.h
Normal 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;
|
||||
};
|
||||
}
|
193
Kernel/Devices/GPU/VMWare/GraphicsAdapter.cpp
Normal file
193
Kernel/Devices/GPU/VMWare/GraphicsAdapter.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
61
Kernel/Devices/GPU/VMWare/GraphicsAdapter.h
Normal file
61
Kernel/Devices/GPU/VMWare/GraphicsAdapter.h
Normal 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 {};
|
||||
};
|
||||
|
||||
}
|
103
Kernel/Devices/GPU/VirtIO/Console.cpp
Normal file
103
Kernel/Devices/GPU/VirtIO/Console.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
37
Kernel/Devices/GPU/VirtIO/Console.h
Normal file
37
Kernel/Devices/GPU/VirtIO/Console.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
201
Kernel/Devices/GPU/VirtIO/DisplayConnector.cpp
Normal file
201
Kernel/Devices/GPU/VirtIO/DisplayConnector.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
87
Kernel/Devices/GPU/VirtIO/DisplayConnector.h
Normal file
87
Kernel/Devices/GPU/VirtIO/DisplayConnector.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
158
Kernel/Devices/GPU/VirtIO/GPU3DDevice.cpp
Normal file
158
Kernel/Devices/GPU/VirtIO/GPU3DDevice.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
158
Kernel/Devices/GPU/VirtIO/GPU3DDevice.h
Normal file
158
Kernel/Devices/GPU/VirtIO/GPU3DDevice.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
557
Kernel/Devices/GPU/VirtIO/GraphicsAdapter.cpp
Normal file
557
Kernel/Devices/GPU/VirtIO/GraphicsAdapter.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
125
Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h
Normal file
125
Kernel/Devices/GPU/VirtIO/GraphicsAdapter.h
Normal 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;
|
||||
};
|
||||
}
|
334
Kernel/Devices/GPU/VirtIO/Protocol.h
Normal file
334
Kernel/Devices/GPU/VirtIO/Protocol.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue