Initial 0.1 commit

This is a small daemon/libary to enable games to request a
performance "game mode" from the system.

Currently only implemented to upgrade the CPU governor and
automatically downgrade it once done.

A game only needs to load libgamemodeauto to activate the request.
Which means an LD_PRELOAD can be applied to any game to activate
the mode as needed.

However gamemode_client.h can be used to manually activate/deactivate
the mode as needed, and also perform error checking.

The libs are miminal loaders to call into an installed libgamemode
which, if the daemon is installed and running, will send through
the equivelant game mode request, and magic will happen.

See the README.md for more details.

Currently licensed under the BSD 3 clause license.
This commit is contained in:
Marc Di Luzio 2017-12-11 11:26:59 +00:00
commit c459c05076
23 changed files with 1621 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
*.swp

26
LICENSE.txt Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

111
README.md Normal file
View File

@ -0,0 +1,111 @@
# GameMode
A preliminary implementation of a daemon/lib combo to allow games to request a performance mode from the host OS on Linux. It was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU power states as needed.
Currently using `sd-bus` on the user bus internally for messaging
---
## Components
### Host
#### gamemoded
Runs in the background, waits for requests, refcounts and also checks caller PID lifetime.
Accepts `-d` (daemonize) and `-l` (log to syslog)
#### libgamemode
Dynamic library to dispatch requests to the daemon
Note: Behaviour of `gamemoded` may change, so `libgamemode` should never be linked with directly.
#### cpugovctl
Small program used to to control the cpu governor.
Accepts `get` (gets current governor) and `set <GOVERNOR>` (sets the current governor).
### Clients
#### libgamemodeauto
Simple dynamic library that automatically requests game mode when loaded. Minimal dependencies.
Useful to `LD_PRELOAD` into any game as needed.
#### gamemode\_client.h
Very small header only lib that lets a game request game mode and handle errors. Minimal dependencies.
Can also be included with `GAMEMODE_AUTO` defined to behave automatically.
---
## Build and install
### Daemon and host library
#### Dependencies
* meson
* systemd
```bash
# Ubuntu
apt install meson libsystemd-dev
# Arch
pacman -S meson systemd
```
```bash
git clone <git repo>
cd gamemode
meson --prefix=/usr build
cd build
ninja
sudo ninja install
```
---
## Using with any game or program
After installing `libgamemodeauto.so` simple preload it into the program. Examples:
```bash
LD_PRELOAD=/usr/lib/libgamemodeauto.so ./game
```
Or steam launch options
```bash
LD_PRELOAD=/usr/lib/libgamemodeauto.so %command%
```
---
## Building into a game or program
You may want to build in functionality directly into an app, this stops the need for users to have to manually set anything up.
`gamemode_client.h` and `libgamemodeauto` are safe to use regardless of whether libgamemode and gamemoded are installed on the system. They also do not require `systemd`.
### Using directly
```C
#include "gamemode_client.h"
if( gamemode_request_start() < 0 )
fprintf( stderr, "gamemode request failed: %s\n", gamemode_error_string() );
/* run game... */
gamemode_request_end(); // Not required, gamemoded will clean up after game exists anyway
```
### Using automatically
#### Option 1: Include in code
```C
#define GAMEMODE_AUTO
#include "gamemode_client.h"
```
#### Option 2: Link and distribute
Add `-lgamemodeauto` to linker arguments and distribute `libgamemodeauto.so` with the game
#### Option 3: Distribute and script
Distribute `libgamemodeauto.so` with the game and add to LD\_PRELOAD in a launch script
---
## TODO
* Use polkit for cpugovctl (currently simply using chmod +4555)
* Implement some kind of user confuguration to allow for whitelists, extra behaviour, etc.

28
bootstrap.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
# Simple bootstrap script to build and run the daemon
set -e
# Echo the rest so it's obvious
set -x
meson --prefix=/usr build
cd build
ninja
# Verify user wants to install
set +x
read -p "Install to /usr? [Yy] " -r
[[ $REPLY =~ ^[Yy]$ ]]
set -x
sudo ninja install
# Verify user wants to run the daemon
set +x
read -p "Enable and run the daemon? [Yy] " -r
[[ $REPLY =~ ^[Yy]$ ]]
set -x
systemctl --user daemon-reload
systemctl --user enable gamemoded
systemctl --user start gamemoded
systemctl --user status gamemoded

193
daemon/cpugovctl.c Normal file
View File

@ -0,0 +1,193 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "logging.h"
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
static const int max_governors = 128;
static const int max_governor_length = PATH_MAX+1;
// Governers are located at /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
static int fetch_governors( char governors[max_governors][max_governor_length] )
{
const char* cpu_base_path = "/sys/devices/system/cpu/";
DIR* dir = opendir( cpu_base_path );
if( !dir )
FATAL_ERRORNO( "cpu device path not found" );
int num_governors = 0;
// Explore the directory
struct dirent* ent;
while( ( ent = readdir(dir) ) && num_governors < max_governors )
{
// CPU directories all start with "cpu"
if( strncmp( ent->d_name, "cpu", 3 ) == 0 )
{
// Check if this matches "cpu\d+"
const int len = strlen( ent->d_name );
if( len > 3 && len < 5
&& isdigit( ent->d_name[3] ) )
{
// Construct the full path
char path[PATH_MAX] = {};
snprintf( path, sizeof(path), "%s%s/cpufreq/scaling_governor", cpu_base_path, ent->d_name );
// Get the real path to the file
// Traditionally cpufreq symlinks to a policy directory that can be shared
// So let's prevent duplicates
char fullpath[PATH_MAX] = {};
const char* ptr = realpath( path, fullpath );
if( fullpath != ptr )
continue;
// Only add if unique
for( int i = 0; i < num_governors; i++ )
{
if( strncmp( fullpath, governors[i], max_governor_length ) == 0 )
continue;
}
strncpy( governors[num_governors], fullpath, max_governor_length );
num_governors++;
}
}
}
closedir(dir);
return num_governors;
}
// Get the current governor state
const char* get_gov_state()
{
// To be returned
static char governor[64];
memset( governor, 0, sizeof(governor) );
// State of all the overnors
char governors[max_governors][max_governor_length];
memset( governors, 0, sizeof(governors) );
int num = fetch_governors( governors );
// Check the list
for( int i = 0; i < num; i++ )
{
const char* gov = governors[i];
FILE* f = fopen( gov, "r" );
if( !f )
{
LOG_ERROR( "Failed to open file for read %s\n", gov );
continue;
}
// Pull out the file contents
fseek( f, 0, SEEK_END );
int length = ftell(f);
fseek( f, 0, SEEK_SET );
char contents[length];
if( fread(contents, 1, length, f) > 0 )
{
// Files have a newline
strtok(contents, "\n");
if( strlen(governor) > 0 && strncmp( governor, contents, 64 ) != 0 )
{
// Don't handle the mixed case, this shouldn't ever happen
// But it is a clear sign we shouldn't carry on
LOG_ERROR( "Governors malformed: got \"%s\", expected \"%s\"", contents, governor );
return "malformed";
}
strncpy( governor, contents, sizeof(governor) );
}
else
{
LOG_ERROR( "Failed to read contents of %s\n", gov );
}
fclose( f );
}
return governor;
}
// Sets all governors to a value
void set_gov_state( const char* value )
{
char governors[max_governors][max_governor_length];
memset( governors, 0, sizeof(governors) );
int num = fetch_governors( governors );
LOG_MSG( "Setting governors to %s\n", value );
for( int i = 0; i < num; i++ )
{
const char* gov = governors[i];
FILE* f = fopen( gov, "w" );
if( !f )
{
LOG_ERROR( "Failed to open file for write %s\n", gov );
continue;
}
fprintf( f, "%s\n", value );
fclose( f );
}
}
// Main entry point
int main( int argc, char *argv[] )
{
if( argc < 2 )
{
fprintf( stderr, "usage: cpugovctl [get] [set VALUE]\n" );
exit( EXIT_FAILURE );
}
if( strncmp( argv[1], "get", 3 ) == 0 )
{
printf( "%s", get_gov_state() );
}
else if( strncmp( argv[1], "set", 3 ) == 0 )
{
const char* value = argv[2];
set_gov_state( value );
}
else
{
exit( EXIT_FAILURE );
}
}

69
daemon/daemonize.c Normal file
View File

@ -0,0 +1,69 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "daemonize.h"
#include "logging.h"
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// function to daemonize the process
void daemonize( char* name )
{
// Fork once
pid_t pid = fork();
if( pid < 0 )
FATAL_ERRORNO( "Failed to fork" );
if( pid != 0 )
{
LOG_MSG( "Daemon launched...\n" );
exit( EXIT_SUCCESS );
}
// Fork a second time
pid = fork();
if( pid < 0 )
FATAL_ERRORNO( "Failed to fork" );
else if( pid > 0 )
exit( EXIT_SUCCESS );
// Continue to set up as a daemon
umask(0);
if ( setsid() < 0 )
FATAL_ERRORNO( "Failed to create process group\n" );
if ( chdir( "/" ) < 0 )
FATAL_ERRORNO( "Failed to change to root directory\n" );
close( STDIN_FILENO );
close( STDOUT_FILENO );
close( STDERR_FILENO );
}

38
daemon/daemonize.h Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _DAEMONIZE_GAMEMODE_H_
#define _DAEMONIZE_GAMEMODE_H_
// Function to daemonize the process
// Exits with error in case of failure
void daemonize( char* name );
#endif // _DAEMONIZE_GAMEMODE_H_

154
daemon/dbus_messaging.c Normal file
View File

@ -0,0 +1,154 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "dbus_messaging.h"
#include "logging.h"
#include "gamemode.h"
#include "governors.h"
#include "daemonize.h"
#include <stdlib.h>
#include <systemd/sd-bus.h>
// sd-bus tracker values
static sd_bus* bus = NULL;
static sd_bus_slot* slot = NULL;
// Clean up any resources as needed
static void clean_up()
{
if( slot )
sd_bus_slot_unref( slot );
slot = NULL;
if( bus )
sd_bus_unref( bus );
bus = NULL;
}
// Callback for RegisterGame
static int method_register_game( sd_bus_message *m,
void *userdata,
sd_bus_error *ret_error )
{
int pid = 0;
int ret = sd_bus_message_read( m, "i", &pid );
if( ret < 0 )
{
LOG_ERROR( "Failed to parse input parameters: %s\n", strerror(-ret) );
return ret;
}
register_game( pid );
return sd_bus_reply_method_return( m, "i", 0 );
}
// Callback for UnregisterGame
static int method_unregister_game( sd_bus_message *m,
void *userdata,
sd_bus_error *ret_error )
{
int pid = 0;
int ret = sd_bus_message_read( m, "i", &pid );
if( ret < 0 )
{
LOG_ERROR( "Failed to parse input parameters: %s\n", strerror(-ret) );
return ret;
}
unregister_game( pid );
return sd_bus_reply_method_return( m, "i", 0 );
}
// Vtable for function dispatch
static const sd_bus_vtable gamemode_vtable[] = {
SD_BUS_VTABLE_START( 0 ),
SD_BUS_METHOD( "RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED ),
SD_BUS_METHOD( "UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED ),
SD_BUS_VTABLE_END
};
// Main loop, will not return until something request a quit
void run_dbus_main_loop( bool system_dbus )
{
// Set up function to handle clean up of resources
atexit( clean_up );
int ret = 0;
// Connec to the desired bus
if( system_dbus )
ret = sd_bus_open_system( &bus );
else
ret = sd_bus_open_user( &bus );
if( ret < 0 )
FATAL_ERROR( "Failed to connect to the bus: %s", strerror(-ret) );
// Create the object to allow connections
ret = sd_bus_add_object_vtable( bus,
&slot,
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
gamemode_vtable,
NULL );
if( ret < 0 )
FATAL_ERROR( "Failed to install GameMode object: %s", strerror(-ret) );
// Request our name
ret = sd_bus_request_name( bus, "com.feralinteractive.GameMode", 0 );
if( ret < 0 )
FATAL_ERROR( "Failed to acquire service name: %s", strerror(-ret) );
LOG_MSG( "Successfully initialised bus with name [%s]...\n", "com.feralinteractive.GameMode" );
// Now loop, waiting for callbacks
for(;;)
{
ret = sd_bus_process( bus, NULL );
if( ret < 0 )
FATAL_ERROR( "Failure when processing the bus: %s", strerror(-ret) );
// We're done processing
if( ret > 0 )
continue;
// Wait for more
ret = sd_bus_wait( bus, (uint64_t)-1 );
if( ret < 0 && -ret != EINTR )
FATAL_ERROR( "Failure when waiting on bus: %s", strerror(-ret) );
}
}

40
daemon/dbus_messaging.h Normal file
View File

@ -0,0 +1,40 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _DBUS_MESSAGING_GAMEMODE_H_
#define _DBUS_MESSAGING_GAMEMODE_H_
#include <stdbool.h>
// Run the main dbus loop
// Will not return until finished
void run_dbus_main_loop( bool system_dbus );
#endif // _DBUS_MESSAGING_GAMEMODE_H_

177
daemon/gamemode.c Normal file
View File

@ -0,0 +1,177 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "gamemode.h"
#include "logging.h"
#include "governors.h"
#include <signal.h>
#include <string.h>
// Storage for game PIDs
#define MAX_GAMES 16
static int game_pids[MAX_GAMES];
static int num_games = 0;
// Constant to control how often we'll wake up and check process ids
static const int wakeup_timer = 5;
static void start_alarm_timer();
// Called once to enter game mode
// Must be followed by a call to leave_game_mode
// Must be called after init_game_mode
static void enter_game_mode()
{
LOG_MSG( "Entering Game Mode...\n" );
set_governors( "performance" );
// Set up the alarm callback
start_alarm_timer();
}
// Called once to leave game mode
// Must be called after an equivelant call to enter_game_mode
static void leave_game_mode()
{
LOG_MSG( "Leaving Game Mode...\n" );
set_governors( NULL );
}
// Set up an alarm callback to check for current process ids
static void alarm_handler( int sig )
{
// Quick return if no games, and don't register another callback
if( num_games == 0 )
return;
// Check if games are alive at all
for( int i = 0; i < num_games; )
{
int game = game_pids[i];
if( kill( game, 0 ) != 0 )
{
LOG_MSG( "Removing expired game [%i]...\n", game );
memmove( &game_pids[i], &game_pids[i+1], MAX_GAMES - (i+1) );
num_games--;
}
else
i++;
}
// Either trigger another alarm, or reset the governors
if( num_games )
start_alarm_timer();
else
leave_game_mode();
}
// Call to trigger starting the alarm timer for pid checks
static void start_alarm_timer()
{
// Set up process check alarms
signal( SIGALRM, alarm_handler );
alarm( wakeup_timer );
}
// Intialise any game mode needs
void init_game_mode()
{
// Read current governer state before setting up any message handling
update_initial_gov_state();
LOG_MSG( "governor is set to [%s]\n", get_initial_governor() );
}
// Called on exit to clean up the governors if appropriate
void term_game_mode()
{
if( num_games )
leave_game_mode();
}
// Register a game pid with the game mode
// Will trigger enter game mode if appropriate
void register_game( int pid )
{
// Check for duplicates
for( int i = 0; i < num_games; i++ )
{
if( game_pids[i] == pid )
{
LOG_ERROR( "Addition requested for already known process [%i]\n", pid );
return;
}
}
// Check we've not already hit max
if( num_games == MAX_GAMES )
{
LOG_ERROR( "Max games (%i) reached, could not add [%i]\n", MAX_GAMES, pid );
return;
}
// Add the game to the database
LOG_MSG( "Adding game: %i\n", pid );
game_pids[num_games] = pid;
num_games++;
if( num_games == 1 )
enter_game_mode();
}
// Remove a game from game mode
// Will exit game mode if appropriate
void unregister_game( int pid )
{
bool found = false;
// Check list even contains this entry
for( int i = 0; i < num_games; i++ )
{
if( game_pids[i] == pid )
{
LOG_MSG( "Removing game: %i\n", pid );
memmove( &game_pids[i], &game_pids[i+1], MAX_GAMES - (i+1) );
num_games--;
found = true;
}
}
if( !found )
{
LOG_ERROR( "Removal requested for unknown process [%i]\n", pid );
return;
}
// Leave game mode if needed
if( num_games == 0 )
leave_game_mode();
}

43
daemon/gamemode.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _GAME_MODE_GAMEMODE_H_
#define _GAME_MODE_GAMEMODE_H_
// Initialise or terminate the game mode system
void init_game_mode();
void term_game_mode();
// Add or remove games to the tracker
// Tracker will automatically start and stop game mode as appropriate
void register_game( int pid );
void unregister_game( int pid );
#endif // _GAME_MODE_GAMEMODE_H_

78
daemon/governors.c Normal file
View File

@ -0,0 +1,78 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "governors.h"
#include "logging.h"
#include <stdio.h>
#include <unistd.h>
#include <linux/limits.h>
static char initial[32];
// Store the initial governor state to be referenced later
void update_initial_gov_state()
{
static char* command = "cpugovctl get";
FILE* f = popen( command, "r" );
if( !f )
FATAL_ERRORNO( "Failed to launch \"%s\" script", command );
if( !fgets( initial, sizeof(initial)-1, f ) )
FATAL_ERROR( "Failed to get output from \"%s\"", command );
pclose(f);
strtok( initial, "\n" );
}
// Sets all governors to a value, if NULL argument provided, will reset them back
void set_governors( const char* value )
{
const char* newval = value ? value : initial;
LOG_MSG("Setting governors to %s\n", newval ? newval : "initial values");
char command[PATH_MAX] = {};
snprintf( command, sizeof(command), "cpugovctl set %s", newval );
FILE* f = popen( command, "r" );
if( !f )
FATAL_ERRORNO( "Failed to launch %s script", command );
pclose(f);
}
// Return the initial governor
const char* get_initial_governor()
{
return initial;
}

43
daemon/governors.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _GOVERNORS_GAMEMODE_H_
#define _GOVERNORS_GAMEMODE_H_
// Store the initial governor state to be referenced later
void update_initial_gov_state();
// Get the initial governor state
const char* get_initial_governor();
// Sets all governors to a value, if null argument provided, will reset them back
void set_governors( const char* value );
#endif // _GOVERNORS_GAMEMODE_H_

48
daemon/logging.c Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "logging.h"
#include "syslog.h"
static bool use_syslog = false;
// Control if we want to use the system logger
void set_use_syslog( const char* name )
{
// Open the syslog
openlog( name, LOG_PID, LOG_DAEMON );
use_syslog = true;
}
// Simple getter for the syslog var
bool get_use_syslog()
{
return use_syslog;
}

59
daemon/logging.h Normal file
View File

@ -0,0 +1,59 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _LOGGING_GAMEMODE_H_
#define _LOGGING_GAMEMODE_H_
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <stdbool.h>
// Logging helpers
#define PLOG_MSG( msg, ... ) printf( msg, ##__VA_ARGS__ )
#define SYSLOG_MSG( msg, ... ) syslog( LOG_INFO, msg, ##__VA_ARGS__ )
#define LOG_MSG( msg, ... ) do { if( get_use_syslog() ) SYSLOG_MSG( msg, ##__VA_ARGS__ ); else PLOG_MSG( msg, ##__VA_ARGS__ ); } while(0)
#define PLOG_ERROR( msg, ... ) fprintf( stderr, msg, ##__VA_ARGS__ )
#define SYSLOG_ERROR( msg, ... ) syslog( LOG_ERR, msg, ##__VA_ARGS__ )
#define LOG_ERROR( msg, ... ) do { if( get_use_syslog() ) SYSLOG_MSG( msg, ##__VA_ARGS__ ); else PLOG_MSG( msg, ##__VA_ARGS__ ); } while(0)
// Fatal errors trigger an exit
#define FATAL_ERRORNO( msg, ... ) do { LOG_ERROR( msg " (%s)\n", ##__VA_ARGS__, strerror(errno) ); exit(EXIT_FAILURE); } while(0)
#define FATAL_ERROR( msg, ... ) do { LOG_ERROR( msg, ##__VA_ARGS__ ); exit(EXIT_FAILURE); } while(0)
// Control if we want to use the system logger
void set_use_syslog( const char* name );
bool get_use_syslog();
#endif //_LOGGING_GAMEMODE_H_

99
daemon/main.c Normal file
View File

@ -0,0 +1,99 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
// Simple daemon to allow user space programs to control the CPU governors
#include "gamemode.h"
#include "dbus_messaging.h"
#include "logging.h"
#include "daemonize.h"
#include <string.h>
#include <unistd.h>
#include <signal.h>
static void sigint_handler(int signo)
{
LOG_MSG( "Quitting by request...\n" );
// Terminate the game mode
term_game_mode();
exit( EXIT_SUCCESS );
}
// Main entry point
int main( int argc, char *argv[] )
{
// Gather command line options
bool daemon = false;
bool system_dbus = false;
bool use_syslog = false;
int opt = 0;
while( ( opt = getopt( argc, argv, "dsl") ) != -1 )
{
switch( opt )
{
case 'd':
daemon = true;
break;
case 's':
system_dbus = true;
break;
case 'l':
use_syslog = true;
break;
default:
fprintf( stderr, "Usage: %s [-d] [-s] [-l]\n", argv[0] );
exit( EXIT_FAILURE );
break;
}
}
// Use syslog if requested
if( use_syslog )
set_use_syslog( argv[0] );
// Daemonize ourselves first if asked
if ( daemon )
daemonize( argv[0] );
// Set up the game mode
init_game_mode();
// Set up the SIGINT handler
if( signal( SIGINT, sigint_handler ) == SIG_ERR )
FATAL_ERRORNO( "Could not catch SIGINT" );
// Run the main dbus message loop
run_dbus_main_loop( system_dbus );
// Log we're finished
LOG_MSG( "Quitting naturally...\n" );
}

5
data/cpugovctl_perms.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# Allow cpugovctl to control the governors
chmod +4555 ${MESON_INSTALL_PREFIX}/bin/cpugovctl

10
data/gamemoded.service Normal file
View File

@ -0,0 +1,10 @@
[Unit]
Description=gamemoded
[Service]
Type=dbus
BusName=com.feralinteractive.GameMode
ExecStart=/usr/bin/gamemoded -l
[Install]
WantedBy=default.target

48
example/main.c Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "gamemode_client.h"
#include <unistd.h>
#include <stdio.h>
int main(){
// Request we start game mode
if( gamemode_request_start() != 0 )
printf( "Failed to request gamemode start: %s...\n", gamemode_error_string() );
// Simulate running a game
sleep( 10 );
// Request we end game mode (optional)
if( gamemode_request_end() != 0 )
printf( "Failed to request gamemode end: %s...\n", gamemode_error_string() );
}

97
lib/client_impl.c Normal file
View File

@ -0,0 +1,97 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <systemd/sd-bus.h>
// Storage for error strings
static char error_string[512] = {};
// Simple requestor function for a gamemode
static int gamemode_request( const char* function )
{
sd_bus_message* msg = NULL;
sd_bus* bus = NULL;
int result = -1;
// Open the user bus
int ret = sd_bus_open_user(&bus);
if( ret < 0 )
snprintf( error_string, sizeof(error_string), "Could not connect to bus: %s", strerror(-ret) );
else
{
// Attempt to send the requested function
ret = sd_bus_call_method( bus,
"com.feralinteractive.GameMode",
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
function,
NULL,
&msg,
"i",
getpid() );
if( ret < 0 )
snprintf( error_string, sizeof(error_string), "Could not call method on bus: %s", strerror(-ret) );
else
{
// Read the reply
ret = sd_bus_message_read( msg, "i", &result );
if( ret < 0 )
snprintf( error_string, sizeof(error_string), "Failure to parse response: %s", strerror(-ret) );
}
}
return result;
}
// Get the error string
extern const char* real_gamemode_error_string()
{
return error_string;
}
// Wrapper to call RegisterGame
extern int real_gamemode_request_start()
{
return gamemode_request( "RegisterGame" );
}
// Wrapper to call UnregisterGame
extern int real_gamemode_request_end()
{
return gamemode_request( "UnregisterGame" );
}

35
lib/client_loader.c Normal file
View File

@ -0,0 +1,35 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
// Simply include the header with GAMEMODE_AUTO set
// This will ensure it calls the functions when it's loaded
#define GAMEMODE_AUTO
#include "gamemode_client.h"

165
lib/gamemode_client.h Normal file
View File

@ -0,0 +1,165 @@
/*
Copyright (c) 2017, Feral Interactive
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Feral Interactive nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _CLIENT_GAMEMODE_H_
#define _CLIENT_GAMEMODE_H_
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
char _client_error_string[512] = {};
// Load libgamemode dynamically to dislodge us from most dependencies
// This allows clients to link and/or use this regardless of runtime
// See SDL2 for an example of the reasoning behind this in terms of
// dynamic versioning as well
int _libgamemode_loaded = 1;
// Typedefs for the functions to load
typedef int(*_gamemode_request_start)();
typedef int(*_gamemode_request_end)();
typedef const char*(*_gamemode_error_string)();
// Storage for functors
_gamemode_request_start _REAL_gamemode_request_start = NULL;
_gamemode_request_end _REAL_gamemode_request_end = NULL;
_gamemode_error_string _REAL_gamemode_error_string = NULL;
// Loads libgamemode and needed functions
// returns 0 on success and -1 on failure
__attribute__((always_inline))
inline int _load_libgamemode()
{
// We start at 1, 0 is a success and -1 is a fail
if ( _libgamemode_loaded != 1 )
return _libgamemode_loaded;
void* libgamemode = NULL;
// Try and load libgamemode
libgamemode = dlopen( "libgamemode.so", RTLD_NOW );
if( !libgamemode )
snprintf( _client_error_string, sizeof(_client_error_string), "dylopen failed - %s", dlerror() );
else
{
_REAL_gamemode_request_start = (_gamemode_request_start)dlsym( libgamemode, "real_gamemode_request_start" );
_REAL_gamemode_request_end = (_gamemode_request_end) dlsym( libgamemode, "real_gamemode_request_end" );
_REAL_gamemode_error_string = (_gamemode_error_string) dlsym( libgamemode, "real_gamemode_error_string" );
// Verify we have the functions we want
if( _REAL_gamemode_request_start && _REAL_gamemode_request_end && _REAL_gamemode_error_string )
{
_libgamemode_loaded = 0;
return 0;
}
else
snprintf( _client_error_string, sizeof(_client_error_string), "dlsym failed - %s", dlerror() );
}
_libgamemode_loaded = -1;
return -1;
}
// Redirect to the real libgamemode
__attribute__((always_inline))
inline const char* gamemode_error_string()
{
// If we fail to load the system gamemode, return our error string
if( _load_libgamemode() < 0 )
return _client_error_string;
return _REAL_gamemode_error_string();
}
// Redirect to the real libgamemode
// Allow automatically requesting game mode
// Also prints errors as they happen
#ifdef GAMEMODE_AUTO
__attribute__((constructor))
#else
__attribute__((always_inline))
inline
#endif
int gamemode_request_start()
{
// Need to load gamemode
if( _load_libgamemode() < 0 )
{
#ifdef GAMEMODE_AUTO
fprintf( stderr, "gamemodeauto: %s\n", gamemode_error_string() );
#endif
return -1;
}
if( _REAL_gamemode_request_start() < 0 )
{
#ifdef GAMEMODE_AUTO
fprintf( stderr, "gamemodeauto: %s\n", gamemode_error_string() );
#endif
return -1;
}
return 0;
}
// Redirect to the real libgamemode
#ifdef GAMEMODE_AUTO
__attribute__((destructor))
#else
__attribute__((always_inline))
inline
#endif
int gamemode_request_end()
{
// Need to load gamemode
if( _load_libgamemode() < 0 )
{
#ifdef GAMEMODE_AUTO
fprintf( stderr, "gamemodeauto: %s\n", gamemode_error_string() );
#endif
return -1;
}
if( _REAL_gamemode_request_end() < 0 )
{
#ifdef GAMEMODE_AUTO
fprintf( stderr, "gamemodeauto: %s\n", gamemode_error_string() );
#endif
return -1;
}
return 0;
}
#endif // _CLIENT_GAMEMODE_H_

53
meson.build Normal file
View File

@ -0,0 +1,53 @@
project('gamemode', 'c',
version : '0.1',
license : 'BSD' )
cc = meson.get_compiler('c')
libsystemd = cc.find_library('systemd')
libdl = cc.find_library('dl')
executable( 'gamemoded',
'daemon/main.c',
'daemon/gamemode.c',
'daemon/logging.c',
'daemon/daemonize.c',
'daemon/dbus_messaging.c',
'daemon/governors.c',
dependencies: libsystemd,
install: true )
# Main client library to message the daemon
shared_library( 'gamemode',
'lib/client_impl.c',
dependencies: libsystemd,
install : true )
# install the service file
install_data( 'data/gamemoded.service', install_dir: '/etc/systemd/user' )
# Small target util to get and set cpu governors
executable( 'cpugovctl',
'daemon/cpugovctl.c',
'daemon/logging.c',
install : true )
# Give cpugovctl the permissions it needs
meson.add_install_script( 'data/cpugovctl_perms.sh',
dependencies : 'cpugovctl' )
# Small library to automatically use gamemode
shared_library( 'gamemodeauto',
'lib/client_loader.c',
dependencies : libdl,
install : true )
# Install the gamemode_client header
install_headers( 'lib/gamemode_client.h' )
# An example game
libdir = include_directories('lib')
executable( 'example',
'example/main.c',
dependencies : libdl,
include_directories : libdir )