Preface:
Recently I had to switch to NV video card - I'm not really happy about it, but I was forced to do this.
The very first problem which I've encountered while using NV gfx card is that NV proprietary drivers are not fully compatible with the Linux graphics stack - their implementation is merely a hack, which indeed works in typical cases, but fails otherwise.
I have to use OpenGL in a schroot'ed debootstrap'ed system: nouveau works perfectly, nvidia drivers are completely useless, a total disaster speaking gently. (technically because it's impossible to use another instance of nVidia's libGL.so in a schroot'ed system)
So, I have to use nouveau driver - of course "as everyone knows" nouveau offers much worse performance,
...unless...
.. only by accident, I've discovered that actually the nouveau driver performs significantly better than proprietary NV driver -> if I manually change the pstate of my gfx card to performance mode:
- glxgears renders 20K frames with Nouveau, but only 15K frames with the latest nVidia driver (v440.59) on my GF750Ti/Ryzen7 3700X -> but yeah, glxgears is not a benchmark ...
- UT2K4, Onslaught mode (Linux version) works perfectly with all settings set to MAX on my system with nouveau, but it "choke" from time to time with NV drivers
What is interesting, in UT2K4 on nouveau the max GPU temp is typically ~50 deg.C, the fan runs @1150rpm, while on nVidia driver I have over 60 deg.C and the fan runs @1500rpm -> nouveau gives 10 degrees lower temps, less noise from the fan, better performance, while running exactly the same game with the same pstate setting
EDIT:
The above results in UT2K4 were obtained with VSync=ON (60fps), without VSync:
Nouveau: 270 ... 1100fps, fan@1600rpm, GPU temp: 54 deg.C
nVidia: 250 ... 900fps, fan@2100rpm, GPU temp: 67 deg.C
???
/EDIT
This HOWTO describes a way to automatically change the pstate of nVidia gfx cards running with nouveau driver.
Rationale:
Nouveau driver currently does not change the pstates automatically, because nVidia is not willing to publish full technical documentation for their GPUs and additionaly, often there are bugs in the firmware, which are bypassed by using some undocumented hacks in propietary drivers - so basically there is a risk that changing the pstate will crash the GPU or even the entire system.
However, in many cases switching between selected "stable" pstates works perfectly correct, and it can be done automatically, using the following method:
Step 1:
Test/discover the "stable" pstates:
Code: Select all
sudo cat sys/kernel/debug/dri/<gfx_card_number>/pstate, default gfx_card_number == 0
The "powersave" pstate is obvoiusly the one with the lowest GPU/memory clock
There can be also pstates with "middle" performance settings -> you have to *experimentally* find the best/stable pstates.
My card reports the following pstates:
Code: Select all
> sudo cat /sys/kernel/debug/dri/0/pstate
0f: core 270-1241 MHz memory 5400 MHz <- performance pstate (stable)
AC: core 405 MHz memory 810 MHz <- broken/unstable pstate
Testing pstates:
Code: Select all
sudo echo <pstate_value> /sys/kernel/debug/dri/<gfx_card_number>/pstate
b) run some game and check if it works correctly
c) change the pstate back to powersave mode
NOTE:
If during testing Your screen will get "messed", then usually the only way to recover is to perform a HARD RESET of your PC. Save Your work before experimenting.
If (unlikely) You can't find stable pstates by manually switching between them, then Your GFX card has a broken firmware -> STOP -> You can't do anything about this. Stop reading this thread
Step 2:
A binary application for changing pstates is needed, because only binary executables can have the suid bit set.
Otherwise, You'll need to manually change the pstates as ROOT, what definitely would be a pain in the ass... (not to mention security issues)
I wrote a simple application, which does the job - it can set the NV pstate trough nouveau debugfs interface.
<Source code at the end of this post>
It does exactly the same thing as the terminal command: <sudo> echo <pstate_value> /sys/kernel/debug/dri/<gfx_card_number>/pstate,
but as an executable, it can have the suid bit set.
Compile the application:
Code: Select all
gcc -O1 -s -o nv_pstate nv_pstate.c
Set the suid bit: (allow running with root privileges, without asking for password)
Code: Select all
chmod u+s /usr/local/bin/nv_pstate
Create startup script for Your application(s):
Code: Select all
#!/bin/bash
#switch to performance pstate
nv_pstate -c0 -s0xf #(example values)
<your application here>
#switch back to powersave pstate
nv_pstate -c0 -s0x7
Create a launcher in Your DE (desktop icon/menu item), which invokes the startup script.
DONE.
One more thing: I've discovered, that by default my card is running in some strange mode: performance voltage level and powersave clocking. If I change the pstate:
>nv_pstate -c0 -s0x7
on system startup, then the card is running with both voltages and clocks defined for powersave mode.
In the result, in the idle state, the GPU temp. dropped from 40 to 38 deg.C and the fan runs @850rpm instead of 950
EDIT:
How to add systemd service for switching pstate to powersave mode on system startup:
Create service unit file: /etc/systemd/system/nv_pstate.service, with the following content:
Code: Select all
[Unit]
Description=switch nv card to a powersave mode (pstate)
[Service]
Type=oneshot
Restart=no
ExecStart=/usr/local/bin/nv_pstate -c0 -s0x7 #(example values)
[Install]
WantedBy=graphical.target
Code: Select all
>systemctl daemon-reload
>systemctl enable nv_pstate.service
Here's the source code: (copy the source and save it as nv_pstate.c)
Code: Select all
/* nv_pstate:
Change pstate of nvidia gfx card.
This program uses nouveau driver interface exposed trough kernel debugfs:
/sys/kernel/debug/dri/<card_number>/pstate
nv_pstate is intended to be used in start-up scripts for games or
other gfx-intensive programs - and therefore it must have suid bit set
WARNING: support for changing pstates is experimental - use at Your own risk!
Author : Tomasz Pawlak
e-mail : tomasz.pawlak@wp.eu
License: GPLv3
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#define ERR_PRINT(...) \
fprintf(stderr, __VA_ARGS__)
#define LOG_PRINT(...) \
fprintf(stdout, __VA_ARGS__)
#define _PROGNAME_ "nv_pstate"
#define MAX_CARD_NR 255
#define MAX_PSTATE 255
const char VERSION [] = "1.0";
const char PROG_NAME [] = _PROGNAME_;
const char HLP_UINFO [] = "Use " _PROGNAME_ " --help or -h to display usage info\n";
const char BAD_PSTATE[] = "[E!] Invalid/missing pstate value!\n";
/* cmd line args */
struct option long_options[] = {
{"card " , required_argument, NULL, 'c'},
{"pstate " , required_argument, NULL, 's'},
{"verbose" , no_argument , NULL, 'V'},
{"help " , no_argument , NULL, 'h'},
{"version" , no_argument , NULL, 'v'},
{0, 0, 0, 0}
};
#define SHORT_OPTS "c:s:Vhv"
#define PATH_SZ 48
#define PATH_MAX (PATH_SZ-1)
typedef struct {
int card ;
int verbose;
int pstate ;
char path [PATH_SZ];
} config_t;
void cfg_print(const config_t *const cfg) {
LOG_PRINT("Configuration:\n"
"card : %d\n"
"path : %s\n"
"pstate: 0x%02X\n",
cfg->card,
cfg->path,
cfg->pstate
);
}
int get_cmdline_opts(int inargc, char* const* inargv, config_t *const cfg) {
static const char BUILD[] = "build: " __DATE__ ", " __TIME__;
static const char HELP[] =
"Options:\n"
" -c --card gfx card number\n"
" -s --pstate pstate value\n"
" -V --verbose verbose mode\n"
" -h --help *this* short usage info\n"
" -v --version display program version\n";
int opt_rv; /* 0 = continue main(), 1 = exit_ok, -1 = exit_error */
int tmpi;
long tmpl;
char *endptr;
opt_rv = 0;
while ((tmpi = getopt_long(inargc, inargv, SHORT_OPTS, long_options, NULL)) != EOF)
{
errno = 0;
switch (tmpi) {
case 'c': /* card */
tmpl = strtol(optarg, &endptr, 0);
if ( (errno != 0) || (tmpl < 0) || (tmpl > MAX_CARD_NR) ) {
ERR_PRINT( "[E!] Invalid card number: \'%s\'\n", optarg);
opt_rv = -1;
}
cfg->card = (int) tmpl;
break;
case 's': /* pstate */
tmpl = strtol(optarg, &endptr, 0);
if ( (errno != 0) || (tmpl < 0) || (tmpl > MAX_PSTATE) ) {
ERR_PRINT( BAD_PSTATE );
opt_rv = -1;
}
cfg->pstate = (int) tmpl;
break;
case 'V': /* verbose */
cfg->verbose = 1;
break;
case 'h': /* help */
LOG_PRINT( HELP );
opt_rv = 1;
break;
case 'v': /* version */
LOG_PRINT("%s version %s, %s\n", PROG_NAME, VERSION, BUILD);
opt_rv = 1;
break;
case '?': /* error */
opt_rv = -1;
break;
}
if (opt_rv != 0) return opt_rv;
}
return opt_rv;
}
int write_pstate(const config_t *const cfg) {
char str_pstate[8];
int fds;
int slen;
int wlen;
int retv = 0;
if (cfg->verbose) cfg_print(cfg);
if (cfg->pstate < 0) {
ERR_PRINT( BAD_PSTATE );
return -1;
}
slen = snprintf(str_pstate, 8, "%x%c", cfg->pstate, 0);
if (slen < 1) {
ERR_PRINT( "[E!] Failed conversion: pstate -> string.\n");
return -1;
}
errno = 0;
fds = open(cfg->path, O_WRONLY);
if (fds < 0) {
ERR_PRINT( "[E!] Failed to open file:\n\"%s\"\n%s\n", cfg->path, strerror(errno) );
return -1;
}
slen ++ ; //++ NULL byte
errno = 0;
wlen = write(fds, str_pstate, slen);
if (wlen < slen) {
ERR_PRINT( "[E!] Failed writing pstate:\n\"%s\"\n%s\n", cfg->path, strerror(errno) );
retv = -1;
}
close(fds);
if ( (retv == 0) && (cfg->verbose) ) LOG_PRINT("[i] pstate changed to 0x%02X\n", cfg->pstate);
return retv;
}
int main( int argc, char *argv[] )
{
/* default debugfs path for nouveau driver */
static const char def_path[] = "/sys/kernel/debug/dri/%u/pstate";
config_t cfg;
int retv;
/* init config */
memset(&cfg, 0, sizeof(config_t) );
cfg.card = -1;
cfg.pstate = -1;
retv = get_cmdline_opts(argc, argv, &cfg);
if (0 != retv) {
if (0 > retv) goto exit_err;
goto exit;
}
/* If card number is given, use the default debugfs path */
if (cfg.card >= 0) {
snprintf(cfg.path, PATH_MAX, def_path, cfg.card);
retv = write_pstate(&cfg);
return retv;
}
exit_err:
ERR_PRINT( "[E!] Incorrect and/or missing parameters.\n%s", HLP_UINFO );
exit:
if (cfg.verbose != 0) cfg_print(&cfg);
return retv;
}