From 8dc5955f5e05930432ae3b2b1e4395ae04f434af Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Tue, 22 May 2012 21:45:15 +0200 Subject: [PATCH] Wrote a driver for the Bochs VBE Extensions (BGA). --- sortix/Makefile | 1 + sortix/bga.cpp | 529 ++++++++++++++++++++++++++++++++++++++++++++++ sortix/bga.h | 36 ++++ sortix/kernel.cpp | 4 + 4 files changed, 570 insertions(+) create mode 100644 sortix/bga.cpp create mode 100644 sortix/bga.h diff --git a/sortix/Makefile b/sortix/Makefile index 794b97f4..eec7dcb0 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -119,6 +119,7 @@ descriptors.o \ device.o \ refcount.o \ video.o \ +bga.o \ vga.o \ kernelinfo.o \ elf.o \ diff --git a/sortix/bga.cpp b/sortix/bga.cpp new file mode 100644 index 00000000..dac62a3d --- /dev/null +++ b/sortix/bga.cpp @@ -0,0 +1,529 @@ +/******************************************************************************* + + 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 . + + bga.cpp + Driver for the Bochs VBE Extensions. + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "x86-family/memorymanagement.h" +#include "lfbtextbuffer.h" +#include "cpu.h" +#include "bga.h" + +using namespace Maxsi; + +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. + Maxsi::Memory::Set(&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. + Maxsi::Memory::Set(&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 DetectModes() const; + +private: + mutable size_t nummodes; + mutable char** modes; + char* curmode; + size_t lfbmapped; + size_t framesize; + +}; + +BGADriver::BGADriver() +{ + nummodes = 0; + modes = NULL; + curmode = NULL; + lfbmapped = 0; + framesize = 0; +} + +BGADriver::~BGADriver() +{ + MapVideoMemory(0); + for ( size_t i = 0; i < nummodes; i++ ) + { + delete[] modes[i]; + } + delete[] modes; + delete[] curmode; +} + +bool BGADriver::MapVideoMemory(size_t size) +{ + size = Page::AlignUp(size); + addr_t phys = bgaframebuffer; + addr_t mapat = Memory::GetVideoMemory(); + + if ( size == lfbmapped ) + return true; + + if ( size < lfbmapped ) + { + for ( size_t i = size; i < size; i += Page::Size() ) + Memory::Unmap(phys + i); + Memory::Flush(); + lfbmapped = size; + return true; + } + + size_t maxsize = Memory::GetMaxVideoMemorySize(); + if ( maxsize < size ) + { + Log::PrintF("Error: Insufficient virtual address space for BGA frame " + "of size 0x%zx bytes, only 0x%zx was available.\n", size, + maxsize); + return false; + } + + const addr_t mtype = Memory::PAT_WC; + for ( size_t i = lfbmapped; i < size; 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", size, i); + MapVideoMemory(lfbmapped); // Unmap what we added. + return false; + } + Memory::Flush(); + lfbmapped = size; + return true; +} + +bool BGADriver::StartUp() +{ + if ( !modes && !DetectModes() ) { return false; } + return true; +} + +bool BGADriver::ShutDown() +{ + MapVideoMemory(0); + if ( curmode ) + { + delete[] curmode; curmode = NULL; + Error::Set(ENOSYS); + return false; // TODO: Return to VGA Text Mode. + } + return true; +} + +char* BGADriver::GetCurrentMode() const +{ + if ( !curmode ) { Error::Set(EINVAL); return NULL; } + return String::Clone(curmode); +} + +bool BGADriver::SwitchMode(const char* mode) +{ + bool result = false; + char* modeclone = String::Clone(mode); + char* xstr = NULL; + char* ystr = NULL; + char* bppstr = NULL; + if ( !ReadParamString(mode, "width", &xstr, "height", &ystr, + "bpp", &bppstr, "STOP") ) { return false; } + uint16_t xres = xstr ? Maxsi::String::ToInt(xstr) : 0; + uint16_t yres = ystr ? Maxsi::String::ToInt(ystr) : 0; + uint16_t bpp = bppstr ? Maxsi::String::ToInt(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 ? Maxsi::String::ToInt(xstr) : 0; + uint16_t yres = ystr ? Maxsi::String::ToInt(ystr) : 0; + uint16_t bpp = bppstr ? Maxsi::String::ToInt(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*) Memory::GetVideoMemory(); + if ( (off_t) framesize <= off ) + return 0; + if ( framesize < off + count ) + count = off - framesize; + Maxsi::Memory::Copy(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*) Memory::GetVideoMemory(); + if ( (off_t) framesize <= off ) + return 0; + if ( framesize < off + count ) + count = off - framesize; + Maxsi::Memory::Copy(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; } + Maxsi::Memory::Set(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]; + bppstr[String::ConvertUInt32(bpp, bppstr)] = 0; + xresstr[String::ConvertUInt32(w, xresstr)] = 0; + yresstr[String::ConvertUInt32(h, yresstr)] = 0; + 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*) Memory::GetVideoMemory(); + 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 diff --git a/sortix/bga.h b/sortix/bga.h new file mode 100644 index 00000000..36cce912 --- /dev/null +++ b/sortix/bga.h @@ -0,0 +1,36 @@ +/******************************************************************************* + + 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 . + + bga.h + Driver for the Bochs VBE Extensions. + +*******************************************************************************/ + +#ifndef SORTIX_BGA_H +#define SORTIX_BGA_H + +namespace Sortix { +namespace BGA { + +void Init(); + +} // namespace BGA +} // namespace Sortix + +#endif diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index c04f90c5..41fac5e1 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -54,6 +54,7 @@ #include "elf.h" #include "initrd.h" #include "vga.h" +#include "bga.h" #include "sound.h" #include "io.h" #include "pipe.h" @@ -226,6 +227,9 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) // Initialize ATA devices. ATA::Init(); + // Initialize the BGA driver. + BGA::Init(); + // Alright, now the system's drivers are loaded and initialized. It is // time to load the initial user-space programs and start execution of // the actual operating system.