From cc92538d49a629ed91fb8cadfdc4c65225248e63 Mon Sep 17 00:00:00 2001 From: Liav A Date: Mon, 12 Apr 2021 22:07:30 +0300 Subject: [PATCH] Kernel/Graphics: Add basic support for Intel native accelerator We simply modeset the resolution after determining the preferred resolution after getting the EDID from the attached display. --- Kernel/CMakeLists.txt | 1 + Kernel/Debug.h.in | 4 + Kernel/Graphics/Definitions.h | 85 +++ Kernel/Graphics/GraphicsManagement.cpp | 6 + .../Graphics/IntelNativeGraphicsAdapter.cpp | 605 ++++++++++++++++++ Kernel/Graphics/IntelNativeGraphicsAdapter.h | 180 ++++++ Meta/CMake/all_the_debug_macros.cmake | 1 + 7 files changed, 882 insertions(+) create mode 100644 Kernel/Graphics/Definitions.h create mode 100644 Kernel/Graphics/IntelNativeGraphicsAdapter.cpp create mode 100644 Kernel/Graphics/IntelNativeGraphicsAdapter.h diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index e0e6be75d83..9e0d5826044 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -54,6 +54,7 @@ set(KERNEL_SOURCES Graphics/BochsGraphicsAdapter.cpp Graphics/FramebufferDevice.cpp Graphics/GraphicsManagement.cpp + Graphics/IntelNativeGraphicsAdapter.cpp Graphics/RawFramebufferDevice.cpp Graphics/VGACompatibleAdapter.cpp Storage/Partition/DiskPartition.cpp diff --git a/Kernel/Debug.h.in b/Kernel/Debug.h.in index 0db094ad4c6..134bcf1b4ed 100644 --- a/Kernel/Debug.h.in +++ b/Kernel/Debug.h.in @@ -102,6 +102,10 @@ #cmakedefine01 ICMP_DEBUG #endif +#ifndef INTEL_GRAPHICS_DEBUG +#cmakedefine01 INTEL_GRAPHICS_DEBUG +#endif + #ifndef INTERRUPT_DEBUG #cmakedefine01 INTERRUPT_DEBUG #endif diff --git a/Kernel/Graphics/Definitions.h b/Kernel/Graphics/Definitions.h new file mode 100644 index 00000000000..a96e051b132 --- /dev/null +++ b/Kernel/Graphics/Definitions.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Kernel::Graphics { + +struct Timings { + size_t blanking_start() const + { + return active; + } + size_t blanking_end() const + { + return total; + } + + size_t active; + size_t sync_start; + size_t sync_end; + size_t total; +}; + +struct Modesetting { + size_t pixel_clock_in_khz; + Timings horizontal; + Timings vertical; +}; + +struct [[gnu::packed]] StandardTimings { + u8 resolution; + u8 frequency; +}; + +struct [[gnu::packed]] DetailTimings { + u16 pixel_clock; + u8 horizontal_active; + u8 horizontal_blank; + u8 horizontal_active_blank_msb; + u8 vertical_active; + u8 vertical_blank; + u8 vertical_active_blank_msb; + u8 horizontal_sync_offset; + u8 horizontal_sync_pulse; + u8 vertical_sync; + u8 sync_msb; + u8 dimension_width; + u8 dimension_height; + u8 dimension_msb; + u8 horizontal_border; + u8 vertical_border; + u8 features; +}; + +struct [[gnu::packed]] VideoInfoBlock { + u64 padding; + u16 manufacture_id; + u16 product_id; + u32 serial_number; + u8 manufacture_week; + u8 manufacture_year; + u8 edid_version; + u8 edid_revision; + u8 video_input_type; + u8 max_horizontal_size; + u8 max_vertical_size; + u8 gama_factor; + u8 dpms_flags; + u8 chroma_info[10]; + u8 established_timing[2]; + u8 manufacture_reserved_timings; + StandardTimings timings[8]; + DetailTimings details[4]; + u8 unused; + u8 checksum; +}; + +static_assert(sizeof(VideoInfoBlock) == 128); + +} diff --git a/Kernel/Graphics/GraphicsManagement.cpp b/Kernel/Graphics/GraphicsManagement.cpp index 4fbef686296..e329d997009 100644 --- a/Kernel/Graphics/GraphicsManagement.cpp +++ b/Kernel/Graphics/GraphicsManagement.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,11 @@ UNMAP_AFTER_INIT RefPtr GraphicsManagement::determine_graphics_d return BochsGraphicsAdapter::initialize(address); } if (PCI::get_class(address) == 0x3 && PCI::get_subclass(address) == 0x0) { + if (id.vendor_id == 0x8086) { + auto adapter = IntelNativeGraphicsAdapter::initialize(address); + if (!adapter.is_null()) + return adapter; + } VERIFY(multiboot_info_ptr->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB || multiboot_info_ptr->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT); return VGACompatibleAdapter::initialize_with_preset_resolution(address, PhysicalAddress((u32)(multiboot_info_ptr->framebuffer_addr)), diff --git a/Kernel/Graphics/IntelNativeGraphicsAdapter.cpp b/Kernel/Graphics/IntelNativeGraphicsAdapter.cpp new file mode 100644 index 00000000000..fa3b0b47924 --- /dev/null +++ b/Kernel/Graphics/IntelNativeGraphicsAdapter.cpp @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2021, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Kernel { + +static constexpr IntelNativeGraphicsAdapter::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 +}; + +#define DDC2_I2C_ADDRESS 0x50 + +RefPtr IntelNativeGraphicsAdapter::initialize(PCI::Address address) +{ + auto id = PCI::get_id(address); + VERIFY(id.vendor_id == 0x8086); + if (id.device_id != 0x29c2) + return {}; + return adopt_ref(*new IntelNativeGraphicsAdapter(address)); +} + +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; + } +} + +static Graphics::Modesetting calculate_modesetting_from_edid(const Graphics::VideoInfoBlock& edid, size_t index) +{ + Graphics::Modesetting mode; + VERIFY(edid.details[0].pixel_clock); + mode.pixel_clock_in_khz = edid.details[0].pixel_clock * 10; + + size_t horizontal_active = edid.details[index].horizontal_active | ((edid.details[index].horizontal_active_blank_msb >> 4) << 8); + size_t horizontal_blank = edid.details[index].horizontal_blank | ((edid.details[index].horizontal_active_blank_msb & 0xF) << 8); + size_t horizontal_sync_offset = edid.details[index].horizontal_sync_offset | ((edid.details[index].sync_msb >> 6) << 8); + size_t horizontal_sync_pulse = edid.details[index].horizontal_sync_pulse | (((edid.details[index].sync_msb >> 4) & 0x3) << 8); + + mode.horizontal.active = horizontal_active; + mode.horizontal.sync_start = horizontal_active + horizontal_sync_offset; + mode.horizontal.sync_end = horizontal_active + horizontal_sync_offset + horizontal_sync_pulse; + mode.horizontal.total = horizontal_active + horizontal_blank; + + size_t vertical_active = edid.details[index].vertical_active | ((edid.details[index].vertical_active_blank_msb >> 4) << 8); + size_t vertical_blank = edid.details[index].vertical_blank | ((edid.details[index].vertical_active_blank_msb & 0xF) << 8); + size_t vertical_sync_offset = (edid.details[index].vertical_sync >> 4) | (((edid.details[index].sync_msb >> 2) & 0x3) << 4); + size_t vertical_sync_pulse = (edid.details[index].vertical_sync & 0xF) | ((edid.details[index].sync_msb & 0x3) << 4); + + mode.vertical.active = vertical_active; + mode.vertical.sync_start = vertical_active + vertical_sync_offset; + mode.vertical.sync_end = vertical_active + vertical_sync_offset + vertical_sync_pulse; + mode.vertical.total = vertical_active + vertical_blank; + return mode; +} + +static bool check_pll_settings(const IntelNativeGraphicsAdapter::PLLSettings& settings, size_t reference_clock, const IntelNativeGraphicsAdapter::PLLMaxSettings& 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; +} + +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 IntelNativeGraphicsAdapter::create_pll_settings(u64 target_frequency, u64 reference_clock, const PLLMaxSettings& limits) +{ + IntelNativeGraphicsAdapter::PLLSettings settings; + IntelNativeGraphicsAdapter::PLLSettings best_settings; + // 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 {}; +} + +IntelNativeGraphicsAdapter::IntelNativeGraphicsAdapter(PCI::Address address) + : PCI::DeviceController(address) + , m_registers(PCI::get_BAR0(address) & 0xfffffffc) + , m_framebuffer_addr(PCI::get_BAR2(address) & 0xfffffffc) +{ + dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Native Graphics Adapter @ {}", address); + auto bar0_space_size = PCI::get_BAR_space_size(address, 0); + VERIFY(bar0_space_size == 0x80000); + dmesgln("Intel Native Graphics Adapter @ {}, MMIO @ {}, space size is {:x} bytes", address, PhysicalAddress(PCI::get_BAR0(address)), bar0_space_size); + dmesgln("Intel Native Graphics Adapter @ {}, framebuffer @ {}", address, PhysicalAddress(PCI::get_BAR2(address))); + m_registers_region = MM.allocate_kernel_region(PhysicalAddress(PCI::get_BAR0(address)).page_base(), bar0_space_size, "Intel Native Graphics Registers", Region::Access::Read | Region::Access::Write); + PCI::enable_bus_mastering(address); + { + ScopedSpinLock control_lock(m_control_lock); + set_gmbus_default_rate(); + set_gmbus_pin_pair(GMBusPinPair::DedicatedAnalog); + } + gmbus_read_edid(); + auto modesetting = calculate_modesetting_from_edid(m_crt_edid, 0); + dmesgln("Intel Native Graphics Adapter @ {}, preferred resolution is {:d}x{:d}", address, modesetting.horizontal.active, modesetting.vertical.active); + + set_crt_resolution(modesetting.horizontal.active, modesetting.vertical.active); +} + +static inline const char* convert_register_index_to_string(IntelGraphics::RegisterIndex index) +{ + switch (index) { + case IntelGraphics::RegisterIndex::PipeAConf: + return "PipeAConf"; + case IntelGraphics::RegisterIndex::PipeBConf: + return "PipeBConf"; + case IntelGraphics::RegisterIndex::GMBusData: + return "GMBusData"; + case IntelGraphics::RegisterIndex::GMBusStatus: + return "GMBusStatus"; + case IntelGraphics::RegisterIndex::GMBusCommand: + return "GMBusCommand"; + case IntelGraphics::RegisterIndex::GMBusClock: + return "GMBusClock"; + case IntelGraphics::RegisterIndex::DisplayPlaneAControl: + return "DisplayPlaneAControl"; + case IntelGraphics::RegisterIndex::DisplayPlaneALinearOffset: + return "DisplayPlaneALinearOffset"; + case IntelGraphics::RegisterIndex::DisplayPlaneAStride: + return "DisplayPlaneAStride"; + case IntelGraphics::RegisterIndex::DisplayPlaneASurface: + return "DisplayPlaneASurface"; + case IntelGraphics::RegisterIndex::DPLLDivisorA0: + return "DPLLDivisorA0"; + case IntelGraphics::RegisterIndex::DPLLDivisorA1: + return "DPLLDivisorA1"; + case IntelGraphics::RegisterIndex::DPLLControlA: + return "DPLLControlA"; + case IntelGraphics::RegisterIndex::DPLLControlB: + return "DPLLControlB"; + case IntelGraphics::RegisterIndex::DPLLMultiplierA: + return "DPLLMultiplierA"; + case IntelGraphics::RegisterIndex::HTotalA: + return "HTotalA"; + case IntelGraphics::RegisterIndex::HBlankA: + return "HBlankA"; + case IntelGraphics::RegisterIndex::HSyncA: + return "HSyncA"; + case IntelGraphics::RegisterIndex::VTotalA: + return "VTotalA"; + case IntelGraphics::RegisterIndex::VBlankA: + return "VBlankA"; + case IntelGraphics::RegisterIndex::VSyncA: + return "VSyncA"; + case IntelGraphics::RegisterIndex::PipeASource: + return "PipeASource"; + case IntelGraphics::RegisterIndex::AnalogDisplayPort: + return "AnalogDisplayPort"; + case IntelGraphics::RegisterIndex::VGADisplayPlaneControl: + return "VGADisplayPlaneControl"; + default: + VERIFY_NOT_REACHED(); + } +} + +void IntelNativeGraphicsAdapter::write_to_register(IntelGraphics::RegisterIndex index, u32 value) const +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_registers_region); + ScopedSpinLock lock(m_registers_lock); + dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics {}: Write to {} value of {:x}", pci_address(), convert_register_index_to_string(index), value); + auto* reg = (volatile u32*)m_registers_region->vaddr().offset(index).as_ptr(); + *reg = value; +} +u32 IntelNativeGraphicsAdapter::read_from_register(IntelGraphics::RegisterIndex index) const +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_registers_region); + ScopedSpinLock lock(m_registers_lock); + auto* reg = (volatile u32*)m_registers_region->vaddr().offset(index).as_ptr(); + u32 value = *reg; + dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics {}: Read from {} value of {:x}", pci_address(), convert_register_index_to_string(index), value); + return value; +} + +bool IntelNativeGraphicsAdapter::pipe_a_enabled() const +{ + VERIFY(m_control_lock.is_locked()); + return read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30); +} +bool IntelNativeGraphicsAdapter::pipe_b_enabled() const +{ + VERIFY(m_control_lock.is_locked()); + return read_from_register(IntelGraphics::RegisterIndex::PipeBConf) & (1 << 30); +} + +bool IntelNativeGraphicsAdapter::gmbus_wait_for(GMBusStatus desired_status, Optional milliseconds_timeout) +{ + VERIFY(m_control_lock.is_locked()); + size_t milliseconds_passed = 0; + while (1) { + if (milliseconds_timeout.has_value() && milliseconds_timeout.value() < milliseconds_passed) + return false; + full_memory_barrier(); + u32 status = read_from_register(IntelGraphics::RegisterIndex::GMBusStatus); + 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(); + } + IO::delay(1000); + milliseconds_passed++; + } +} + +void IntelNativeGraphicsAdapter::gmbus_write(unsigned address, u32 byte) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(address < 256); + full_memory_barrier(); + write_to_register(IntelGraphics::RegisterIndex::GMBusData, byte); + full_memory_barrier(); + write_to_register(IntelGraphics::RegisterIndex::GMBusCommand, ((address << 1) | (1 << 16) | (GMBusCycle::Wait << 25) | (1 << 30))); + full_memory_barrier(); + gmbus_wait_for(GMBusStatus::TransactionCompletion, {}); +} +void IntelNativeGraphicsAdapter::gmbus_read(unsigned address, u8* buf, size_t length) +{ + VERIFY(address < 256); + VERIFY(m_control_lock.is_locked()); + size_t nread = 0; + auto read_set = [&] { + full_memory_barrier(); + u32 data = read_from_register(IntelGraphics::RegisterIndex::GMBusData); + 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(); + write_to_register(IntelGraphics::RegisterIndex::GMBusCommand, (1 | (address << 1) | (length << 16) | (GMBusCycle::Wait << 25) | (1 << 30))); + full_memory_barrier(); + while (nread < length) { + gmbus_wait_for(GMBusStatus::HardwareReady, {}); + read_set(); + } + gmbus_wait_for(GMBusStatus::TransactionCompletion, {}); +} + +void IntelNativeGraphicsAdapter::gmbus_read_edid() +{ + ScopedSpinLock control_lock(m_control_lock); + gmbus_write(DDC2_I2C_ADDRESS, 0); + gmbus_read(DDC2_I2C_ADDRESS, (u8*)&m_crt_edid, sizeof(Graphics::VideoInfoBlock)); +} + +bool IntelNativeGraphicsAdapter::is_resolution_valid(size_t, size_t) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + // FIXME: Check that we are able to modeset to the requested resolution! + return true; +} + +void IntelNativeGraphicsAdapter::disable_output() +{ + VERIFY(m_control_lock.is_locked()); + disable_dac_output(); + disable_all_planes(); + disable_pipe_a(); + disable_pipe_b(); + disable_vga_emulation(); + disable_dpll(); +} + +void IntelNativeGraphicsAdapter::enable_output(PhysicalAddress fb_address, size_t width) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(!pipe_a_enabled()); + + enable_pipe_a(); + enable_primary_plane(fb_address, width); + enable_dac_output(); +} + +bool IntelNativeGraphicsAdapter::set_crt_resolution(size_t width, size_t height) +{ + ScopedSpinLock control_lock(m_control_lock); + ScopedSpinLock modeset_lock(m_modeset_lock); + if (!is_resolution_valid(width, height)) { + return false; + } + + // FIXME: Get the requested resolution from the EDID!! + auto modesetting = calculate_modesetting_from_edid(m_crt_edid, 0); + + disable_output(); + + auto dac_multiplier = compute_dac_multiplier(modesetting.pixel_clock_in_khz); + auto pll_settings = create_pll_settings((1000 * modesetting.pixel_clock_in_khz * dac_multiplier), 96'000'000, G35Limits); + if (!pll_settings.has_value()) + VERIFY_NOT_REACHED(); + auto settings = pll_settings.value(); + dbgln_if(INTEL_GRAPHICS_DEBUG, "PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2); + set_dpll_registers(pll_settings.value(), dac_multiplier); + set_display_timings(modesetting); + auto address = PhysicalAddress(PCI::get_BAR2(pci_address()) & 0xfffffff0); + VERIFY(!address.is_null()); + enable_output(address, width); + + m_framebuffer_width = width; + m_framebuffer_height = height; + m_framebuffer_stride = width * 4; + + return true; +} + +void IntelNativeGraphicsAdapter::set_display_timings(const Graphics::Modesetting& modesetting) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 31))); + VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30))); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "htotal - {}, {}", (modesetting.horizontal.active - 1), (modesetting.horizontal.total - 1)); + write_to_register(IntelGraphics::RegisterIndex::HTotalA, (modesetting.horizontal.active - 1) | (modesetting.horizontal.total - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "hblank - {}, {}", (modesetting.horizontal.blanking_start() - 1), (modesetting.horizontal.blanking_end() - 1)); + write_to_register(IntelGraphics::RegisterIndex::HBlankA, (modesetting.horizontal.blanking_start() - 1) | (modesetting.horizontal.blanking_end() - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "hsync - {}, {}", (modesetting.horizontal.sync_start - 1), (modesetting.horizontal.sync_end - 1)); + write_to_register(IntelGraphics::RegisterIndex::HSyncA, (modesetting.horizontal.sync_start - 1) | (modesetting.horizontal.sync_end - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "vtotal - {}, {}", (modesetting.vertical.active - 1), (modesetting.vertical.total - 1)); + write_to_register(IntelGraphics::RegisterIndex::VTotalA, (modesetting.vertical.active - 1) | (modesetting.vertical.total - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "vblank - {}, {}", (modesetting.vertical.blanking_start() - 1), (modesetting.vertical.blanking_end() - 1)); + write_to_register(IntelGraphics::RegisterIndex::VBlankA, (modesetting.vertical.blanking_start() - 1) | (modesetting.vertical.blanking_end() - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "vsync - {}, {}", (modesetting.vertical.sync_start - 1), (modesetting.vertical.sync_end - 1)); + write_to_register(IntelGraphics::RegisterIndex::VSyncA, (modesetting.vertical.sync_start - 1) | (modesetting.vertical.sync_end - 1) << 16); + + dbgln_if(INTEL_GRAPHICS_DEBUG, "sourceSize - {}, {}", (modesetting.vertical.active - 1), (modesetting.horizontal.active - 1)); + write_to_register(IntelGraphics::RegisterIndex::PipeASource, (modesetting.vertical.active - 1) | (modesetting.horizontal.active - 1) << 16); + + IO::delay(200); +} + +bool IntelNativeGraphicsAdapter::wait_for_enabled_pipe_a(size_t milliseconds_timeout) const +{ + size_t current_time = 0; + while (current_time < milliseconds_timeout) { + if (pipe_a_enabled()) + return true; + IO::delay(1000); + current_time++; + } + return false; +} +bool IntelNativeGraphicsAdapter::wait_for_disabled_pipe_a(size_t milliseconds_timeout) const +{ + size_t current_time = 0; + while (current_time < milliseconds_timeout) { + if (!pipe_a_enabled()) + return true; + IO::delay(1000); + current_time++; + } + return false; +} + +bool IntelNativeGraphicsAdapter::wait_for_disabled_pipe_b(size_t milliseconds_timeout) const +{ + size_t current_time = 0; + while (current_time < milliseconds_timeout) { + if (!pipe_b_enabled()) + return true; + IO::delay(1000); + current_time++; + } + return false; +} + +void IntelNativeGraphicsAdapter::disable_dpll() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & ~0x80000000); + write_to_register(IntelGraphics::RegisterIndex::DPLLControlB, read_from_register(IntelGraphics::RegisterIndex::DPLLControlB) & ~0x80000000); +} + +void IntelNativeGraphicsAdapter::disable_pipe_a() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & ~0x80000000); + dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A"); + wait_for_disabled_pipe_a(100); + dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A - done."); +} + +void IntelNativeGraphicsAdapter::disable_pipe_b() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeBConf) & ~0x80000000); + dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B"); + wait_for_disabled_pipe_b(100); + dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B - done."); +} + +void IntelNativeGraphicsAdapter::set_gmbus_default_rate() +{ + // FIXME: Verify GMBUS Rate Select is set only when GMBUS is idle + VERIFY(m_control_lock.is_locked()); + // Set the rate to 100KHz + write_to_register(IntelGraphics::RegisterIndex::GMBusClock, read_from_register(IntelGraphics::RegisterIndex::GMBusClock) & ~(0b111 << 8)); +} + +void IntelNativeGraphicsAdapter::enable_pipe_a() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 31))); + VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30))); + write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeAConf) | 0x80000000); + dbgln_if(INTEL_GRAPHICS_DEBUG, "enabling Pipe A"); + // FIXME: Seems like my video card is buggy and doesn't set the enabled bit (bit 30)!! + wait_for_enabled_pipe_a(100); + dbgln_if(INTEL_GRAPHICS_DEBUG, "enabling Pipe A - done."); +} + +void IntelNativeGraphicsAdapter::enable_primary_plane(PhysicalAddress fb_address, size_t width) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + VERIFY(((width * 4) % 64 == 0)); + + write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAStride, width * 4); + write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneALinearOffset, 0); + write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneASurface, fb_address.get()); + + // FIXME: Serenity uses BGR 32 bit pixel format, but maybe we should try to determine it somehow! + write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl, (read_from_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl) & (~(0b1111 << 26))) | (0b0110 << 26) | (1 << 31)); +} + +void IntelNativeGraphicsAdapter::set_dpll_registers(const PLLSettings& settings, size_t dac_multiplier) +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::DPLLDivisorA0, (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16)); + write_to_register(IntelGraphics::RegisterIndex::DPLLDivisorA1, (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16)); + + write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & ~0x80000000); + + IO::delay(200); + + write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, (6 << 9) | (settings.p1) << 16 | (1 << 26) | (1 << 28) | (1 << 31)); + write_to_register(IntelGraphics::RegisterIndex::DPLLMultiplierA, (dac_multiplier - 1) | ((dac_multiplier - 1) << 8)); + + // The specification says we should wait (at least) about 150 microseconds + // after enabling the DPLL to allow the clock to stabilize + IO::delay(200); + VERIFY(read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & (1 << 31)); +} + +void IntelNativeGraphicsAdapter::set_gmbus_pin_pair(GMBusPinPair pin_pair) +{ + // FIXME: Verify GMBUS is idle + VERIFY(m_control_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::GMBusClock, (read_from_register(IntelGraphics::RegisterIndex::GMBusClock) & (~0b111)) | (pin_pair & 0b111)); +} + +void IntelNativeGraphicsAdapter::disable_dac_output() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, 0b11 << 10); +} + +void IntelNativeGraphicsAdapter::enable_dac_output() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, (read_from_register(IntelGraphics::RegisterIndex::AnalogDisplayPort) & (~(0b11 << 10))) | 0x80000000); +} + +void IntelNativeGraphicsAdapter::disable_vga_emulation() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl, (read_from_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl) & (~(1 << 30))) | 0x80000000); +} + +void IntelNativeGraphicsAdapter::disable_all_planes() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_modeset_lock.is_locked()); + write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl, read_from_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl) & ~(1 << 31)); +} + +void IntelNativeGraphicsAdapter::initialize_framebuffer_devices() +{ + auto address = PhysicalAddress(PCI::get_BAR2(pci_address()) & 0xfffffff0); + VERIFY(!address.is_null()); + VERIFY(m_framebuffer_stride != 0); + VERIFY(m_framebuffer_height != 0); + VERIFY(m_framebuffer_width != 0); + m_framebuffer = m_framebuffer = RawFramebufferDevice::create(*this, address, m_framebuffer_stride, m_framebuffer_width, m_framebuffer_height); +} +} diff --git a/Kernel/Graphics/IntelNativeGraphicsAdapter.h b/Kernel/Graphics/IntelNativeGraphicsAdapter.h new file mode 100644 index 00000000000..a2219291187 --- /dev/null +++ b/Kernel/Graphics/IntelNativeGraphicsAdapter.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2021, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel { + +namespace IntelGraphics { + +enum RegisterIndex { + PipeAConf = 0x70008, + PipeBConf = 0x71008, + GMBusData = 0x510C, + GMBusStatus = 0x5108, + GMBusCommand = 0x5104, + GMBusClock = 0x5100, + DisplayPlaneAControl = 0x70180, + DisplayPlaneALinearOffset = 0x70184, + DisplayPlaneAStride = 0x70188, + DisplayPlaneASurface = 0x7019C, + DPLLDivisorA0 = 0x6040, + DPLLDivisorA1 = 0x6044, + DPLLControlA = 0x6014, + DPLLControlB = 0x6018, + DPLLMultiplierA = 0x601C, + HTotalA = 0x60000, + HBlankA = 0x60004, + HSyncA = 0x60008, + VTotalA = 0x6000C, + VBlankA = 0x60010, + VSyncA = 0x60014, + PipeASource = 0x6001C, + AnalogDisplayPort = 0x61100, + VGADisplayPlaneControl = 0x71400, +}; +} + +class IntelNativeGraphicsAdapter final + : public GraphicsDevice + , public PCI::DeviceController { + AK_MAKE_ETERNAL +public: + 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 }; + }; + struct PLLParameterLimit { + size_t min, max; + }; + struct PLLMaxSettings { + PLLParameterLimit dot_clock, vco, n, m, m1, m2, p, p1, p2; + }; + +private: + enum GMBusPinPair : u8 { + None = 0, + DedicatedControl = 1, + DedicatedAnalog = 0b10, + IntegratedDigital = 0b11, + sDVO = 0b101, + Dconnector = 0b111, + }; + + enum class GMBusStatus { + TransactionCompletion, + HardwareReady + }; + + enum GMBusCycle { + Wait = 1, + Stop = 4, + }; + +public: + static RefPtr initialize(PCI::Address); + +private: + explicit IntelNativeGraphicsAdapter(PCI::Address); + + void write_to_register(IntelGraphics::RegisterIndex, u32 value) const; + u32 read_from_register(IntelGraphics::RegisterIndex) const; + + // ^GraphicsDevice + virtual void initialize_framebuffer_devices() override; + virtual Type type() const override { return Type::VGACompatible; } + + bool pipe_a_enabled() const; + bool pipe_b_enabled() const; + + bool is_resolution_valid(size_t width, size_t height); + + bool set_crt_resolution(size_t width, size_t height); + + void disable_output(); + void enable_output(PhysicalAddress fb_address, size_t width); + + void disable_vga_emulation(); + + void disable_dac_output(); + void enable_dac_output(); + + void disable_all_planes(); + void disable_pipe_a(); + void disable_pipe_b(); + void disable_dpll(); + + void set_dpll_registers(const PLLSettings&, size_t dac_multiplier); + void set_display_timings(const Graphics::Modesetting&); + void enable_pipe_a(); + void set_framebuffer_parameters(size_t, size_t); + void enable_primary_plane(PhysicalAddress fb_address, size_t stride); + + bool wait_for_enabled_pipe_a(size_t milliseconds_timeout) const; + bool wait_for_disabled_pipe_a(size_t milliseconds_timeout) const; + bool wait_for_disabled_pipe_b(size_t milliseconds_timeout) const; + + void set_gmbus_default_rate(); + void set_gmbus_pin_pair(GMBusPinPair pin_pair); + + // FIXME: It would be better if we generalize the I2C access later on + void gmbus_read_edid(); + void gmbus_write(unsigned address, u32 byte); + void gmbus_read(unsigned address, u8* buf, size_t length); + bool gmbus_wait_for(GMBusStatus desired_status, Optional milliseconds_timeout); + + Optional create_pll_settings(u64 target_frequency, u64 reference_clock, const PLLMaxSettings&); + + SpinLock m_control_lock; + SpinLock m_modeset_lock; + mutable SpinLock m_registers_lock; + + Graphics::VideoInfoBlock m_crt_edid; + const PhysicalAddress m_registers; + const PhysicalAddress m_framebuffer_addr; + + OwnPtr m_registers_region; + + size_t m_framebuffer_width { 0 }; + size_t m_framebuffer_height { 0 }; + size_t m_framebuffer_stride { 0 }; + +protected: + RefPtr m_framebuffer; +}; + +} diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 66e696b2ecc..037cbb8caba 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -136,6 +136,7 @@ set(HTTPSJOB_DEBUG ON) set(ICMP_DEBUG ON) set(ICO_DEBUG ON) set(IPV4_DEBUG ON) +set(INTEL_GRAPHICS_DEBUG ON) set(IRC_DEBUG ON) set(KEYBOARD_DEBUG ON) set(LEXER_DEBUG ON)