mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
524 lines
15 KiB
C++
524 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2012, 2014 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* gpu/bga/bga.cpp
|
|
* Driver for the Bochs VBE Extensions.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sortix/mman.h>
|
|
|
|
#include <sortix/kernel/addralloc.h>
|
|
#include <sortix/kernel/cpu.h>
|
|
#include <sortix/kernel/ioport.h>
|
|
#include <sortix/kernel/kernel.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <sortix/kernel/pci.h>
|
|
#include <sortix/kernel/pci-mmio.h>
|
|
#include <sortix/kernel/refcount.h>
|
|
#include <sortix/kernel/string.h>
|
|
#include <sortix/kernel/textbuffer.h>
|
|
#include <sortix/kernel/video.h>
|
|
|
|
#include "x86-family/memorymanagement.h"
|
|
#include "lfbtextbuffer.h"
|
|
#include "bga.h"
|
|
|
|
namespace Sortix {
|
|
namespace BGA {
|
|
|
|
const uint16_t VBE_DISPI_INDEX_ID = 0;
|
|
const uint16_t VBE_DISPI_INDEX_XRES = 1;
|
|
const uint16_t VBE_DISPI_INDEX_YRES = 2;
|
|
const uint16_t VBE_DISPI_INDEX_BPP = 3;
|
|
const uint16_t VBE_DISPI_INDEX_ENABLE = 4;
|
|
const uint16_t VBE_DISPI_INDEX_BANK = 5;
|
|
const uint16_t VBE_DISPI_INDEX_VIRT_WIDTH = 6;
|
|
const uint16_t VBE_DISPI_INDEX_VIRT_HEIGHT = 7;
|
|
const uint16_t VBE_DISPI_INDEX_X_OFFSET = 8;
|
|
const uint16_t VBE_DISPI_INDEX_Y_OFFSET = 9;
|
|
const uint16_t VBE_DISPI_NUM_REGISTERS = 10;
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
const uint16_t VBE_DISPI_IOPORT_INDEX = 0x01CE;
|
|
const uint16_t VBE_DISPI_IOPORT_DATA = 0x01CF;
|
|
#endif
|
|
|
|
const uint16_t VBE_DISPI_BPP_4 = 0x04;
|
|
const uint16_t VBE_DISPI_BPP_8 = 0x08;
|
|
const uint16_t VBE_DISPI_BPP_15 = 0x0F;
|
|
const uint16_t VBE_DISPI_BPP_16 = 0x10;
|
|
const uint16_t VBE_DISPI_BPP_24 = 0x18;
|
|
const uint16_t VBE_DISPI_BPP_32 = 0x20;
|
|
|
|
const uint16_t VBE_DISPI_DISABLED = 0x00;
|
|
const uint16_t VBE_DISPI_ENABLED = 0x01;
|
|
const uint16_t VBE_DISPI_GETCAPS = 0x02;
|
|
const uint16_t VBE_DISPI_8BIT_DAC = 0x20;
|
|
const uint16_t VBE_DISPI_LFB_ENABLED = 0x40;
|
|
const uint16_t VBE_DISPI_NOCLEARMEM = 0x80;
|
|
|
|
const uint16_t VBE_MIN_SUP_VERSION = 0xB0C0;
|
|
const uint16_t VBE_MIN_POS_VERSION = 0xB0C0;
|
|
const uint16_t VBE_MAX_POS_VERSION = 0xB0CF;
|
|
|
|
static bool IsStandardResolution(uint16_t width, uint16_t height, uint16_t depth)
|
|
{
|
|
if ( depth != VBE_DISPI_BPP_32 ) { return false; }
|
|
if ( width == 720 && height == 400 ) { return true; }
|
|
if ( width == 800 && height == 600 ) { return true; }
|
|
if ( width == 1024 && height == 768 ) { return true; }
|
|
if ( width == 1280 && height == 720 ) { return true; }
|
|
if ( width == 1280 && height == 1024 ) { return true; }
|
|
if ( width == 1600 && height == 900 ) { return true; }
|
|
if ( width == 1920 && height == 1080 ) { return true; }
|
|
return false;
|
|
}
|
|
|
|
class BGADevice : public VideoDevice
|
|
{
|
|
public:
|
|
BGADevice(uint32_t devaddr, addralloc_t fb_alloc, addralloc_t mmio_alloc);
|
|
virtual ~BGADevice();
|
|
|
|
public:
|
|
virtual struct dispmsg_crtc_mode GetCurrentMode(uint64_t connector) const;
|
|
virtual bool SwitchMode(uint64_t connector, struct dispmsg_crtc_mode mode);
|
|
virtual bool Supports(uint64_t connector, struct dispmsg_crtc_mode mode) const;
|
|
virtual struct dispmsg_crtc_mode* GetModes(uint64_t connector, size_t* num_modes) const;
|
|
virtual off_t FrameSize() const;
|
|
virtual ssize_t WriteAt(ioctx_t* ctx, off_t off, const void* buf, size_t count);
|
|
virtual ssize_t ReadAt(ioctx_t* ctx, off_t off, void* buf, size_t count);
|
|
virtual TextBuffer* CreateTextBuffer(uint64_t connector);
|
|
|
|
public:
|
|
bool Initialize();
|
|
|
|
private:
|
|
bool DetectModes() const;
|
|
uint16_t WriteRegister(uint16_t index, uint16_t value);
|
|
uint16_t ReadRegister(uint16_t index);
|
|
uint16_t GetCapability(uint16_t index);
|
|
bool SetVideoMode(uint16_t width, uint16_t height, uint16_t depth, bool keep);
|
|
bool SupportsResolution(uint16_t width, uint16_t height, uint16_t depth);
|
|
|
|
private:
|
|
mutable size_t num_modes;
|
|
mutable struct dispmsg_crtc_mode* modes;
|
|
struct dispmsg_crtc_mode current_mode;
|
|
addralloc_t fb_alloc;
|
|
addralloc_t mmio_alloc;
|
|
uint32_t devaddr;
|
|
uint16_t version;
|
|
uint16_t maxbpp;
|
|
uint16_t maxxres;
|
|
uint16_t maxyres;
|
|
|
|
};
|
|
|
|
BGADevice::BGADevice(uint32_t devaddr, addralloc_t fb_alloc, addralloc_t mmio_alloc) :
|
|
fb_alloc(fb_alloc), mmio_alloc(mmio_alloc), devaddr(devaddr)
|
|
{
|
|
num_modes = 0;
|
|
modes = NULL;
|
|
memset(¤t_mode, 0, sizeof(current_mode));
|
|
version = 0;
|
|
maxbpp = 0;
|
|
maxxres = 0;
|
|
maxyres = 0;
|
|
}
|
|
|
|
BGADevice::~BGADevice()
|
|
{
|
|
UnmapPCIBar(&fb_alloc);
|
|
UnmapPCIBar(&mmio_alloc);
|
|
delete[] modes;
|
|
}
|
|
|
|
uint16_t BGADevice::WriteRegister(uint16_t index, uint16_t value)
|
|
{
|
|
assert(index < VBE_DISPI_NUM_REGISTERS);
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
if ( mmio_alloc.size == 0 )
|
|
{
|
|
outport16(VBE_DISPI_IOPORT_INDEX, index);
|
|
return outport16(VBE_DISPI_IOPORT_DATA, value);
|
|
}
|
|
#endif
|
|
volatile little_uint16_t* regs =
|
|
(volatile little_uint16_t*) (mmio_alloc.from + 0x500);
|
|
return regs[index] = value;
|
|
}
|
|
|
|
uint16_t BGADevice::ReadRegister(uint16_t index)
|
|
{
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
if ( mmio_alloc.size == 0 )
|
|
{
|
|
outport16(VBE_DISPI_IOPORT_INDEX, index);
|
|
return inport16(VBE_DISPI_IOPORT_DATA);
|
|
}
|
|
#endif
|
|
assert(index < VBE_DISPI_NUM_REGISTERS);
|
|
volatile little_uint16_t* regs =
|
|
(volatile little_uint16_t*) (mmio_alloc.from + 0x500);
|
|
return regs[index];
|
|
}
|
|
|
|
uint16_t BGADevice::GetCapability(uint16_t index)
|
|
{
|
|
uint16_t was_enabled = ReadRegister(VBE_DISPI_INDEX_ENABLE);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, was_enabled | VBE_DISPI_GETCAPS);
|
|
uint16_t cap = ReadRegister(index);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, was_enabled);
|
|
return cap;
|
|
}
|
|
|
|
bool BGADevice::Initialize()
|
|
{
|
|
if ( (version = ReadRegister(VBE_DISPI_INDEX_ID)) < VBE_MIN_SUP_VERSION )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Hardware version 0x%X is too old, "
|
|
"minimum version supported is 0x%X\n",
|
|
devaddr, version, VBE_MIN_SUP_VERSION);
|
|
return false;
|
|
}
|
|
|
|
maxbpp = GetCapability(VBE_DISPI_INDEX_BPP);
|
|
maxxres = GetCapability(VBE_DISPI_INDEX_XRES);
|
|
maxyres = GetCapability(VBE_DISPI_INDEX_YRES);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BGADevice::SetVideoMode(uint16_t width, uint16_t height, uint16_t depth, bool keep)
|
|
{
|
|
bool uselinear = true;
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
|
|
WriteRegister(VBE_DISPI_INDEX_XRES, width);
|
|
WriteRegister(VBE_DISPI_INDEX_YRES, height);
|
|
WriteRegister(VBE_DISPI_INDEX_BPP, depth);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED |
|
|
(uselinear ? VBE_DISPI_LFB_ENABLED : 0) |
|
|
(keep ? VBE_DISPI_NOCLEARMEM : 0));
|
|
// TODO: How do we verify the video mode was *actually* set?
|
|
return true;
|
|
}
|
|
|
|
// TODO: Need a better method of detecting available/desired resolutions.
|
|
bool BGADevice::SupportsResolution(uint16_t width, uint16_t height, uint16_t depth)
|
|
{
|
|
if ( !width || !height || !depth )
|
|
return false;
|
|
if ( maxxres < width || maxyres < height || maxbpp < depth )
|
|
return false;
|
|
// TODO: Is this actually a restriction?
|
|
if ( width % 8U )
|
|
return false;
|
|
// TODO: Can we determine this more closely in advance? Perhaps if the
|
|
// framebuffer we will be using is larger than video memory?
|
|
return true;
|
|
}
|
|
|
|
struct dispmsg_crtc_mode BGADevice::GetCurrentMode(uint64_t connector) const
|
|
{
|
|
if ( connector != 0 )
|
|
{
|
|
errno = EINVAL;
|
|
struct dispmsg_crtc_mode mode;
|
|
memset(&mode, 0, sizeof(mode));
|
|
return mode;
|
|
}
|
|
|
|
return current_mode;
|
|
}
|
|
|
|
bool BGADevice::SwitchMode(uint64_t connector, struct dispmsg_crtc_mode mode)
|
|
{
|
|
if ( !Supports(connector, mode) )
|
|
return false;
|
|
|
|
if ( connector != 0 )
|
|
return errno = EINVAL, false;
|
|
|
|
size_t new_framesize = (size_t) mode.view_xres *
|
|
(size_t) mode.view_yres *
|
|
((size_t) mode.fb_format + 7) / 8UL;
|
|
// TODO: Use a better error code than ENOSPC?
|
|
if ( fb_alloc.size < new_framesize )
|
|
return errno = ENOSPC, false;
|
|
|
|
if ( !SetVideoMode(mode.view_xres, mode.view_yres, mode.fb_format, false) )
|
|
return false;
|
|
|
|
current_mode = mode;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BGADevice::Supports(uint64_t connector, struct dispmsg_crtc_mode mode) const
|
|
{
|
|
if ( connector != 0 )
|
|
return errno = EINVAL, false;
|
|
|
|
if ( mode.control & DISPMSG_CONTROL_VGA )
|
|
return errno = EINVAL, false;
|
|
if ( !(mode.control & DISPMSG_CONTROL_VALID) )
|
|
return errno = EINVAL, false;
|
|
|
|
if ( UINT16_MAX < mode.view_xres )
|
|
return errno = EINVAL, false;
|
|
if ( UINT16_MAX < mode.view_yres )
|
|
return errno = EINVAL, false;
|
|
// TODO: This is wrong, list the right values as above.
|
|
if ( mode.fb_format != VBE_DISPI_BPP_4 &&
|
|
mode.fb_format != VBE_DISPI_BPP_8 &&
|
|
mode.fb_format != VBE_DISPI_BPP_15 &&
|
|
mode.fb_format != VBE_DISPI_BPP_16 &&
|
|
mode.fb_format != VBE_DISPI_BPP_24 &&
|
|
mode.fb_format != VBE_DISPI_BPP_32 )
|
|
return errno = EINVAL, false;
|
|
|
|
// TODO: This is disabled because its support needs to be verified, see the
|
|
// framebuffer size calculation above?
|
|
if ( mode.fb_format != VBE_DISPI_BPP_24 &&
|
|
mode.fb_format != VBE_DISPI_BPP_32 )
|
|
return errno = ENOSYS, false;
|
|
|
|
return ((BGADevice*) this)->SupportsResolution(mode.view_xres, mode.view_yres, mode.fb_format);
|
|
}
|
|
|
|
struct dispmsg_crtc_mode* BGADevice::GetModes(uint64_t connector, size_t* retnum) const
|
|
{
|
|
if ( connector != 0 )
|
|
return errno = EINVAL, (struct dispmsg_crtc_mode*) NULL;
|
|
|
|
if ( !modes && !DetectModes() )
|
|
return NULL;
|
|
struct dispmsg_crtc_mode* result = new struct dispmsg_crtc_mode[num_modes];
|
|
if ( !result )
|
|
return NULL;
|
|
for ( size_t i = 0; i < num_modes; i++ )
|
|
result[i] = modes[i];
|
|
*retnum = num_modes;
|
|
return result;
|
|
}
|
|
|
|
off_t BGADevice::FrameSize() const
|
|
{
|
|
return (off_t) fb_alloc.size;
|
|
}
|
|
|
|
ssize_t BGADevice::WriteAt(ioctx_t* ctx, off_t off, const void* buf, size_t count)
|
|
{
|
|
uint8_t* frame = (uint8_t*) fb_alloc.from;
|
|
if ( (off_t) fb_alloc.size <= off )
|
|
return 0;
|
|
if ( fb_alloc.size < off + count )
|
|
count = fb_alloc.size - off;
|
|
if ( !ctx->copy_from_src(frame + off, buf, count) )
|
|
return -1;
|
|
return count;
|
|
}
|
|
|
|
ssize_t BGADevice::ReadAt(ioctx_t* ctx, off_t off, void* buf, size_t count)
|
|
{
|
|
const uint8_t* frame = (const uint8_t*) fb_alloc.from;
|
|
if ( (off_t) fb_alloc.size <= off )
|
|
return 0;
|
|
if ( fb_alloc.size < off + count )
|
|
count = fb_alloc.size - off;
|
|
if ( !ctx->copy_to_dest(buf, frame + off, count) )
|
|
return -1;
|
|
return count;
|
|
}
|
|
|
|
bool BGADevice::DetectModes() const
|
|
{
|
|
num_modes = 0;
|
|
unsigned bpp = VBE_DISPI_BPP_32;
|
|
for ( unsigned w = 0; w < maxxres; w += 4U )
|
|
{
|
|
for ( unsigned h = 0; h < maxyres; h += 4UL )
|
|
{
|
|
if ( !IsStandardResolution(w, h, bpp) )
|
|
continue;
|
|
if ( !((BGADevice*) this)->SupportsResolution(w, h, bpp) )
|
|
continue;
|
|
num_modes++;
|
|
}
|
|
}
|
|
num_modes++;
|
|
modes = new struct dispmsg_crtc_mode[num_modes];
|
|
if ( !modes )
|
|
return false;
|
|
memset(modes, 0, sizeof(char*) * num_modes);
|
|
size_t current_mode_id = 0;
|
|
for ( unsigned w = 0; w < maxxres; w += 4U )
|
|
{
|
|
for ( unsigned h = 0; h < maxyres; h += 4UL )
|
|
{
|
|
if ( !IsStandardResolution(w, h, bpp) )
|
|
continue;
|
|
if ( !((BGADevice*) this)->SupportsResolution(w, h, bpp) )
|
|
continue;
|
|
struct dispmsg_crtc_mode mode;
|
|
memset(&mode, 0, sizeof(mode));
|
|
mode.view_xres = w;
|
|
mode.view_yres = h;
|
|
mode.fb_format = bpp;
|
|
mode.control = DISPMSG_CONTROL_VALID;
|
|
modes[current_mode_id++] = mode;
|
|
}
|
|
}
|
|
|
|
struct dispmsg_crtc_mode any_mode;
|
|
memset(&any_mode, 0, sizeof(any_mode));
|
|
any_mode.view_xres = 0;
|
|
any_mode.view_yres = 0;
|
|
any_mode.fb_format = 0;
|
|
any_mode.control = DISPMSG_CONTROL_OTHER_RESOLUTIONS;
|
|
modes[num_modes-1] = any_mode;
|
|
|
|
return true;
|
|
}
|
|
|
|
TextBuffer* BGADevice::CreateTextBuffer(uint64_t connector)
|
|
{
|
|
if ( connector != 0 )
|
|
return errno = EINVAL, (TextBuffer*) NULL;
|
|
|
|
uint8_t* lfb = (uint8_t*) fb_alloc.from;
|
|
uint32_t lfbformat = current_mode.fb_format;
|
|
size_t scansize = current_mode.view_xres * current_mode.fb_format / 8UL;
|
|
return CreateLFBTextBuffer(lfb, lfbformat, current_mode.view_xres, current_mode.view_yres, scansize);
|
|
}
|
|
|
|
static void TryInitializeDevice(uint32_t devaddr)
|
|
{
|
|
pciid_t id = PCI::GetDeviceId(devaddr);
|
|
|
|
bool is_qemu_bga = id.vendorid == 0x1234 && id.deviceid == 0x1111;
|
|
bool is_vbox_bga = id.vendorid == 0x80EE && id.deviceid == 0xBEEF;
|
|
|
|
(void) is_qemu_bga;
|
|
(void) is_vbox_bga;
|
|
|
|
pcibar_t fb_bar;
|
|
pcibar_t mmio_bar;
|
|
addralloc_t fb_alloc;
|
|
addralloc_t mmio_alloc;
|
|
bool has_mmio = false;
|
|
bool fallback_ioport = false;
|
|
|
|
fb_bar = PCI::GetBAR(devaddr, 0);
|
|
if ( !MapPCIBAR(&fb_alloc, fb_bar, MAP_PCI_BAR_WRITE_COMBINE) )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Framebuffer could not be mapped: %s\n",
|
|
devaddr, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if ( is_qemu_bga )
|
|
mmio_bar = PCI::GetBAR(devaddr, 2);
|
|
|
|
if ( is_qemu_bga && mmio_bar.is_mmio() && 4096 <= mmio_bar.size() )
|
|
{
|
|
has_mmio = true;
|
|
|
|
if ( !MapPCIBAR(&mmio_alloc, mmio_bar, MAP_PCI_BAR_WRITE_COMBINE) )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Memory-mapped registers could not be mapped: %s\n",
|
|
devaddr, strerror(errno));
|
|
UnmapPCIBar(&fb_alloc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
// This device doesn't come with its own set of registers, so we have to
|
|
// assume that the global BGA io port registers are available and that
|
|
// only a single such device is present (since two concurrent devices)
|
|
// could not exist then. This is only available on x86-family systems.
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
fallback_ioport = true;
|
|
#endif
|
|
}
|
|
|
|
if ( !has_mmio && !fallback_ioport )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Device provides no registers.\n",
|
|
devaddr);
|
|
UnmapPCIBar(&fb_alloc);
|
|
return;
|
|
}
|
|
|
|
if ( fallback_ioport )
|
|
memset(&mmio_alloc, 0, sizeof(mmio_alloc));
|
|
|
|
BGADevice* bga_device = new BGADevice(devaddr, fb_alloc, mmio_alloc);
|
|
if ( !bga_device )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Unable to allocate driver structure: %s\n",
|
|
devaddr, strerror(errno));
|
|
UnmapPCIBar(&mmio_alloc);
|
|
UnmapPCIBar(&fb_alloc);
|
|
return;
|
|
}
|
|
|
|
if ( !bga_device->Initialize() )
|
|
{
|
|
delete bga_device;
|
|
return;
|
|
}
|
|
|
|
if ( !Video::RegisterDevice("bga", bga_device) )
|
|
{
|
|
Log::PrintF("[BGA device @ PCI:0x%X] Unable to register device: %s\n",
|
|
devaddr, strerror(errno));
|
|
delete bga_device;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
pcifind_t bga_pcifind;
|
|
memset(&bga_pcifind, 255, sizeof(bga_pcifind));
|
|
bga_pcifind.vendorid = 0x1234;
|
|
bga_pcifind.deviceid = 0x1111;
|
|
|
|
uint32_t devaddr = 0;
|
|
while ( (devaddr = PCI::SearchForDevices(bga_pcifind, devaddr)) )
|
|
TryInitializeDevice(devaddr);
|
|
|
|
memset(&bga_pcifind, 255, sizeof(bga_pcifind));
|
|
bga_pcifind.vendorid = 0x80EE;
|
|
bga_pcifind.deviceid = 0xBEEF;
|
|
|
|
devaddr = 0;
|
|
while ( (devaddr = PCI::SearchForDevices(bga_pcifind, devaddr)) )
|
|
TryInitializeDevice(devaddr);
|
|
}
|
|
|
|
} // namespace BGA
|
|
} // namespace Sortix
|