mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
578 lines
16 KiB
C++
578 lines
16 KiB
C++
/*******************************************************************************
|
|
|
|
Copyright(C) Jonas 'Sortie' Termansen 2012.
|
|
|
|
This file is part of Sortix.
|
|
|
|
Sortix is free software: you can redistribute it and/or modify it under the
|
|
terms of the GNU General Public License as published by the Free Software
|
|
Foundation, either version 3 of the License, or (at your option) any later
|
|
version.
|
|
|
|
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
Sortix. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
bga.cpp
|
|
Driver for the Bochs VBE Extensions.
|
|
|
|
*******************************************************************************/
|
|
|
|
#include <assert.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/kernel.h>
|
|
#include <sortix/kernel/memorymanagement.h>
|
|
#include <sortix/kernel/pci.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 bool TEST_RES_BY_TRYING = false;
|
|
|
|
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_IOPORT_INDEX = 0x01CE;
|
|
const uint16_t VBE_DISPI_IOPORT_DATA = 0x01CF;
|
|
|
|
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;
|
|
|
|
const size_t VBE_BANK_SIZE = 64UL * 1024UL;
|
|
volatile uint8_t* const VBE_VIDEO_MEM = (volatile uint8_t*) 0xA0000;
|
|
|
|
addr_t DetectBGAFramebuffer()
|
|
{
|
|
uint32_t devaddr;
|
|
pcifind_t pcifind;
|
|
|
|
// Search for the bochs BGA device and compatible.
|
|
memset(&pcifind, 255, sizeof(pcifind));
|
|
pcifind.vendorid = 0x1234;
|
|
pcifind.deviceid = 0x1111;
|
|
if ( (devaddr = PCI::SearchForDevice(pcifind)) )
|
|
return PCI::ParseDevBar0(devaddr);
|
|
|
|
// Search for a generic VGA compatible device.
|
|
memset(&pcifind, 255, sizeof(pcifind));
|
|
pcifind.classid = 0x03;
|
|
pcifind.subclassid = 0x00;
|
|
pcifind.progif = 0x00;
|
|
if ( (devaddr = PCI::SearchForDevice(pcifind)) )
|
|
return PCI::ParseDevBar0(devaddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16_t version;
|
|
uint16_t maxbpp;
|
|
uint16_t maxxres;
|
|
uint16_t maxyres;
|
|
|
|
uint16_t curbpp;
|
|
uint16_t curxres;
|
|
uint16_t curyres;
|
|
uint16_t curbank;
|
|
|
|
addr_t bgaframebuffer;
|
|
|
|
void WriteRegister(uint16_t index, uint16_t value)
|
|
{
|
|
CPU::OutPortW(VBE_DISPI_IOPORT_INDEX, index);
|
|
CPU::OutPortW(VBE_DISPI_IOPORT_DATA, value);
|
|
}
|
|
|
|
uint16_t ReadRegister(uint16_t index)
|
|
{
|
|
CPU::OutPortW(VBE_DISPI_IOPORT_INDEX, index);
|
|
return CPU::InPortW(VBE_DISPI_IOPORT_DATA);
|
|
}
|
|
|
|
uint16_t GetCapability(uint16_t index)
|
|
{
|
|
uint16_t wasenabled = ReadRegister(VBE_DISPI_INDEX_ENABLE);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, wasenabled | VBE_DISPI_GETCAPS);
|
|
uint16_t cap = ReadRegister(index);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, wasenabled);
|
|
return cap;
|
|
}
|
|
|
|
bool 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));
|
|
curbpp = depth;
|
|
curxres = width;
|
|
curyres = height;
|
|
return true;
|
|
}
|
|
|
|
bool IsStandardResolution(uint16_t width, uint16_t height, uint16_t depth)
|
|
{
|
|
if ( depth != VBE_DISPI_BPP_32 ) { return false; }
|
|
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;
|
|
}
|
|
|
|
// TODO: Need a better method of detecting available/desired resolutions.
|
|
bool 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;
|
|
if ( width % 8U ) { return false; }
|
|
uint16_t wasenabled = ReadRegister(VBE_DISPI_INDEX_ENABLE);
|
|
if ( width == curxres && height == curyres && depth == curbpp) return true;
|
|
if ( !TEST_RES_BY_TRYING ) { return true; }
|
|
SetVideoMode(width, height, depth, true);
|
|
uint16_t newxres = ReadRegister(VBE_DISPI_INDEX_XRES);
|
|
uint16_t newyres = ReadRegister(VBE_DISPI_INDEX_XRES);
|
|
uint16_t newbpp = ReadRegister(VBE_DISPI_INDEX_BPP);
|
|
bool result = newxres != curxres || newyres != curyres || newbpp != curbpp;
|
|
SetVideoMode(curxres, curyres, curbpp, true);
|
|
WriteRegister(VBE_DISPI_INDEX_ENABLE, wasenabled);
|
|
return result;
|
|
}
|
|
|
|
class BGADriver : public VideoDriver
|
|
{
|
|
public:
|
|
BGADriver();
|
|
virtual ~BGADriver();
|
|
|
|
public:
|
|
virtual bool StartUp();
|
|
virtual bool ShutDown();
|
|
virtual char* GetCurrentMode() const;
|
|
virtual bool SwitchMode(const char* mode);
|
|
virtual bool Supports(const char* mode) const;
|
|
virtual char** GetModes(size_t* nummodes) const;
|
|
virtual off_t FrameSize() const;
|
|
virtual ssize_t WriteAt(off_t off, const void* buf, size_t count);
|
|
virtual ssize_t ReadAt(off_t off, void* buf, size_t count);
|
|
virtual TextBuffer* CreateTextBuffer();
|
|
|
|
private:
|
|
bool MapVideoMemory(size_t size);
|
|
bool MapVideoMemoryRange(addr_t mapat, size_t from, size_t to);
|
|
bool IncreaseVirtual(size_t new_size);
|
|
bool DetectModes() const;
|
|
|
|
private:
|
|
mutable size_t nummodes;
|
|
mutable char** modes;
|
|
char* curmode;
|
|
size_t lfbmapped;
|
|
size_t framesize;
|
|
addralloc_t addr_allocation;
|
|
|
|
};
|
|
|
|
BGADriver::BGADriver()
|
|
{
|
|
nummodes = 0;
|
|
modes = NULL;
|
|
curmode = NULL;
|
|
lfbmapped = 0;
|
|
framesize = 0;
|
|
memset(&addr_allocation, 0, sizeof(addr_allocation));
|
|
}
|
|
|
|
BGADriver::~BGADriver()
|
|
{
|
|
MapVideoMemory(0);
|
|
FreeKernelAddress(&addr_allocation);
|
|
for ( size_t i = 0; i < nummodes; i++ )
|
|
{
|
|
delete[] modes[i];
|
|
}
|
|
delete[] modes;
|
|
delete[] curmode;
|
|
}
|
|
|
|
bool BGADriver::MapVideoMemoryRange(addr_t mapat, size_t from, size_t to)
|
|
{
|
|
addr_t phys = bgaframebuffer;
|
|
const addr_t mtype = Memory::PAT_WC;
|
|
for ( size_t i = from; i < to; i += Page::Size() )
|
|
if ( !Memory::MapPAT(phys+i, mapat+i, PROT_KWRITE | PROT_KREAD, mtype) )
|
|
{
|
|
Log::PrintF("Error: Insufficient memory to map BGA framebuffer "
|
|
"onto kernel address space: needed 0x%zx bytes but "
|
|
"only 0x%zx was available at this point.\n", to, i);
|
|
for ( size_t n = from; n < i; n += Page::Size() )
|
|
Memory::Unmap(mapat + n);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BGADriver::IncreaseVirtual(size_t new_size)
|
|
{
|
|
new_size = Page::AlignUp(new_size);
|
|
assert(addr_allocation.size < new_size);
|
|
|
|
addralloc_t new_addralloc;
|
|
if ( !AllocateKernelAddress(&new_addralloc, new_size) )
|
|
{
|
|
Log::PrintF("Error: Insufficient virtual address space for BGA "
|
|
"frame of size 0x%zx bytes, only 0x%zx was available.\n",
|
|
new_size, addr_allocation.size);
|
|
return false;
|
|
}
|
|
|
|
addr_t old_mapat = addr_allocation.from;
|
|
addr_t new_mapat = new_addralloc.from;
|
|
|
|
if ( !MapVideoMemoryRange(new_mapat, 0, new_size) )
|
|
{
|
|
FreeKernelAddress(&addr_allocation);
|
|
return false;
|
|
}
|
|
|
|
for ( size_t i = 0; i < lfbmapped; i += Page::Size() )
|
|
Memory::Unmap(old_mapat + i);
|
|
|
|
FreeKernelAddress(&addr_allocation);
|
|
|
|
lfbmapped = new_size;
|
|
addr_allocation = new_addralloc;
|
|
|
|
Memory::Flush();
|
|
return true;
|
|
}
|
|
|
|
bool BGADriver::MapVideoMemory(size_t size)
|
|
{
|
|
size = Page::AlignUp(size);
|
|
|
|
if ( size == lfbmapped )
|
|
return true;
|
|
|
|
if ( addr_allocation.size < size )
|
|
return IncreaseVirtual(size);
|
|
|
|
addr_t mapat = addr_allocation.from;
|
|
for ( size_t i = size; i < lfbmapped; i+= Page::Size() )
|
|
Memory::Unmap(mapat + i);
|
|
|
|
lfbmapped = size;
|
|
Memory::Flush();
|
|
|
|
if ( !size )
|
|
FreeKernelAddress(&addr_allocation);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BGADriver::StartUp()
|
|
{
|
|
if ( !modes && !DetectModes() ) { return false; }
|
|
return true;
|
|
}
|
|
|
|
bool BGADriver::ShutDown()
|
|
{
|
|
MapVideoMemory(0);
|
|
if ( curmode )
|
|
{
|
|
delete[] curmode; curmode = NULL;
|
|
errno = ENOSYS;
|
|
return false; // TODO: Return to VGA Text Mode.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
char* BGADriver::GetCurrentMode() const
|
|
{
|
|
if ( !curmode ) { errno = EINVAL; return NULL; }
|
|
return String::Clone(curmode);
|
|
}
|
|
|
|
bool BGADriver::SwitchMode(const char* mode)
|
|
{
|
|
bool result = false;
|
|
char* modeclone = String::Clone(mode);
|
|
if ( !modeclone )
|
|
return NULL;
|
|
char* xstr = NULL;
|
|
char* ystr = NULL;
|
|
char* bppstr = NULL;
|
|
if ( !ReadParamString(mode, "width", &xstr, "height", &ystr,
|
|
"bpp", &bppstr, "STOP") )
|
|
{
|
|
delete[] modeclone;
|
|
return false;
|
|
}
|
|
uint16_t xres = xstr ? atoi(xstr) : 0;
|
|
uint16_t yres = ystr ? atoi(ystr) : 0;
|
|
uint16_t bpp = bppstr ? atoi(bppstr) : 32;
|
|
size_t newframesize = (size_t) xres * (size_t) yres * (size_t) bpp/8UL;
|
|
|
|
// If the current resolution uses more memory than the new one, keep it
|
|
// around in case setting the video mode failed.
|
|
if ( MapVideoMemory(newframesize < lfbmapped ? lfbmapped : newframesize) &&
|
|
SetVideoMode(xres, yres, bpp, false) )
|
|
{
|
|
delete[] curmode;
|
|
curmode = modeclone;
|
|
modeclone = NULL;
|
|
// We can now truncate the amount of memory to what we really need.
|
|
MapVideoMemory(framesize = newframesize);
|
|
result = true;
|
|
}
|
|
|
|
delete[] xstr;
|
|
delete[] ystr;
|
|
delete[] bppstr;
|
|
delete[] modeclone;
|
|
return result;
|
|
}
|
|
|
|
bool BGADriver::Supports(const char* mode) const
|
|
{
|
|
char* xstr = NULL;
|
|
char* ystr = NULL;
|
|
char* bppstr = NULL;
|
|
if ( !ReadParamString(mode, "width", &xstr, "height", &ystr,
|
|
"bpp", &bppstr, NULL, NULL) ) { return false; }
|
|
uint16_t xres = xstr ? atoi(xstr) : 0;
|
|
uint16_t yres = ystr ? atoi(ystr) : 0;
|
|
uint16_t bpp = bppstr ? atoi(bppstr) : 0;
|
|
bool result = SupportsResolution(xres, yres, bpp);
|
|
delete[] xstr;
|
|
delete[] ystr;
|
|
delete[] bppstr;
|
|
return result;
|
|
}
|
|
|
|
char** BGADriver::GetModes(size_t* retnum) const
|
|
{
|
|
if ( !modes && !DetectModes() ) { return NULL; }
|
|
char** result = new char*[nummodes];
|
|
if ( !result ) { return NULL; }
|
|
for ( size_t i = 0; i < nummodes; i++ )
|
|
{
|
|
result[i] = String::Clone(modes[i]);
|
|
if ( !result[i] )
|
|
{
|
|
for ( size_t n = 0; n < i; i++ ) { delete[] result[n]; }
|
|
delete[] result;
|
|
return NULL;
|
|
}
|
|
}
|
|
*retnum = nummodes;
|
|
return result;
|
|
}
|
|
|
|
off_t BGADriver::FrameSize() const
|
|
{
|
|
return curxres * curyres * (curbpp / 8UL);
|
|
}
|
|
|
|
ssize_t BGADriver::WriteAt(off_t off, const void* buf, size_t count)
|
|
{
|
|
uint8_t* frame = (uint8_t*) addr_allocation.from;
|
|
if ( (off_t) framesize <= off )
|
|
return 0;
|
|
if ( framesize < off + count )
|
|
count = framesize - off;
|
|
memcpy(frame + off, buf, count);
|
|
return count;
|
|
}
|
|
|
|
ssize_t BGADriver::ReadAt(off_t off, void* buf, size_t count)
|
|
{
|
|
const uint8_t* frame = (const uint8_t*) addr_allocation.from;
|
|
if ( (off_t) framesize <= off )
|
|
return 0;
|
|
if ( framesize < off + count )
|
|
count = framesize - off;
|
|
memcpy(buf, frame + off, count);
|
|
return count;
|
|
}
|
|
|
|
bool BGADriver::DetectModes() const
|
|
{
|
|
nummodes = 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 ( !SupportsResolution(w, h, bpp) ) { continue; }
|
|
nummodes++;
|
|
}
|
|
}
|
|
modes = new char*[nummodes];
|
|
if ( !modes ) { return false; }
|
|
memset(modes, 0, sizeof(char*) * nummodes);
|
|
size_t curmodeid = 0;
|
|
for ( unsigned w = 0; w < maxxres; w += 4U )
|
|
{
|
|
for ( unsigned h = 0; h < maxyres; h += 4UL )
|
|
{
|
|
if ( !IsStandardResolution(w, h, bpp) ) { continue; }
|
|
if ( !SupportsResolution(w, h, bpp) ) { continue; }
|
|
char bppstr[64];
|
|
char xresstr[64];
|
|
char yresstr[64];
|
|
snprintf(bppstr, 64, "%u", bpp);
|
|
snprintf(xresstr, 64, "%u", w);
|
|
snprintf(yresstr, 64, "%u", h);
|
|
char* modestr = String::Combine(6, "width=", xresstr, ",height=",
|
|
yresstr, ",bpp=", bppstr);
|
|
if ( !modestr ) { return false; }
|
|
modes[curmodeid++] = modestr;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TextBuffer* BGADriver::CreateTextBuffer()
|
|
{
|
|
uint8_t* lfb = (uint8_t*) addr_allocation.from;
|
|
uint32_t lfbformat = curbpp;
|
|
size_t scansize = curxres * curbpp / 8UL;
|
|
return CreateLFBTextBuffer(lfb, lfbformat, curxres, curyres, scansize);
|
|
}
|
|
|
|
static uint16_t ProbeBGAVersion()
|
|
{
|
|
// First see if the register is in the legal range.
|
|
uint16_t ver = ReadRegister(VBE_DISPI_INDEX_ID);
|
|
if ( ver < VBE_MIN_POS_VERSION )
|
|
return 0;
|
|
if ( ver > VBE_MAX_POS_VERSION )
|
|
return 0;
|
|
|
|
// The bootloader or BIOS may have set the current version to less than what
|
|
// really is supported. If we a version number to the register, we can read
|
|
// it back only if it is supported.
|
|
|
|
// If the register accepts an invalid version number, don't trust it and we
|
|
// may be in danger if an unrelated type of register is using this IO port.
|
|
// Since that is unlikely, just assume we got a real BGA device.
|
|
WriteRegister(VBE_DISPI_INDEX_ID, 0xFFFF);
|
|
if ( ReadRegister(VBE_DISPI_INDEX_ID) == 0xFFFF )
|
|
{
|
|
WriteRegister(VBE_DISPI_INDEX_ID, ver);
|
|
Log::PrintF("Warning: Found what appears to be BGA hardware, but it "
|
|
"behaves differently when attempting to scan what version "
|
|
"it conforms to. ");
|
|
if ( ver < VBE_MIN_SUP_VERSION )
|
|
{
|
|
Log::PrintF("The hardware is by default set to an old unsupported "
|
|
"version, this driver will not use it.\n");
|
|
return 0;
|
|
}
|
|
Log::PrintF("The driver will use this hardware (even though it may not "
|
|
"be BGA hardware) and bad things may happen.\n");
|
|
return ver;
|
|
}
|
|
|
|
// Attempt to query all possible version ids.
|
|
for ( uint16_t i = ver; i < VBE_MAX_POS_VERSION; i++ )
|
|
{
|
|
WriteRegister(VBE_DISPI_INDEX_ID, i);
|
|
if ( ReadRegister(VBE_DISPI_INDEX_ID) == i )
|
|
ver = i;
|
|
}
|
|
|
|
return ver;
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
if ( !(version = ProbeBGAVersion()) )
|
|
return;
|
|
|
|
curbpp = 0;
|
|
curxres = 0;
|
|
curyres = 0;
|
|
curbank = 0xFFFF;
|
|
|
|
maxbpp = GetCapability(VBE_DISPI_INDEX_BPP);
|
|
maxxres = GetCapability(VBE_DISPI_INDEX_XRES);
|
|
maxyres = GetCapability(VBE_DISPI_INDEX_YRES);
|
|
|
|
if ( !(bgaframebuffer = DetectBGAFramebuffer()) )
|
|
{
|
|
Log::PrintF("BGA support detected but no PCI device could be found "
|
|
"determines the location of the framebuffer. Rather than "
|
|
"guessing it is at 0xE0000000, this driver shuts down to "
|
|
"avoid corrupting possible memory there.\n");
|
|
return;
|
|
}
|
|
|
|
BGADriver* bgadriver = new BGADriver;
|
|
if ( !bgadriver )
|
|
{
|
|
Log::PrintF("Unable to allocate BGA driver, but hardware present\n");
|
|
return;
|
|
}
|
|
|
|
if ( !Video::RegisterDriver("bga", bgadriver) )
|
|
{
|
|
Log::PrintF("Unable to register BGA driver, but hardware present\n");
|
|
delete bgadriver;
|
|
return;
|
|
}
|
|
}
|
|
|
|
} // namespace BGA
|
|
} // namespace Sortix
|