HOWTO: Nouveau: automatic changing of pstates

Share your own howto's etc. Not for support questions!

HOWTO: Nouveau: automatic changing of pstates

Postby LE_746F6D617A7A69 » 2020-05-04 09:26

EDIT: Added new perf results and an example of systemd service used to enable powersave mode on system startup. /EDID

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 "performance" pstate is the one with the highest GPU/memory clock
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

07: core 405 MHz memory 810 MHz <- powersave pstate (stable)
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

a) change the pstate to one of the performance modes
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


Install (copy) the executable to /usr/local/bin/nv_pstate

Set the suid bit: (allow running with root privileges, without asking for password)
Code: Select all
chmod u+s /usr/local/bin/nv_pstate

Step 3:
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

Step 4:
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

Then run:
Code: Select all
>systemctl daemon-reload
>systemctl enable nv_pstate.service


Regards.

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;
}
Bill Gates: "(...) In my case, I went to the garbage cans at the Computer Science Center and I fished out listings of their operating system."
The_full_story and Nothing_have_changed
LE_746F6D617A7A69
 
Posts: 414
Joined: 2020-05-03 14:16

Return to Docs, Howtos, Tips & Tricks

Who is online

Users browsing this forum: No registered users and 5 guests

fashionable