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.
This commit is contained in:
Edwin Rijkee 2023-08-24 17:05:42 +02:00 committed by Jelle Raaijmakers
parent 77441079dd
commit 8388fe51b5
Notes: sideshowbarker 2024-07-17 00:37:23 +09:00
11 changed files with 805 additions and 4 deletions

View file

@ -17,6 +17,7 @@ enum VendorID {
QEMUOld = 0x1234,
VirtualBox = 0x80ee,
VMWare = 0x15ad,
Tdfx = 0x121a,
};
enum DeviceID {

View file

@ -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

View file

@ -299,6 +299,10 @@
#cmakedefine01 TCP_SOCKET_DEBUG
#endif
#ifndef TDFX_DEBUG
#cmakedefine01 TDFX_DEBUG
#endif
#ifndef THREAD_DEBUG
#cmakedefine01 THREAD_DEBUG
#endif

View file

@ -0,0 +1,209 @@
/*
* Copyright (c) 2023, Edwin Rijkee <edwin@virtualparadise.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/Types.h>
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<u8, 0x1c> cr_data = { 0 };
CRRegisters cr;
};
union {
Array<u8, 0x09> gr_data = { 0 };
GRRegisters gr;
};
union {
Array<u8, 0x15> ar_data = { 0 };
ARRegisters ar;
};
union {
Array<u8, 0x05> 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);
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2023, Edwin Rijkee <edwin@virtualparadise.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/GPU/3dfx/Definitions.h>
#include <Kernel/Devices/GPU/3dfx/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h>
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<bool> 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<NonnullLockRefPtr<GenericGraphicsAdapter>> 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<PCI::DeviceIdentifier&>(device_identifier))
{
}
UNMAP_AFTER_INIT ErrorOr<void> 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<VoodooGraphics::RegisterMap volatile>(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 {};
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023, Edwin Rijkee <edwin@virtualparadise.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/GenericGraphicsAdapter.h>
namespace Kernel {
class VoodooGraphicsAdapter final : public GenericGraphicsAdapter
, public PCI::Device {
public:
static ErrorOr<bool> probe(PCI::DeviceIdentifier const&);
static ErrorOr<NonnullLockRefPtr<GenericGraphicsAdapter>> create(PCI::DeviceIdentifier const&);
virtual ~VoodooGraphicsAdapter() = default;
virtual StringView device_name() const override { return "VoodooGraphicsAdapter"sv; }
private:
ErrorOr<void> initialize_adapter(PCI::DeviceIdentifier const&);
explicit VoodooGraphicsAdapter(PCI::DeviceIdentifier const&);
LockRefPtr<DisplayConnector> m_display_connector;
};
}

View file

@ -0,0 +1,416 @@
/*
* Copyright (c) 2023, Edwin Rijkee <edwin@virtualparadise.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/GPU/3dfx/VoodooDisplayConnector.h>
#include <Kernel/Devices/GPU/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Time/TimeManagement.h>
namespace Kernel::VoodooGraphics {
NonnullLockRefPtr<VoodooDisplayConnector> VoodooDisplayConnector::must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<RegisterMap volatile> registers_mapping, NonnullOwnPtr<IOWindow> io_window)
{
auto device_or_error = DeviceManagement::try_create_device<VoodooDisplayConnector>(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<void> VoodooDisplayConnector::fetch_and_initialize_edid()
{
// TODO: actually fetch the EDID.
return initialize_edid_for_generic_monitor({});
}
ErrorOr<void> 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<RegisterMap volatile> registers_mapping, NonnullOwnPtr<IOWindow> io_window)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, true)
, m_registers(move(registers_mapping))
, m_io_window(move(io_window))
{
}
ErrorOr<IterationDecision> VoodooDisplayConnector::for_each_dmt_timing_in_edid(Function<IterationDecision(EDID::DMT::MonitorTiming const&)> 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<ModeSetting>
{
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<void>
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<void> VoodooDisplayConnector::set_y_offset(size_t)
{
return ENOTIMPL;
}
ErrorOr<void> 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<void> VoodooDisplayConnector::unblank()
{
return ENOTIMPL;
}
ErrorOr<void> 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<u16>(port) - 0x300);
}
u8 VoodooDisplayConnector::read_vga_indexed(VGAPort index_port, VGAPort data_port, u8 index)
{
m_io_window->write8(static_cast<u16>(index_port) - 0x300, index);
return m_io_window->read8(static_cast<u16>(data_port) - 0x300);
}
void VoodooDisplayConnector::write_vga(VGAPort port, u8 value)
{
m_io_window->write8(static_cast<u16>(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<u16>(index_port) - 0x300, index);
m_io_window->write8(static_cast<u16>(data_port) - 0x300, value);
}
ErrorOr<void> 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<ModeRegisters> 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<void> 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 {};
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2023, Edwin Rijkee <edwin@virtualparadise.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/GPU/3dfx/Definitions.h>
#include <Kernel/Devices/GPU/Console/GenericFramebufferConsole.h>
#include <Kernel/Devices/GPU/DisplayConnector.h>
#include <Kernel/Library/IOWindow.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel::VoodooGraphics {
class VoodooDisplayConnector final
: public DisplayConnector {
friend class VoodooGraphicsAdapter;
friend class Kernel::DeviceManagement;
public:
static NonnullLockRefPtr<VoodooDisplayConnector> must_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<RegisterMap volatile>, NonnullOwnPtr<IOWindow> io_window);
private:
ErrorOr<void> fetch_and_initialize_edid();
ErrorOr<void> create_attached_framebuffer_console();
VoodooDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, Memory::TypedMapping<RegisterMap volatile>, NonnullOwnPtr<IOWindow> io_window);
virtual bool mutable_mode_setting_capable() const override final { return false; }
virtual bool double_framebuffering_capable() const override { return false; }
virtual ErrorOr<void> set_mode_setting(ModeSetting const&) override;
virtual ErrorOr<void> set_y_offset(size_t y) override;
virtual ErrorOr<void> set_safe_mode_setting() override final;
virtual ErrorOr<void> unblank() override;
virtual bool partial_flush_support() const override final { return false; }
virtual bool flush_support() const override final { return false; }
virtual bool refresh_rate_support() const override final { return false; }
virtual ErrorOr<void> flush_first_surface() override final;
virtual void enable_console() override final;
virtual void disable_console() override final;
ErrorOr<IterationDecision> for_each_dmt_timing_in_edid(Function<IterationDecision(EDID::DMT::MonitorTiming const&)>) const;
ErrorOr<ModeSetting> 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<void> wait_for_fifo_space(u32 minimum_entries);
static PLLSettings calculate_pll(i32 desired_frequency_in_khz);
ErrorOr<ModeRegisters> prepare_mode_switch(ModeSetting const& mode_setting);
ErrorOr<void> perform_mode_switch(ModeRegisters const& regs);
LockRefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console;
Memory::TypedMapping<RegisterMap volatile> m_registers;
NonnullOwnPtr<IOWindow> m_io_window;
};
}

View file

@ -197,10 +197,15 @@ ErrorOr<void> DisplayConnector::initialize_edid_for_generic_monitor(Optional<Arr
/* colour space, preferred timing mode) */
0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
/* chromaticity for standard colour space. */
0x00, 0x00, 0x00, /* no default timings */
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01,
0x01, 0x01, 0x01, 0x01, /* no standard timings */
0x21, 0x08, 0x00, /* default timings: 640x480@60, 800x600@60, 1024x768@60 */
0xd1, 0xc0, /* standard timing 1920x1080 @ 60 Hz */
0xb3, 0x00, /* standard timing 1680x1050 @ 60 Hz */
0xa9, 0xc0, /* standard timing 1600x900 @ 60 Hz */
0x95, 0x00, /* standard timing 1440x900 @ 60 Hz */
0x8b, 0xc0, /* standard timing 1360x768 @ 60 Hz */
0x81, 0x80, /* standard timing 1280x1024 @ 60 Hz */
0x81, 0x40, /* standard timing 1280x960 @ 60 Hz */
0x81, 0xc0, /* standard timing 1280x720 @ 60 Hz */
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x02, 0x02,
0x02, 0x02,
/* descriptor block 1 goes below */

View file

@ -13,6 +13,7 @@
#include <Kernel/Boot/Multiboot.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Devices/GPU/3dfx/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/Bochs/GraphicsAdapter.h>
#include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h>
#include <Kernel/Devices/GPU/Intel/NativeGraphicsAdapter.h>
@ -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<void> GraphicsManagement::determine_and_initialize_graphics_device(PCI::DeviceIdentifier const& device_identifier)

View file

@ -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)