diff --git a/sortix/Makefile b/sortix/Makefile index ebc7f505..a2f96ff3 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -117,6 +117,7 @@ vgatextbuffer.o \ descriptors.o \ device.o \ refcount.o \ +video.o \ vga.o \ kernelinfo.o \ elf.o \ diff --git a/sortix/include/sortix/kernel/video.h b/sortix/include/sortix/kernel/video.h new file mode 100644 index 00000000..1e4ee6fb --- /dev/null +++ b/sortix/include/sortix/kernel/video.h @@ -0,0 +1,68 @@ +/******************************************************************************* + + 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 . + + video.h + Framework for Sortix video drivers. + +*******************************************************************************/ + +#ifndef SORTIX_VIDEO_H +#define SORTIX_VIDEO_H + +namespace Sortix { + +class TextBuffer; +class TextBufferHandle; + +bool ReadParamString(const char* str, ...); + +class VideoDriver +{ +public: + virtual ~VideoDriver() { } + virtual bool StartUp() = 0; + virtual bool ShutDown() = 0; + virtual char* GetCurrentMode() const = 0; + virtual bool SwitchMode(const char* mode) = 0; + virtual bool Supports(const char* mode) const = 0; + virtual char** GetModes(size_t* nummodes) const = 0; + virtual off_t FrameSize() const = 0; + virtual ssize_t WriteAt(off_t off, const void* buf, size_t count) = 0; + virtual ssize_t ReadAt(off_t off, void* buf, size_t count) = 0; + virtual TextBuffer* CreateTextBuffer() = 0; + +}; + +namespace Video { + +void Init(TextBufferHandle* textbufhandle); +bool RegisterDriver(const char* name, VideoDriver* driver); +char* GetCurrentMode(); +bool Supports(const char* mode); +bool SwitchMode(const char* mode); +char** GetModes(size_t* modesnum); +off_t FrameSize(); +ssize_t WriteAt(off_t off, const void* buf, size_t count); +ssize_t ReadAt(off_t off, void* buf, size_t count); + +} // namespace Video + +} // namespace Sortix + +#endif diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index deeddc87..c04f90c5 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "kernelinfo.h" #include "x86-family/gdt.h" #include "time.h" @@ -216,6 +217,9 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo) // Set up the initial ram disk. InitRD::Init(initrd, initrdsize); + // Initialize the Video Driver framework. + Video::Init(&textbufhandle); + // Search for PCI devices and load their drivers. PCI::Init(); diff --git a/sortix/video.cpp b/sortix/video.cpp new file mode 100644 index 00000000..9b7e823d --- /dev/null +++ b/sortix/video.cpp @@ -0,0 +1,433 @@ +/******************************************************************************* + + 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 . + + video.cpp + Framework for Sortix video drivers. + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Maxsi; + +namespace Sortix { + +bool ReadParamString(const char* str, ...) +{ + if ( String::Seek(str, '\n') ) { Error::Set(EINVAL); } + const char* keyname; + va_list args; + while ( *str ) + { + size_t varlen = String::Reject(str, ","); + if ( !varlen ) { str++; continue; } + size_t namelen = String::Reject(str, "="); + if ( !namelen ) { Error::Set(EINVAL); goto cleanup; } + if ( !str[namelen] ) { Error::Set(EINVAL); goto cleanup; } + if ( varlen < namelen ) { Error::Set(EINVAL); goto cleanup; } + size_t valuelen = varlen - 1 /*=*/ - namelen; + char* name = String::Substring(str, 0, namelen); + if ( !name ) { goto cleanup; } + char* value = String::Substring(str, namelen+1, valuelen); + if ( !value ) { delete[] name; goto cleanup; } + va_start(args, str); + while ( (keyname = va_arg(args, const char*)) ) + { + char** nameptr = va_arg(args, char**); + if ( String::Compare(keyname, name) ) { continue; } + *nameptr = value; + break; + } + va_end(args); + if ( !keyname ) { delete[] value; } + delete[] name; + str += varlen; + str += String::Accept(str, ","); + } + return true; + +cleanup: + va_start(args, str); + while ( (keyname = va_arg(args, const char*)) ) + { + char** nameptr = va_arg(args, char**); + delete[] *nameptr; *nameptr = NULL; + } + va_end(args); + return false; +} + +namespace Video { + +const unsigned long DRIVER_GOT_MODES = (1UL<<0UL); + +struct DriverEntry +{ + char* name; + VideoDriver* driver; + unsigned long flags; + size_t id; +}; + +size_t numdrivers; +size_t driverslen; +DriverEntry* drivers; + +size_t nummodes; +size_t modeslen; +char** modes; + +char* currentmode; +size_t currentdrvid; +bool newdrivers; + +kthread_mutex_t videolock; +TextBufferHandle* textbufhandle; + +void Init(TextBufferHandle* thetextbufhandle) +{ + videolock = KTHREAD_MUTEX_INITIALIZER; + textbufhandle = thetextbufhandle; + numdrivers = driverslen = 0; + drivers = NULL; + nummodes = modeslen = 0; + modes = NULL; + currentdrvid = SIZE_MAX; + newdrivers = false; + currentmode = NULL; +} + +static DriverEntry* CurrentDriverEntry() +{ + if ( currentdrvid == SIZE_MAX ) { return NULL; } + return drivers + currentdrvid; +} + +bool RegisterDriver(const char* name, VideoDriver* driver) +{ + ScopedLock lock(&videolock); + if ( numdrivers == driverslen ) + { + size_t newdriverslen = driverslen ? 2 * driverslen : 8UL; + DriverEntry* newdrivers = new DriverEntry[newdriverslen]; + if ( !newdrivers ) { return false; } + Memory::Copy(newdrivers, drivers, sizeof(*drivers) * numdrivers); + delete[] drivers; drivers = newdrivers; + driverslen = driverslen; + } + + char* drivername = String::Clone(name); + if ( !drivername ) { return false; } + + size_t index = numdrivers++; + drivers[index].name = drivername; + drivers[index].driver = driver; + drivers[index].flags = 0; + drivers[index].id = index; + newdrivers = true; + return true; +} + +static bool ExpandModesArray(size_t needed) +{ + size_t modesneeded = nummodes + needed; + if ( modesneeded <= modeslen ) { return true; } + size_t newmodeslen = 2 * modeslen; + if ( newmodeslen < modesneeded ) { newmodeslen = modesneeded; } + char** newmodes = new char*[newmodeslen]; + if ( !newmodes ) { return false; } + Memory::Copy(newmodes, modes, sizeof(char*) * nummodes); + delete[] modes; modes = newmodes; + modeslen = newmodeslen; + return true; +} + +static void UpdateModes() +{ + if ( !newdrivers ) { return; } + bool allsuccess = true; + for ( size_t i = 0; i < numdrivers; i++ ) + { + bool success = false; + if ( drivers[i].flags & DRIVER_GOT_MODES ) { continue; } + const char* drivername = drivers[i].name; + VideoDriver* driver = drivers[i].driver; + size_t prevnummodes = nummodes; + size_t drvnummodes = 0; + char** drvmodes = driver->GetModes(&drvnummodes); + if ( !drvmodes ) { goto cleanup_error; } + if ( !ExpandModesArray(drvnummodes) ) { goto cleanup_drvmodes; } + for ( size_t n = 0; n < drvnummodes; n++ ) + { + char* modestr = String::Combine(4, "driver=", drivername, + ",", drvmodes[n]); + if ( !modestr ) { goto cleanup_newmodes; } + modes[nummodes++] = modestr; + } + success = true; + drivers[i].flags |= DRIVER_GOT_MODES; +cleanup_newmodes: + for ( size_t i = prevnummodes; !success && i < nummodes; i++ ) + delete[] modes[i]; + if ( !success ) { nummodes = prevnummodes; } +cleanup_drvmodes: + for ( size_t n = 0; n < drvnummodes; n++ ) { delete[] drvmodes[n]; } + delete[] drvmodes; +cleanup_error: + allsuccess &= success; + } + newdrivers = !allsuccess; +} + +static DriverEntry* GetDriverEntry(const char* drivername) +{ + for ( size_t i = 0; i < numdrivers; i++ ) + { + if ( !String::Compare(drivername, drivers[i].name) ) + { + return drivers + i; + } + } + Error::Set(ENODRV); + return NULL; +} + +static bool StartUpDriver(VideoDriver* driver, const char* drivername) +{ + if ( !driver->StartUp() ) + { + int errnum = Error::Last(); + Log::PrintF("Error: Video driver '%s' was unable to startup\n", + drivername); + Error::Set(errnum); + return false; + } + return true; +} + +static bool ShutDownDriver(VideoDriver* driver, const char* drivername) +{ + textbufhandle->Replace(NULL); + if ( !driver->ShutDown() ) + { + int errnum = Error::Last(); + Log::PrintF("Warning: Video driver '%s' did not shutdown cleanly\n", + drivername); + Error::Set(errnum); + return false; + } + return true; +} + +static bool DriverModeAction(VideoDriver* driver, const char* drivername, + const char* mode, const char* action) +{ + textbufhandle->Replace(NULL); + if ( !driver->SwitchMode(mode) ) + { + int errnum = Error::Last(); + Log::PrintF("Error: Video driver '%s' could not %s mode '%s'\n", + drivername, action, mode); + Error::Set(errnum); + return false; + } + textbufhandle->Replace(driver->CreateTextBuffer()); + return true; +} + +static bool SwitchDriverMode(VideoDriver* driver, const char* drivername, + const char* mode) +{ + return DriverModeAction(driver, drivername, mode, "switch to"); +} + + +static bool RestoreDriverMode(VideoDriver* driver, const char* drivername, + const char* mode) +{ + return DriverModeAction(driver, drivername, mode, "restore"); +} + +// Attempts to use the specific driver and mode, if an error occurs, it will +// attempt to reload the previous driver and mode. If that fails, we are kinda +// screwed and the video adapter is left in an undefined state. +static bool DoSwitchMode(DriverEntry* newdrvent, const char* newmode) +{ + DriverEntry* prevdrvent = CurrentDriverEntry(); + VideoDriver* prevdriver = prevdrvent ? prevdrvent->driver : NULL; + const char* prevdrivername = prevdrvent ? prevdrvent->name : NULL; + + VideoDriver* newdriver = newdrvent->driver; + const char* newdrivername = newdrvent->name; + + char* newcurrentmode = String::Clone(newmode); + if ( !newcurrentmode ) { return false; } + + if ( prevdriver == newdriver ) + { + if ( !SwitchDriverMode(newdriver, newdrivername, newmode) ) + { + delete[] newcurrentmode; + return false; + } + delete[] currentmode; + currentmode = newcurrentmode; + return true; + } + + int errnum = 0; + + if ( prevdriver ) { ShutDownDriver(prevdriver, prevdrivername); } + + char* prevmode = currentmode; currentmode = NULL; + currentdrvid = SIZE_MAX; + + if ( !StartUpDriver(newdriver, newdrivername) ) + { + errnum = Error::Last(); + goto restore_prev_driver; + } + + currentdrvid = newdrvent->id; + + if ( !SwitchDriverMode(newdriver, newdrivername, newmode) ) + { + errnum = Error::Last(); + ShutDownDriver(newdriver, newdrivername); + currentdrvid = SIZE_MAX; + goto restore_prev_driver; + } + + currentmode = newcurrentmode; + delete[] prevmode; + + return true; + +restore_prev_driver: + delete[] newcurrentmode; + if ( !prevdriver ) { goto error_out; } + if ( !StartUpDriver(prevdriver, prevdrivername) ) { goto error_out; } + + currentdrvid = prevdrvent->id; + + if ( !RestoreDriverMode(prevdriver, prevdrivername, prevmode) ) + { + ShutDownDriver(prevdriver, prevdrivername); + currentdrvid = SIZE_MAX; + goto error_out; + } + + Log::PrintF("Successfully restored video driver '%s' mode '%s'\n", + prevdrivername, prevmode); + +error_out: + if ( currentdrvid == SIZE_MAX ) + Log::PrintF("Warning: Could not fall back upon a video driver\n"); + Error::Set(errnum); // Return the original error, not the last one. + return false; +} + +char* GetCurrentMode() +{ + ScopedLock lock(&videolock); + UpdateModes(); + return String::Clone(currentmode ? currentmode : "driver=none"); +} + +char** GetModes(size_t* modesnum) +{ + ScopedLock lock(&videolock); + UpdateModes(); + 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 j = 0; j < i; j++ ) + { + delete[] result[j]; + } + delete[] result; + return NULL; + } + } + *modesnum = nummodes; + return result; +} + +bool SwitchMode(const char* mode) +{ + ScopedLock lock(&videolock); + UpdateModes(); + char* drivername = NULL; + if ( !ReadParamString(mode, "driver", &drivername, NULL) ) { return false; } + DriverEntry* drvent = GetDriverEntry(drivername); + delete[] drivername; + if ( !drvent ) { return false; } + return DoSwitchMode(drvent, mode); +} + +bool Supports(const char* mode) +{ + ScopedLock lock(&videolock); + UpdateModes(); + char* drivername = NULL; + if ( !ReadParamString(mode, "driver", &drivername, NULL) ) { return false; } + DriverEntry* drvent = GetDriverEntry(drivername); + delete[] drivername; + if ( !drvent ) { return false; } + return drvent->driver->Supports(mode); +} + +off_t FrameSize() +{ + ScopedLock lock(&videolock); + DriverEntry* drvent = CurrentDriverEntry(); + if ( !drvent ) { Error::Set(EINVAL); return -1; } + return drvent->driver->FrameSize(); +} + +ssize_t WriteAt(off_t off, const void* buf, size_t count) +{ + ScopedLock lock(&videolock); + DriverEntry* drvent = CurrentDriverEntry(); + if ( !drvent ) { Error::Set(EINVAL); return -1; } + return drvent->driver->WriteAt(off, buf, count); +} + +ssize_t ReadAt(off_t off, void* buf, size_t count) +{ + ScopedLock lock(&videolock); + DriverEntry* drvent = CurrentDriverEntry(); + if ( !drvent ) { Error::Set(EINVAL); return -1; } + return drvent->driver->WriteAt(off, buf, count); +} + +} // namespace Video + +} // namespace Sortix