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