sortix-mirror/sortix/video.cpp
Jonas 'Sortie' Termansen 1ce55af846 Created framework for video drivers.
This supports dynamic loading and unloading of graphics drivers, mode
switching and detection and flexible kernel access to the framebuffer.
2012-07-30 19:00:24 +02:00

434 lines
11 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/>.
video.cpp
Framework for Sortix video drivers.
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/textbuffer.h>
#include <sortix/kernel/video.h>
#include <stdarg.h>
#include <libmaxsi/error.h>
#include <libmaxsi/memory.h>
#include <libmaxsi/string.h>
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