diff --git a/Kernel/Bus/PCI/IDs.h b/Kernel/Bus/PCI/IDs.h index dc393baf91..688299c5a2 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 53d0dcdbb0..56734f25ed 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 1d8285c982..4deff94392 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 0000000000..8a0e2a7654 --- /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 0000000000..74c2079239 --- /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 0000000000..af70abfb89 --- /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 0000000000..72f51bb197 --- /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 0000000000..9c13bf2110 --- /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 bd69c82d2e..b6e6c246aa 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 03aa94e562..59e8e070d9 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)