/******************************************************************************* 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 #include #include #include #include #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