mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
b0807e8c7a
This really fucks stuff up.
435 lines
11 KiB
C++
435 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*)) )
|
|
{
|
|
if ( String::Compare(keyname, "STOP") == 0 )
|
|
break;
|
|
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->ReadAt(off, buf, count);
|
|
}
|
|
|
|
} // namespace Video
|
|
|
|
} // namespace Sortix
|