From 8388fe51b536fa89789575de3025ae958df91f4d Mon Sep 17 00:00:00 2001 From: Edwin Rijkee Date: Thu, 24 Aug 2023 17:05:42 +0200 Subject: [PATCH] Kernel: Add a framebuffer driver for 3Dfx Voodoo 3 A bit old but a relatively uncomplicated device capable of outputting 1920x1080 video with 32-bit color. Tested with a Voodoo 3 3000 16MB PCI card. Resolution switching from DisplaySettings also works. If the requested mode contains timing information, it is used directly. Otherwise, display timing values are selected from the EDID. First the detailed timings are checked, and then standard and established timings for which there is a matching DMT mode. The driver does not (yet) read the actual EDID, so the generic EDID in DisplayConnector now includes a set of common display modes to make this work. The driver should also be compatible with the Voodoo Banshee, 4 and 5 but I don't have these cards to test this with. The PCI IDs of these cards are included as a commented line in case someone wants to give it a try. --- Kernel/Bus/PCI/IDs.h | 1 + Kernel/CMakeLists.txt | 2 + Kernel/Debug.h.in | 4 + Kernel/Devices/GPU/3dfx/Definitions.h | 209 +++++++++ Kernel/Devices/GPU/3dfx/GraphicsAdapter.cpp | 70 +++ Kernel/Devices/GPU/3dfx/GraphicsAdapter.h | 32 ++ .../GPU/3dfx/VoodooDisplayConnector.cpp | 416 ++++++++++++++++++ .../Devices/GPU/3dfx/VoodooDisplayConnector.h | 59 +++ Kernel/Devices/GPU/DisplayConnector.cpp | 13 +- Kernel/Devices/GPU/Management.cpp | 2 + Meta/CMake/all_the_debug_macros.cmake | 1 + 11 files changed, 805 insertions(+), 4 deletions(-) create mode 100644 Kernel/Devices/GPU/3dfx/Definitions.h create mode 100644 Kernel/Devices/GPU/3dfx/GraphicsAdapter.cpp create mode 100644 Kernel/Devices/GPU/3dfx/GraphicsAdapter.h create mode 100644 Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.cpp create mode 100644 Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h diff --git a/Kernel/Bus/PCI/IDs.h b/Kernel/Bus/PCI/IDs.h index dc393baf914..688299c5a2a 100644 --- a/Kernel/Bus/PCI/IDs.h +++ b/Kernel/Bus/PCI/IDs.h @@ -17,6 +17,7 @@ enum VendorID { QEMUOld = 0x1234, VirtualBox = 0x80ee, VMWare = 0x15ad, + Tdfx = 0x121a, }; enum DeviceID { diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 53d0dcdbb07..56734f25ed5 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -83,6 +83,8 @@ set(KERNEL_SOURCES Devices/Generic/RandomDevice.cpp Devices/Generic/SelfTTYDevice.cpp Devices/Generic/ZeroDevice.cpp + Devices/GPU/3dfx/GraphicsAdapter.cpp + Devices/GPU/3dfx/VoodooDisplayConnector.cpp Devices/GPU/Bochs/GraphicsAdapter.cpp Devices/GPU/Bochs/QEMUDisplayConnector.cpp Devices/GPU/Console/BootFramebufferConsole.cpp diff --git a/Kernel/Debug.h.in b/Kernel/Debug.h.in index 1d8285c982c..4deff94392e 100644 --- a/Kernel/Debug.h.in +++ b/Kernel/Debug.h.in @@ -299,6 +299,10 @@ #cmakedefine01 TCP_SOCKET_DEBUG #endif +#ifndef TDFX_DEBUG +#cmakedefine01 TDFX_DEBUG +#endif + #ifndef THREAD_DEBUG #cmakedefine01 THREAD_DEBUG #endif diff --git a/Kernel/Devices/GPU/3dfx/Definitions.h b/Kernel/Devices/GPU/3dfx/Definitions.h new file mode 100644 index 00000000000..8a0e2a76546 --- /dev/null +++ b/Kernel/Devices/GPU/3dfx/Definitions.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2023, Edwin Rijkee + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Kernel::VoodooGraphics { + +enum class VGAPort : u16 { + AttributeController = 0x3c0, + MiscOutputWrite = 0x3c2, + SequencerIndex = 0x3c4, + SequencerData = 0x3c5, + MiscOutputRead = 0x3cc, + GraphicsControllerIndex = 0x3ce, + GraphicsControllerData = 0x3cf, + CrtcIndex = 0x3d4, + CrtcData = 0x3d5, + InputStatus1 = 0x3da, +}; + +enum CRTCHorizontalBlankingEndFlags : u8 { + CompatibilityRead = 1 << 7 +}; + +enum CRTCVerticalSyncEndFlags : u8 { + EnableVertInt = 1 << 5, + CRTCRegsWriteProt = 1 << 7 +}; + +enum CRTCModeControlFlags : u8 { + ByteWordMode = 1 << 6, + TimingEnable = 1 << 7 +}; + +enum GraphicsControllerMiscellaneousFlags : u8 { + MemoryMapEGAVGAExtended = 1 << 2, +}; + +enum AttributeControllerModeFlags : u8 { + GraphicsMode = 1 << 0, + PixelWidth = 1 << 6, +}; + +enum SequencerResetFlags : u8 { + AsynchronousReset = 1 << 0, + SynchronousReset = 1 << 1, +}; + +enum SequencerClockingModeFlags : u8 { + DotClock8 = 1 << 0, +}; + +enum MiscellaneousOutputFlags : u8 { + CRTCAddressColor = 1 << 0, + ClockSelectPLL = 0b1100, + VerticalSyncPositive = 1 << 7, + HorizontalSyncPositive = 1 << 6, +}; + +enum DacModeFlags : u32 { + DacMode2x = 1 << 0, +}; + +enum VgaInit0Flags : u32 { + FIFODepth8Bit = 1 << 2, + EnableVgaExtensions = 1 << 6, + WakeUpSelect3C3 = 1 << 8, + EnableAltReadback = 1 << 10, + ExtendedShiftOut = 1 << 12, +}; + +enum VidProcCfgFlags : u32 { + VideoProcessorEnable = 1 << 0, + DesktopSurfaceEnable = 1 << 7, + DesktopCLUTBypass = 1 << 10, + DesktopPixelFormat32Bit = 0b11 << 18, + TwoXMode = 1 << 26, +}; + +struct PLLSettings { + static i32 const reference_frequency_in_khz = 14318; + i32 m = 0; + i32 n = 0; + i32 k = 0; + + int frequency_in_khz() const + { + return (reference_frequency_in_khz * (n + 2) / (m + 2)) >> k; + } + + u32 register_value() const + { + return (n << 8) | (m << 2) | k; + } +}; + +// CRT Controller Registers +struct CRRegisters { + u8 horizontal_total; // CR0 + u8 horizontal_display_enable_end; // CR1 + u8 horizontal_blanking_start; // CR2 + u8 horizontal_blanking_end; // CR3 + u8 horizontal_sync_start; // CR4 + u8 horizontal_sync_end; // CR5 + u8 vertical_total; // CR6 + u8 overflow; // CR7 + u8 reserved_0; // CR8 + u8 maximum_scan_line; // CR9 + u8 reserved_1[6]; + u8 vertical_sync_start; // CR10 + u8 vertical_sync_end; // CR11 + u8 vertical_display_enable_end; // CR12 + u8 reserved_2[2]; + u8 vertical_blanking_start; // CR15 + u8 vertical_blanking_end; // CR16 + u8 mode_control; // CR17 + u8 reserved_3[2]; + u8 horizontal_extensions; // CR1A + u8 vertical_extensions; // CR1B +}; + +// Graphics Controller Registers +struct GRRegisters { + u8 reserved_0[6]; + u8 graphics_controller_miscellaneous; // GR6 + u8 reserved_1[2]; +}; + +// Attribute Controller Registers +struct ARRegisters { + u8 reserved_0[15]; + u8 attribute_controller_mode; // AR10 + u8 reserved_1[5]; +}; + +// Sequencer Registers +struct SRRegisters { + u8 sequencer_reset; // SR0 + u8 sequencer_clocking_mode; // SR1 + u8 reserved[3]; +}; + +struct ModeRegisters { + u32 vid_screen_size = 0; + u32 vid_desktop_overlay_stride = 0; + u8 misc_out_reg = 0; + u32 vga_init0 = 0; + u32 vid_proc_cfg = 0; + u32 dac_mode = 0; + u32 pll_ctrl0 = 0; + + union { + Array cr_data = { 0 }; + CRRegisters cr; + }; + + union { + Array gr_data = { 0 }; + GRRegisters gr; + }; + + union { + Array ar_data = { 0 }; + ARRegisters ar; + }; + + union { + Array sr_data = { 0 }; + SRRegisters sr; + }; +}; + +static_assert(sizeof(ModeRegisters::cr_data) == sizeof(ModeRegisters::cr)); +static_assert(sizeof(ModeRegisters::gr_data) == sizeof(ModeRegisters::gr)); +static_assert(sizeof(ModeRegisters::ar_data) == sizeof(ModeRegisters::ar)); + +struct [[gnu::packed]] RegisterMap { + u32 status; + u32 reserved_0[9]; + u32 vga_init0; + u32 reserved_1[5]; + u32 pll_ctrl0; + u32 reserved_2[2]; + u32 dac_mode; + u32 reserved_3[3]; + + u32 vid_proc_cfg; + u32 reserved_4[14]; + u32 vid_screen_size; + u32 reserved_5[18]; + u32 vid_desktop_start_addr; + u32 vid_desktop_overlay_stride; +}; + +static_assert(__builtin_offsetof(RegisterMap, status) == 0); +static_assert(__builtin_offsetof(RegisterMap, vga_init0) == 0x28); +static_assert(__builtin_offsetof(RegisterMap, pll_ctrl0) == 0x40); +static_assert(__builtin_offsetof(RegisterMap, dac_mode) == 0x4c); +static_assert(__builtin_offsetof(RegisterMap, vid_proc_cfg) == 0x5c); +static_assert(__builtin_offsetof(RegisterMap, vid_screen_size) == 0x98); +static_assert(__builtin_offsetof(RegisterMap, vid_desktop_start_addr) == 0xe4); +static_assert(__builtin_offsetof(RegisterMap, vid_desktop_overlay_stride) == 0xe8); +} diff --git a/Kernel/Devices/GPU/3dfx/GraphicsAdapter.cpp b/Kernel/Devices/GPU/3dfx/GraphicsAdapter.cpp new file mode 100644 index 00000000000..74c20792394 --- /dev/null +++ b/Kernel/Devices/GPU/3dfx/GraphicsAdapter.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, Edwin Rijkee + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +static constexpr u16 supported_models[] { + // 0x0003, // Banshee (untested) + 0x0005, // Voodoo 3 + // 0x0009 // Voodoo 4 / Voodoo 5 (untested) +}; + +static bool is_supported_model(u16 device_id) +{ + for (auto& id : supported_models) { + if (id == device_id) + return true; + } + return false; +} + +UNMAP_AFTER_INIT ErrorOr VoodooGraphicsAdapter::probe(PCI::DeviceIdentifier const& pci_device_identifier) +{ + PCI::HardwareID id = pci_device_identifier.hardware_id(); + return id.vendor_id == PCI::VendorID::Tdfx && is_supported_model(id.device_id); +} + +UNMAP_AFTER_INIT ErrorOr> VoodooGraphicsAdapter::create(PCI::DeviceIdentifier const& pci_device_identifier) +{ + auto adapter = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) VoodooGraphicsAdapter(pci_device_identifier))); + MUST(adapter->initialize_adapter(pci_device_identifier)); + return adapter; +} + +UNMAP_AFTER_INIT VoodooGraphicsAdapter::VoodooGraphicsAdapter(PCI::DeviceIdentifier const& device_identifier) + : PCI::Device(const_cast(device_identifier)) +{ +} + +UNMAP_AFTER_INIT ErrorOr VoodooGraphicsAdapter::initialize_adapter(PCI::DeviceIdentifier const& pci_device_identifier) +{ + PCI::enable_io_space(device_identifier()); + PCI::enable_memory_space(device_identifier()); + + auto mmio_addr = PhysicalAddress(PCI::get_BAR0(pci_device_identifier) & PCI::bar_address_mask); + auto mmio_size = PCI::get_BAR_space_size(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR0); + dbgln_if(TDFX_DEBUG, "3dfx mmio addr {} size {}", mmio_addr, mmio_size); + auto mmio_mapping = TRY(Memory::map_typed(mmio_addr, mmio_size, Memory::Region::Access::Read | Memory::Region::Access::Write)); + + auto vmem_addr = PhysicalAddress(PCI::get_BAR1(pci_device_identifier) & PCI::bar_address_mask); + auto vmem_size = PCI::get_BAR_space_size(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR1); + dbgln_if(TDFX_DEBUG, "3dfx vmem addr {} size {}", vmem_addr, vmem_size); + + auto io_window = TRY(IOWindow::create_for_pci_device_bar(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR2)); + + m_display_connector = VoodooGraphics::VoodooDisplayConnector::must_create(vmem_addr, vmem_size, move(mmio_mapping), move(io_window)); + TRY(m_display_connector->set_safe_mode_setting()); + + return {}; +} + +} diff --git a/Kernel/Devices/GPU/3dfx/GraphicsAdapter.h b/Kernel/Devices/GPU/3dfx/GraphicsAdapter.h new file mode 100644 index 00000000000..af70abfb89d --- /dev/null +++ b/Kernel/Devices/GPU/3dfx/GraphicsAdapter.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, Edwin Rijkee + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Kernel { + +class VoodooGraphicsAdapter final : public GenericGraphicsAdapter + , public PCI::Device { + +public: + static ErrorOr probe(PCI::DeviceIdentifier const&); + static ErrorOr> create(PCI::DeviceIdentifier const&); + virtual ~VoodooGraphicsAdapter() = default; + virtual StringView device_name() const override { return "VoodooGraphicsAdapter"sv; } + +private: + ErrorOr initialize_adapter(PCI::DeviceIdentifier const&); + + explicit VoodooGraphicsAdapter(PCI::DeviceIdentifier const&); + + LockRefPtr m_display_connector; +}; +} diff --git a/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.cpp b/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.cpp new file mode 100644 index 00000000000..72f51bb1979 --- /dev/null +++ b/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2023, Edwin Rijkee + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Kernel::VoodooGraphics { + +NonnullLockRefPtr VoodooDisplayConnector::must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping registers_mapping, NonnullOwnPtr io_window) +{ + auto device_or_error = DeviceManagement::try_create_device(framebuffer_address, framebuffer_resource_size, move(registers_mapping), move(io_window)); + 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 VoodooDisplayConnector::fetch_and_initialize_edid() +{ + // TODO: actually fetch the EDID. + return initialize_edid_for_generic_monitor({}); +} + +ErrorOr VoodooDisplayConnector::create_attached_framebuffer_console() +{ + m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), 1024, 768, 1024 * sizeof(u32)); + GraphicsManagement::the().set_console(*m_framebuffer_console); + return {}; +} + +VoodooDisplayConnector::VoodooDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping registers_mapping, NonnullOwnPtr io_window) + : DisplayConnector(framebuffer_address, framebuffer_resource_size, true) + , m_registers(move(registers_mapping)) + , m_io_window(move(io_window)) +{ +} + +ErrorOr VoodooDisplayConnector::for_each_dmt_timing_in_edid(Function callback) const +{ + IterationDecision iteration_decision = TRY(m_edid_parser->for_each_standard_timing([&](auto& standard_timing) { + auto timing = EDID::DMT::find_timing_by_dmt_id(standard_timing.dmt_id()); + if (!timing) { + return IterationDecision::Continue; + } + + return callback(*timing); + })); + + if (iteration_decision == IterationDecision::Break) { + return iteration_decision; + } + + iteration_decision = TRY(m_edid_parser->for_each_established_timing([&](auto& established_timing) { + auto timing = EDID::DMT::find_timing_by_dmt_id(established_timing.dmt_id()); + if (!timing) { + return IterationDecision::Continue; + } + + return callback(*timing); + })); + + return iteration_decision; +} + +auto VoodooDisplayConnector::find_suitable_mode(ModeSetting const& requested_mode) const -> ErrorOr +{ + u32 width = requested_mode.horizontal_active; + u32 height = requested_mode.vertical_active; + ModeSetting result = requested_mode; + + if (requested_mode.horizontal_stride == 0) { + result.horizontal_stride = sizeof(u32) * width; + } + + if (requested_mode.pixel_clock_in_khz != 0) { + dbgln_if(TDFX_DEBUG, "3dfx: Requested mode {}x{} includes timing information", width, height); + return result; + } + + dbgln_if(TDFX_DEBUG, "3dfx: Looking for suitable mode with resolution {}x{}", width, height); + + IterationDecision iteration_decision = TRY(m_edid_parser->for_each_detailed_timing([&](auto& timing, unsigned) { + dbgln_if(TDFX_DEBUG, "3dfx: Considering detailed timing {}x{} @ {}", timing.horizontal_addressable_pixels(), timing.vertical_addressable_lines(), timing.refresh_rate()); + + if (timing.is_interlaced() || timing.horizontal_addressable_pixels() != width || timing.vertical_addressable_lines() != height) { + return IterationDecision::Continue; + } + + result.pixel_clock_in_khz = timing.pixel_clock_khz(); + result.horizontal_front_porch_pixels = timing.horizontal_front_porch_pixels(); + result.horizontal_sync_time_pixels = timing.horizontal_sync_pulse_width_pixels(); + result.horizontal_blank_pixels = timing.horizontal_blanking_pixels(); + result.vertical_front_porch_lines = timing.vertical_front_porch_lines(); + result.vertical_sync_time_lines = timing.vertical_sync_pulse_width_lines(); + result.vertical_blank_lines = timing.vertical_blanking_lines(); + return IterationDecision::Break; + })); + + if (iteration_decision == IterationDecision::Break) { + return result; + } + + iteration_decision = TRY(for_each_dmt_timing_in_edid([&](auto& timing) { + dbgln_if(TDFX_DEBUG, "3dfx: Considering DMT timing {}x{} @ {}", timing.horizontal_pixels, timing.vertical_lines, timing.vertical_frequency_hz()); + + if (timing.scan_type != EDID::DMT::MonitorTiming::ScanType::NonInterlaced || timing.horizontal_pixels != width || timing.vertical_lines != height) { + return IterationDecision::Continue; + } + + result.pixel_clock_in_khz = timing.pixel_clock_khz; + result.horizontal_front_porch_pixels = timing.horizontal_front_porch_pixels; + result.horizontal_sync_time_pixels = timing.horizontal_sync_time_pixels; + result.horizontal_blank_pixels = timing.horizontal_blank_pixels; + result.vertical_front_porch_lines = timing.vertical_front_porch_lines; + result.vertical_sync_time_lines = timing.vertical_sync_time_lines; + result.vertical_blank_lines = timing.vertical_blank_lines; + return IterationDecision::Break; + })); + + if (iteration_decision == IterationDecision::Break) { + return result; + } + + dbgln_if(TDFX_DEBUG, "3dfx: No timing information available for display mode {}x{}", width, height); + return EINVAL; +} + +ErrorOr +VoodooDisplayConnector::set_mode_setting(ModeSetting const& requested_mode_setting) +{ + SpinlockLocker locker(m_modeset_lock); + VERIFY(m_framebuffer_console); + + ModeSetting mode_setting = TRY(find_suitable_mode(requested_mode_setting)); + dbgln_if(TDFX_DEBUG, "VoodooDisplayConnector resolution registers set to - {}x{}", mode_setting.horizontal_active, mode_setting.vertical_active); + + auto regs = TRY(prepare_mode_switch(mode_setting)); + TRY(perform_mode_switch(regs)); + + m_framebuffer_console->set_resolution(mode_setting.horizontal_active, mode_setting.vertical_active, mode_setting.horizontal_stride); + m_current_mode_setting = mode_setting; + return {}; +} + +ErrorOr VoodooDisplayConnector::set_y_offset(size_t) +{ + return ENOTIMPL; +} + +ErrorOr VoodooDisplayConnector::set_safe_mode_setting() +{ + DisplayConnector::ModeSetting safe_mode_set { + .horizontal_stride = 1024 * sizeof(u32), + .pixel_clock_in_khz = 65000, + .horizontal_active = 1024, + .horizontal_front_porch_pixels = 24, + .horizontal_sync_time_pixels = 136, + .horizontal_blank_pixels = 320, + .vertical_active = 768, + .vertical_front_porch_lines = 3, + .vertical_sync_time_lines = 6, + .vertical_blank_lines = 38, + .horizontal_offset = 0, + .vertical_offset = 0, + }; + return set_mode_setting(safe_mode_set); +} + +ErrorOr VoodooDisplayConnector::unblank() +{ + return ENOTIMPL; +} + +ErrorOr VoodooDisplayConnector::flush_first_surface() +{ + return ENOTSUP; +} + +void VoodooDisplayConnector::enable_console() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_framebuffer_console); + m_framebuffer_console->enable(); +} + +void VoodooDisplayConnector::disable_console() +{ + VERIFY(m_control_lock.is_locked()); + VERIFY(m_framebuffer_console); + m_framebuffer_console->disable(); +} + +u8 VoodooDisplayConnector::read_vga(VGAPort port) +{ + return m_io_window->read8(static_cast(port) - 0x300); +} + +u8 VoodooDisplayConnector::read_vga_indexed(VGAPort index_port, VGAPort data_port, u8 index) +{ + m_io_window->write8(static_cast(index_port) - 0x300, index); + return m_io_window->read8(static_cast(data_port) - 0x300); +} + +void VoodooDisplayConnector::write_vga(VGAPort port, u8 value) +{ + m_io_window->write8(static_cast(port) - 0x300, value - 0x300); +} + +void VoodooDisplayConnector::write_vga_indexed(VGAPort index_port, VGAPort data_port, u8 index, u8 value) +{ + m_io_window->write8(static_cast(index_port) - 0x300, index); + m_io_window->write8(static_cast(data_port) - 0x300, value); +} + +ErrorOr VoodooDisplayConnector::wait_for_fifo_space(u32 entries) +{ + VERIFY(entries < 32); + + auto deadline = TimeManagement::the().monotonic_time() + Duration::from_seconds(1); + do { + if ((m_registers->status & 0x1f) >= entries) { + return {}; + } + (void)Thread::current()->sleep(Duration::from_milliseconds(1)); + } while (TimeManagement::the().monotonic_time() < deadline); + + dbgln_if(TDFX_DEBUG, "3dfx: timed out waiting for fifo space"); + return EIO; +} + +PLLSettings VoodooDisplayConnector::calculate_pll(i32 desired_frequency_in_khz) +{ + VoodooGraphics::PLLSettings current; + VoodooGraphics::PLLSettings best; + i32 best_difference; + + best_difference = desired_frequency_in_khz; + + for (current.m = 0; current.m < 64; current.m++) { + for (current.n = 0; current.n < 256; current.n++) { + for (current.k = 0; current.k < 4; current.k++) { + auto frequency_in_khz = current.frequency_in_khz(); + + auto error = AK::abs(frequency_in_khz - desired_frequency_in_khz); + if (error < best_difference) { + best_difference = error; + best = current; + } + } + } + } + + return best; +} + +ErrorOr VoodooDisplayConnector::prepare_mode_switch(ModeSetting const& mode_setting) +{ + u32 width = mode_setting.horizontal_active; + u32 height = mode_setting.vertical_active; + + ModeRegisters regs; + + regs.vga_init0 = EnableVgaExtensions | WakeUpSelect3C3 | EnableAltReadback | FIFODepth8Bit | ExtendedShiftOut; + regs.vid_proc_cfg |= VideoProcessorEnable | DesktopSurfaceEnable | DesktopPixelFormat32Bit | DesktopCLUTBypass; + + // We only want to touch the 2X flag of the DAC Mode register, the other flags are preserved + regs.dac_mode = m_registers->dac_mode & ~DacMode2x; + + u32 const max_pixel_clock_in_khz = 270000; + if (mode_setting.pixel_clock_in_khz > max_pixel_clock_in_khz) { + return ENOTSUP; + } + + u32 horizontal_divisor = 8; + if (mode_setting.pixel_clock_in_khz > max_pixel_clock_in_khz / 2) { + horizontal_divisor = 16; + regs.dac_mode |= DacMode2x; + regs.vid_proc_cfg |= TwoXMode; + } + + dbgln_if(TDFX_DEBUG, "3dfx: Calculating best PLL settings for pixel clock {} KHz", mode_setting.pixel_clock_in_khz); + auto pll = calculate_pll(mode_setting.pixel_clock_in_khz); + dbgln_if(TDFX_DEBUG, "3dfx: Best matching PLL settings: m={}, n={}, k={}. Frequency: {} KHz", pll.m, pll.n, pll.k, pll.frequency_in_khz()); + regs.pll_ctrl0 = pll.register_value(); + regs.vid_screen_size = width | (height << 12); + regs.vid_desktop_overlay_stride = mode_setting.horizontal_stride; + regs.misc_out_reg = ClockSelectPLL | CRTCAddressColor; + if (height < 768) { + regs.misc_out_reg |= VerticalSyncPositive | HorizontalSyncPositive; + } + + u32 hor_total = mode_setting.horizontal_total() / horizontal_divisor - 5; + u32 hor_disp_en_end = width / horizontal_divisor - 1; + u32 hor_sync_start = mode_setting.horizontal_sync_start() / horizontal_divisor; + u32 hor_sync_end = (mode_setting.horizontal_sync_end() / horizontal_divisor) & 0x1f; + u32 hor_blank_start = hor_disp_en_end; + u32 hor_blank_end = hor_total & 0x7f; + + u32 vert_total = mode_setting.vertical_total() - 2; + u32 vert_disp_en_end = height - 1; + u32 vert_sync_start = mode_setting.vertical_sync_start(); + u32 vert_sync_end = mode_setting.vertical_sync_end() & 0xf; + u32 vert_blank_start = mode_setting.vertical_blanking_start() - 1; + u32 vert_blank_end = (mode_setting.vertical_total() - 1) & 0xff; + + if (hor_total > 0x1ff || // 9-bit field + hor_disp_en_end > 0x1ff || // 9-bit field + hor_sync_start > 0x1ff || // 9-bit field + hor_sync_end > 0x1f || // 5-bit field + hor_blank_start > 0x1ff || // 9-bit field + hor_blank_end > 0x7f || // 7-bit field + vert_total > 0x7ff || // 11-bit field + vert_disp_en_end > 0x7ff || // 11-bit field + vert_sync_start > 0x7ff || // 11-bit field + vert_sync_end > 0x0f || // 4-bit field + vert_blank_start > 0x7ff || // 11-bit field + vert_blank_end > 0xff // 8-bit field + ) { + dbgln_if(TDFX_DEBUG, "3dfx: One of the timing values is too large to fit in its register"); + return EOVERFLOW; + } + + // CRT Controller Registers + regs.cr.horizontal_total = hor_total; // bit 0-7 of hor_total + regs.cr.horizontal_display_enable_end = hor_disp_en_end; // bit 0-7 of hor_disp_en_end + regs.cr.horizontal_blanking_start = hor_blank_start; // bit 0-7 of hor_blank_start + regs.cr.horizontal_blanking_end = (hor_blank_end & 0x1f) // bit 0-4 of hor_blank_end + | CompatibilityRead; + regs.cr.horizontal_sync_start = hor_sync_start; // bit 0-7 of hor_sync_start + regs.cr.horizontal_sync_end = ((hor_sync_end & 0x1f) // bit 0-4 of hor_sync_end + | ((hor_blank_end & 0x20) << 2)); // bit 5 of hor_blank_end + regs.cr.vertical_total = vert_total; // bit 0-7 of vert_total + regs.cr.overflow = (((vert_total & 0x100) >> 8) // bit 8 of vert_total + | ((vert_disp_en_end & 0x100) >> 7) // bit 8 of vert_disp_en_end + | ((vert_sync_start & 0x100) >> 6) // bit 8 of vert_sync_start + | ((vert_blank_start & 0x100) >> 5) // bit 8 of vert_blank_start + | ((vert_total & 0x200) >> 4) // bit 9 of vert_disp_en_end + | ((vert_disp_en_end & 0x200) >> 3) // bit 9 of vert_disp_en_end + | ((vert_sync_start & 0x200) >> 2)); // bit 9 of vert_sync_start + regs.cr.maximum_scan_line = ((vert_blank_start & 0x200) >> 4); // bit 9 of vert_blank_start + regs.cr.vertical_sync_start = vert_sync_start; // bit 0-7 of vert_sync_start + regs.cr.vertical_sync_end = (vert_sync_end & 0x0f) // bit 0-3 of vert_sync_end + | EnableVertInt; + regs.cr.vertical_display_enable_end = vert_disp_en_end; // bit 0-7 of vert_disp_en_end + regs.cr.vertical_blanking_start = vert_blank_start; // bit 0-7 of vert_blank_start + regs.cr.vertical_blanking_end = vert_blank_end; // bit 0-7 of vert_blank_end + regs.cr.mode_control = TimingEnable | ByteWordMode; + regs.cr.horizontal_extensions = (hor_total & 0x100) >> 8 // bit 8 of hor_total + | (hor_disp_en_end & 0x100) >> 6 // bit 8 of hor_disp_en_end + | (hor_blank_start & 0x100) >> 4 // bit 8 of hor_blank_start + | (hor_blank_end & 0x40) >> 1 // bit 6 of hor_blank_end + | (hor_sync_start & 0x100) >> 2 // bit 8 of hor_sync_start + | (hor_sync_end & 0x20) << 2; // bit 5 of hor_sync_end + regs.cr.vertical_extensions = (vert_total & 0x400) >> 10 // bit 10 of vert_total + | (vert_disp_en_end & 0x400) >> 8 // bit 10 of vert_disp_en_endx + | (vert_blank_start & 0x400) >> 6 // bit 10 of vert_blank_start + | (vert_blank_end & 0x400) >> 4 // bit 10 of vert_blank_end + | (vert_sync_start & 0x400) >> 4; // bit 10 of vert_sync_start + // Graphics Controller Registers + regs.gr.graphics_controller_miscellaneous = MemoryMapEGAVGAExtended; + + // Attribute Controller Registers + regs.ar.attribute_controller_mode = GraphicsMode | PixelWidth; + + // Sequencer Registers + regs.sr.sequencer_reset = AsynchronousReset | SynchronousReset; + regs.sr.sequencer_clocking_mode = DotClock8; + + return regs; +} + +ErrorOr VoodooDisplayConnector::perform_mode_switch(ModeRegisters const& regs) +{ + TRY(wait_for_fifo_space(2)); + m_registers->vid_proc_cfg = 0; + m_registers->pll_ctrl0 = regs.pll_ctrl0; + + write_vga(VGAPort::MiscOutputWrite, regs.misc_out_reg); + for (u8 i = 0; i < regs.sr_data.size(); i++) { + write_vga_indexed(VGAPort::SequencerIndex, VGAPort::SequencerData, i, regs.sr_data[i]); + } + + // first unprotect CR0 - CR7 + write_vga_indexed(VGAPort::CrtcIndex, VGAPort::CrtcData, 0x11, read_vga_indexed(VGAPort::CrtcIndex, VGAPort::CrtcData, 0x11) & ~CRTCRegsWriteProt); + for (u8 i = 0; i < regs.cr_data.size(); i++) { + write_vga_indexed(VGAPort::CrtcIndex, VGAPort::CrtcData, i, regs.cr_data[i]); + } + + for (u8 i = 0; i < regs.gr_data.size(); i++) { + write_vga_indexed(VGAPort::GraphicsControllerIndex, VGAPort::GraphicsControllerData, i, regs.gr_data[i]); + } + + // The AttributeController IO port flips between the index and the data register on every write. + // Reading InputStatus1 has the side effect of resetting this, this way we know it is in the state we expect + read_vga(VGAPort::InputStatus1); + for (u8 i = 0; i < regs.ar_data.size(); i++) { + write_vga_indexed(VGAPort::AttributeController, VGAPort::AttributeController, i, regs.ar_data[i]); + } + + TRY(wait_for_fifo_space(6)); + m_registers->vga_init0 = regs.vga_init0; + m_registers->dac_mode = regs.dac_mode; + m_registers->vid_desktop_overlay_stride = regs.vid_desktop_overlay_stride; + m_registers->vid_screen_size = regs.vid_screen_size; + m_registers->vid_desktop_start_addr = 0; + m_registers->vid_proc_cfg = regs.vid_proc_cfg; + + return {}; +} +} diff --git a/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h b/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h new file mode 100644 index 00000000000..9c13bf21101 --- /dev/null +++ b/Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, Edwin Rijkee + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Kernel::VoodooGraphics { + +class VoodooDisplayConnector final + : public DisplayConnector { + friend class VoodooGraphicsAdapter; + friend class Kernel::DeviceManagement; + +public: + static NonnullLockRefPtr must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping, NonnullOwnPtr io_window); + +private: + ErrorOr fetch_and_initialize_edid(); + ErrorOr create_attached_framebuffer_console(); + VoodooDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping, NonnullOwnPtr io_window); + + virtual bool mutable_mode_setting_capable() const override final { return false; } + virtual bool double_framebuffering_capable() const override { return false; } + virtual ErrorOr set_mode_setting(ModeSetting const&) override; + virtual ErrorOr set_y_offset(size_t y) override; + virtual ErrorOr set_safe_mode_setting() override final; + virtual ErrorOr unblank() override; + virtual bool partial_flush_support() const override final { return false; } + virtual bool flush_support() const override final { return false; } + virtual bool refresh_rate_support() const override final { return false; } + virtual ErrorOr flush_first_surface() override final; + virtual void enable_console() override final; + virtual void disable_console() override final; + + ErrorOr for_each_dmt_timing_in_edid(Function) const; + ErrorOr find_suitable_mode(ModeSetting const& requested_mode) const; + u8 read_vga(VGAPort port); + u8 read_vga_indexed(VGAPort index_port, VGAPort data_port, u8 index); + void write_vga(VGAPort port, u8 value); + void write_vga_indexed(VGAPort index_port, VGAPort data_port, u8 index, u8 value); + ErrorOr wait_for_fifo_space(u32 minimum_entries); + static PLLSettings calculate_pll(i32 desired_frequency_in_khz); + ErrorOr prepare_mode_switch(ModeSetting const& mode_setting); + ErrorOr perform_mode_switch(ModeRegisters const& regs); + + LockRefPtr m_framebuffer_console; + Memory::TypedMapping m_registers; + NonnullOwnPtr m_io_window; +}; +} diff --git a/Kernel/Devices/GPU/DisplayConnector.cpp b/Kernel/Devices/GPU/DisplayConnector.cpp index bd69c82d2e8..b6e6c246aa2 100644 --- a/Kernel/Devices/GPU/DisplayConnector.cpp +++ b/Kernel/Devices/GPU/DisplayConnector.cpp @@ -197,10 +197,15 @@ ErrorOr DisplayConnector::initialize_edid_for_generic_monitor(Optional #include #include +#include #include #include #include @@ -130,6 +131,7 @@ static constexpr PCIGraphicsDriverInitializer s_initializers[] = { { BochsGraphicsAdapter::probe, BochsGraphicsAdapter::create }, { VirtIOGraphicsAdapter::probe, VirtIOGraphicsAdapter::create }, { VMWareGraphicsAdapter::probe, VMWareGraphicsAdapter::create }, + { VoodooGraphicsAdapter::probe, VoodooGraphicsAdapter::create }, }; UNMAP_AFTER_INIT ErrorOr GraphicsManagement::determine_and_initialize_graphics_device(PCI::DeviceIdentifier const& device_identifier) diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 03aa94e562b..59e8e070d90 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -178,6 +178,7 @@ set(SYSTEM_MENU_DEBUG ON) set(SYSTEMSERVER_DEBUG ON) set(TCP_DEBUG ON) set(TCP_SOCKET_DEBUG ON) +set(TDFX_DEBUG ON) set(TERMCAP_DEBUG ON) set(TERMINAL_DEBUG ON) set(TEXTEDITOR_DEBUG ON)