sortix-mirror/kernel/gpu/bga/bga.cpp
Jonas 'Sortie' Termansen 2b72262b4f Relicense Sortix to the ISC license.
I hereby relicense all my work on Sortix under the ISC license as below.

All Sortix contributions by other people are already under this license,
are not substantial enough to be copyrightable, or have been removed.

All imported code from other projects is compatible with this license.

All GPL licensed code from other projects had previously been removed.

Copyright 2011-2016 Jonas 'Sortie' Termansen and contributors.

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.
2016-03-05 22:21:50 +01:00

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(&current_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_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