diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce9a9ce --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.PHONY: all clean permission + +all: ${CURDIR}/nvidia-xrun-util + +$(CURDIR)/nvidia-xrun-util: $(CURDIR)/nvidia-xrun-util.c + gcc -g -O0 ${CURDIR}/nvidia-xrun-util.c -lm -o ${CURDIR}/nvidia-xrun-util + +permission: $(CURDIR)/nvidia-xrun-util + sudo chown root:root ${CURDIR}/nvidia-xrun-util && sudo chmod gu+s ${CURDIR}/nvidia-xrun-util + +clean: + -rm ${CURDIR}/nvidia-xrun-util diff --git a/README.md b/README.md index 481fd40..30467ee 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,22 @@ +**IMPORTANT: Please do not use anymore. Nvida has released for some years new proprietary drivers that works well. So this project is not usefull anymore.** +That's why it has been archived. + # nvidia-xrun These utility scripts aim to make the life easier for nvidia cards users. It started with a revelation that bumblebee in current state offers very poor performance. This solution offers a bit more complicated procedure but offers a full GPU utilization(in terms of linux drivers) ## Usage: 1. switch to free tty - 1. login - 1. run `nvidia-xrun [app]` - 1. enjoy + 2. login + 3. run `nvidia-xrun [return tty] [app]` + 4. enjoy -Currently sudo is required as the script needs to wake up GPU, modprobe the nvidia driver and perform cleanup afterwards. +## Usage from existing X session: + 1. open a terminal emulator (as Xterm) + 2. run `nvidia-xrun-util start_from_X` you can specify an app with `--exec="[app]"` or the tty number to switch before the nvidia X session finished with `--actualVt=[return tty]` (by default, it switch back to the actual tty, before the script was run) + 3. enjoy + + **This version needs no sudo right for the current user** The systemd service can be used to completely remove the card from the kernel device tree (so that it won't even show in `lspci` output), and this will @@ -25,17 +33,24 @@ When the nvidia-xrun command is used, the device is added again to the tree so t ## Structure * **nvidia-xrun** - uses following dir structure: -* **/usr/bin/nvidia-xrun** - the executable script +* **/usr/bin/nvidia-xrun** - the executable bash script +* **/usr/bin/nvidia-xrun-util** - the executable binary contains all admin commands * **/etc/X11/nvidia-xorg.conf** - the main X confing file * **/etc/X11/xinit/nvidia-xinitrc** - xinitrc config file. Contains the setting of provider output source * **/etc/X11/xinit/nvidia-xinitrc.d** - custom xinitrc scripts directory * **/etc/X11/nvidia-xorg.conf.d** - custom X config directory * **/etc/systemd/system/nvidia-xrun-pm.service** systemd service * **/etc/default/nvidia-xrun** - nvidia-xrun config file -* **/usr/share/xsessions/nvidia-xrun-openbox.desktop** - xsession file for openbox -* **/usr/share/xsessions/nvidia-xrun-plasma.desktop** - xsession file for plasma +* **[OPTIONAL] /usr/share/xsession/nvidia-gnome.desktop** - gnome-session entry using nvidia-xrun in the gdm login manager * **[OPTIONAL] $XDG_CONFIG_HOME/X11/nvidia-xinitrc** - user-level custom xinit script file. You can put here your favourite window manager for example +## Modifications in this repository +(I'm sorry for my bad english, I'm a french student) + This repository is a fork of the tangxinfa repository (branch "fix-no-sudo") who permit to use nvidia-xrun without sudo rights by separating all sudo commands in the binary "nidia-xrun-util" (run with setuid root). + I've modified the binary to start "nvidia-xrun" in a new user session in a new tty using **systemd-run** (https://unix.stackexchange.com/questions/554592/how-to-manually-run-init-start-a-xorg-server-on-a-different-vt-tty/554603#554603). + When the `nvidia-xrun-util start_from_X` command start, it wait one second before switch to the tty8 (to prevent swithing back to the tty1 at first session ending in gdm). After, it will run the nvidia-xrun command in the tty8 as user. When the session finished, it switch back to the previous tty or the tty specified by the user (`--actualVt=[tty number]`). + For the time, you cannot modify the tty opened by the `nvidia-xrun-util start_from_X` because it is hard coded. That means if you run the command twice, it will wait before the first nvidia X ending before starting a new one. (the classic `nvidia-xrun` command is no affected because it run in the current tty) + **To make possible switching back to the previous tty, the nvidia-xrun command has been changed! You must specify the tty number before the app to execute like this `nvidia-xrun 1 xterm`** ## Setting the right bus id Usually the 1:0:0 bus is correct. If this is not your case(you can find out through lspci or bbswitch output mesages) you can create @@ -88,18 +103,20 @@ With this you do not need to specify the app and you can simply run: nvidia-xrun -## AUR Package -The Arch Linux User Repository package can be found [here](https://aur.archlinux.org/packages/nvidia-xrun/). - -## COPR Repository for Enterprise Linux, Fedora, Mageia, and openSUSE -The RPM packages and repository details for all supported distributions can be found on the [ekultails/nvidia-xrun](https://copr.fedorainfracloud.org/coprs/ekultails/nvidia-xrun/) COPR overview page. - -### Install (Enterprise Linux and Fedora) - +## Run graphically from gdm + 1. For convenience you can create `sudo nano /usr/share/xsession/[your session].desktop` and put there your favourite window manager: ``` -sudo dnf copr enable ekultails/nvidia-xrun -sudo dnf install nvidia-xrun + [Desktop Entry] +Encoding=UTF-8 +Name=[the name in the gdm session list] +Comment=[comment in the gdm session list] +Type=Application +Exec=/usr/bin/nvidia-xrun-util start_from_X --actualVt=1 --exec="[put it your session script]" ``` + 2. Restart gdm `sudo systemctl restart gdm` + 3. Now, you will be able to select your new nvidia-xrun session in the gdm list when the computer start. + +In fact, gdm will spawn a new X server who run the `nvidia-xrun-util start_from_X` command and stop. (that make computer switch back to tty1, however, because the nvidia-xrun-util process wait 1 second before starting, the computer will just after switch to the tty8). The argument `--actualVt=1` make the script switch back the tty1 (who contains the gdm session manager) instead of the tty where the script was started. ## Troubleshooting ### Steam issues @@ -127,3 +144,21 @@ In that case, you should add `--ignore-install` to `modprobe` calls in `nvidia-x Check https://wiki.archlinux.org/index.php/Vulkan * remove package vulkan-intel * set VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json + +### Xorg cannot start in Debian +You should comment all "files" section in /etc/X11/nvidia-xorg.conf like this: +``` +#Section "Files" +# ModulePath "/usr/lib/nvidia" +# ModulePath "/usr/lib32/nvidia" +# ModulePath "/usr/lib32/nvidia/xorg/modules" +# ModulePath "/usr/lib32/xorg/modules" +# ModulePath "/usr/lib64/nvidia/xorg/modules" +# ModulePath "/usr/lib64/nvidia/xorg" +# ModulePath "/usr/lib64/xorg/modules" +#EndSection +``` + +### cannot unload "nvidia-drm" before nvidia-xrun +I don't know why, in my debian loading "nvidia_drm modeset=1" cause nvidia_drm cannot be unloaded without kill all X server (even intel graphic X server). More if the script try to remove the nvidia card at this moment, it cause a kernel bug who cause shutdown infinite loop (you must make a forced outage) and you will not be able to kill the "nvidia-xrun-util turn_off_gpu" process. +I must replace **"nvidia_drm modeset=1"** by **nvidia_drm** in /etc/default/nvidia-xrun diff --git a/launchers/nvidia-gnome.desktop b/launchers/nvidia-gnome.desktop new file mode 100644 index 0000000..1bf34d9 --- /dev/null +++ b/launchers/nvidia-gnome.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=GNOME (nvidia-xrun) +Comment=Log in Gnome X11 (running with nvidia-xrun) +Type=Application +Exec=/usr/bin/nvidia-xrun-util start_from_X --actualVt=1 --exec=gnome-session diff --git a/nvidia-xrun b/nvidia-xrun index 56d9855..b109552 100755 --- a/nvidia-xrun +++ b/nvidia-xrun @@ -1,4 +1,5 @@ #!/usr/bin/env bash +CHVT=$1; shift DRY_RUN=0 function printHelp { @@ -18,51 +19,30 @@ function execute { fi } -function turn_off_gpu { - if [[ "$REMOVE_DEVICE" == '1' ]]; then - echo 'Removing Nvidia bus from the kernel' - execute "sudo tee /sys/bus/pci/devices/${DEVICE_BUS_ID}/remove <<<1" - else - echo 'Enabling powersave for the graphic card' - execute "sudo tee /sys/bus/pci/devices/${DEVICE_BUS_ID}/power/control << +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +bool dry_run = false; + +void trim_pair(char *str, int (*begin_match)(int c), int (*end_match)(int c)) +{ + char *begin = str; + char *end; + + if (str == NULL) + { + return; + } + + while (begin_match(*begin)) + { + begin++; + } + + if (*begin == '\0') + { + str[0] = '\0'; + return; + } + + end = begin + strlen(begin) - 1; + while (end > begin && end_match(*end)) + { + end--; + } + + end = end + 1; + *end = '\0'; + + memmove(str, begin, end - begin + 1); +} + +int is_quote(int c) { return c == '"'; } + +int is_array_begin(int c) { return c == '('; } + +int is_array_end(int c) { return c == ')'; } + +void trim_space(char *str) { trim_pair(str, &isspace, &isspace); } +void trim_quote(char *str) { trim_pair(str, &is_quote, &is_quote); } +void trim_array(char *str) { trim_pair(str, &is_array_begin, &is_array_end); } + +void parse_values(const char *data, char *values[], int count) +{ + int index = 0; + for (const char *end, *begin = data; *begin; begin = end) + { + // Skip leading spaces. + while (isspace(*begin)) + { + ++begin; + } + + if (is_quote(*begin)) + { + ++begin; + end = begin; + while (*end) + { + if (is_quote(*end)) + { + break; + } + ++end; + } + if (index >= count) + { + fprintf(stderr, "warn: values(%s) exceeded max count(%d)\n", data, + count); + return; + } + values[index] = strndup(begin, end - begin); + ++index; + if (*end) + { + ++end; + } + } + else + { + end = begin; + while (*end) + { + if (isspace(*end)) + { + break; + } + ++end; + } + if (index >= count) + { + fprintf(stderr, "warn: values(%s) exceeded max count(%d)\n", data, + count); + return; + } + values[index] = strndup(begin, end - begin); + ++index; + } + } +} + +void free_values(char *values[], int count) +{ + for (int i = 0; i < count; ++i) + { + if (values[i]) + { + free(values[i]); + values[i] = NULL; + } + } +} + +typedef struct +{ + bool enable_pm; + bool remove_device; + char device_bus_id[64]; + char controller_bus_id[64]; + int bus_rescan_wait_sec; + char *modules_load[64]; + char *modules_unload[64]; +} Conf; + +bool conf_load(Conf *conf, const char *file) +{ + FILE *f = fopen(file, "r"); + if (f == NULL) + { + fprintf(stderr, "open config file %s failed: %d\n", file, errno); + return false; + } + char line[1024] = {'\0'}; + while (fgets(line, sizeof(line), f)) + { + trim_space(line); + if (line[0] == '#') + { + continue; // Skip comment line + } + char *value = strchr(line, '='); + if (value == NULL) + { + continue; + } + *value = '\0'; + ++value; + trim_space(line); + trim_space(value); + if (strcmp(line, "ENABLE_PM") == 0) + { + trim_quote(value); + conf->enable_pm = (value[0] == '1'); + } + else if (strcmp(line, "REMOVE_DEVICE") == 0) + { + trim_quote(value); + conf->remove_device = (value[0] == '1'); + } + else if (strcmp(line, "CONTROLLER_BUS_ID") == 0) + { + trim_quote(value); + snprintf(conf->controller_bus_id, sizeof(conf->controller_bus_id), "%s", + value); + } + else if (strcmp(line, "DEVICE_BUS_ID") == 0) + { + trim_quote(value); + snprintf(conf->device_bus_id, sizeof(conf->device_bus_id), "%s", value); + } + else if (strcmp(line, "BUS_RESCAN_WAIT_SEC") == 0) + { + trim_quote(value); + conf->bus_rescan_wait_sec = atoi(value); + } + else if (strcmp(line, "MODULES_LOAD") == 0) + { + trim_array(value); + parse_values(value, conf->modules_load, + sizeof(conf->modules_load) / sizeof(conf->modules_load[0])); + } + else if (strcmp(line, "MODULES_UNLOAD") == 0) + { + trim_array(value); + parse_values(value, conf->modules_unload, + sizeof(conf->modules_unload) / + sizeof(conf->modules_unload[0])); + } + else + { + fprintf(stderr, "ignore: unknown config item '%s'\n", line); + } + } + fclose(f); + return true; +} + +void conf_free(Conf *conf) +{ + free_values(conf->modules_load, sizeof(conf->modules_load) / sizeof(conf->modules_load[0])); + free_values(conf->modules_unload, sizeof(conf->modules_unload) / sizeof(conf->modules_unload[0])); +} + +void conf_dump(const Conf *conf) +{ + printf("enable_pm\n\t%d\n" + "remove_device\n\t%d\n" + "device_bus_id\n\t%s\n" + "controller_bus_id\n\t%s\n" + "bus_rescan_wait_sec\n\t%d\n", + conf->enable_pm, conf->remove_device, conf->device_bus_id, + conf->controller_bus_id, conf->bus_rescan_wait_sec); + printf("modules_load\n"); + int i; + for (i = 0; i < sizeof(conf->modules_load) / sizeof(conf->modules_load[0]); + ++i) + { + if (conf->modules_load[i]) + { + printf("\t%s\n", conf->modules_load[i]); + } + } + printf("modules_unload\n"); + for (i = 0; + i < sizeof(conf->modules_unload) / sizeof(conf->modules_unload[0]); + ++i) + { + if (conf->modules_unload[i]) + { + printf("\t%s\n", conf->modules_unload[i]); + } + } +} + +void update_file(const char *file, const char *str) +{ + if (dry_run) + { + printf(">>Dry run. Command: echo '%s' > %s\n", str, file); + return; + } + int fd = open(file, O_WRONLY); + if (fd != -1) + { + write(fd, str, strlen(str)); + close(fd); + } +} + +void turn_off_gpu(const Conf *conf) +{ + char file[PATH_MAX] = {'\0'}; + if (conf->remove_device) + { + puts("Removing Nvidia bus from the kernel"); + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s/remove", + conf->device_bus_id); + update_file(file, "1"); + } + else + { + puts("Enabling powersave for the graphic card"); + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s/power/control", + conf->device_bus_id); + update_file(file, "auto"); + } + + puts("Enabling powersave for the PCIe controller"); + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s/power/control", + conf->controller_bus_id); + update_file(file, "auto"); +} + +void turn_on_gpu(const Conf *conf) +{ + puts("Turning the PCIe controller on to allow card rescan"); + char file[PATH_MAX] = {'\0'}; + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s/power/control", + conf->controller_bus_id); + update_file(file, "on"); + + puts("Waiting 1 second"); + if (dry_run) + { + printf(">>Dry run. Command: sleep 1\n"); + } + else + { + sleep(1); + } + + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s", conf->device_bus_id); + struct stat st; + if (stat(file, &st) != 0 || !S_ISDIR(st.st_mode)) + { + puts("Rescanning PCI devices"); + update_file("/sys/bus/pci/rescan", "1"); + printf("Waiting %d second for rescan\n", conf->bus_rescan_wait_sec); + if (dry_run) + { + printf(">>Dry run. Command: sleep %d\n", conf->bus_rescan_wait_sec); + } + else + { + sleep(conf->bus_rescan_wait_sec); + } + } + + puts("Turning the card on"); + snprintf(file, sizeof(file), "/sys/bus/pci/devices/%s/power/control", + conf->device_bus_id); + update_file(file, "on"); +} + +void load_module(const char *module) +{ + printf("Loading module %s\n", module); + + if (dry_run) + { + printf(">>Dry run. Command: modprobe '%s'\n", module); + return; + } + + pid_t pid = fork(); + + if (pid == -1) + { + perror("fork"); + return; + } + + if (pid > 0) + { + waitpid(pid, NULL, 0); + return; + } + + char *args[64] = {NULL}; + args[0] = strdup("env"); + args[1] = strdup("PATH=/usr/sbin:/usr/bin"); + args[2] = strdup("timeout"); + args[3] = strdup("10"); + args[4] = strdup("modprobe"); + parse_values(module, args + 5, sizeof(args) / sizeof(args[0]) - 4); + execvp(args[0], args); + free_values(args, sizeof(args) / sizeof(args[0])); + exit(EXIT_FAILURE); +} + +void unload_module(const char *module) +{ + printf("Unloading module %s\n", module); + if (dry_run) + { + printf(">>Dry run. Command: modprobe -r '%s'\n", module); + return; + } + + pid_t pid = fork(); + + if (pid == -1) + { + perror("fork"); + return; + } + + if (pid > 0) + { + waitpid(pid, NULL, 0); + return; + } + + //execl("/usr/bin/timeout", "/usr/bin/timeout", "10", "/usr/sbin/modprobe", "-r", module, NULL); + execl("env", "env", "PATH=/usr/sbin:/usr/bin", + "timeout", "10", "modprobe", "-r", module, NULL); + + exit(EXIT_FAILURE); +} + +void load_modules(const Conf *conf) +{ + int i; + + for (i = 0; i < sizeof(conf->modules_load) / sizeof(conf->modules_load[0]); + ++i) + { + if (conf->modules_load[i]) + { + load_module(conf->modules_load[i]); + } + } +} + +void unload_modules(const Conf *conf) +{ + int i; + + for (i = 0; + i < sizeof(conf->modules_unload) / sizeof(conf->modules_unload[0]); + ++i) + { + if (conf->modules_unload[i]) + { + unload_module(conf->modules_unload[i]); + } + } +} + +void usage(int argc, char *argv[]) +{ + fprintf(stderr, + "Usage: %s " + " [dry_run]\n", + argv[0]); +} + +int active_tty_number() +{ + long tty = -1; + long fd = 0; + int result = 0; + struct vt_stat ttyinfo; + + fd = open("/dev/tty0", O_RDWR | O_NOCTTY); + if (fd < 0) + { + perror("Error opening /dev/tty0"); + return -1; + } + result = ioctl(fd, VT_GETSTATE, &ttyinfo); + if (result == 0) + { + tty = ttyinfo.v_active; + printf("Active tty: %ld\n", tty); + return tty; + } + else + { + perror("ioctl failed"); + return -1; + } +} + +void start_from_X(int argc, char *argv[]) +{ + int actualVt; + char command[PATH_MAX]; + char exec[PATH_MAX]; + + actualVt = -1; + memset(exec, 0, sizeof(exec)); + for (size_t i = 2; i < argc; i++) + { + if (!strncmp(argv[i], "--actualVt=", 11)) + { + argv[i] += 11; + actualVt = atoi(argv[i]); + printf("actualVt number=%d\n", actualVt); + } + else if (!strncmp(argv[i], "--exec=", 7)) + { + argv[i] += 7; + strncpy(exec, argv[i], PATH_MAX - 1); + printf("exec=%s\n", exec, argv[i]); + } + } + + if (actualVt < 0) + { + if ((actualVt = active_tty_number()) < 0) + exit(EXIT_FAILURE); + } + + snprintf(command, sizeof(command), + "export PATH=\"/home/user/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games\"; sleep 1 && chvt 8 && nvidia-xrun %d %s", + actualVt, exec); + + execl("/usr/bin/systemd-run", "/usr/bin/systemd-run", "--property", "PAMName=login", + "--property", "User=user", + "--property", "StandardInput=tty", + "--property", "TTYPath=/dev/tty8", + "sh", "-c", command, NULL); +} + +int main(int argc, char *argv[]) +{ + Conf conf = {'\0'}; + if (!conf_load(&conf, "/etc/default/nvidia-xrun")) + { + return EXIT_FAILURE; + } + + if (argc > 1 && argc < 5) + { + dry_run = (argc > 2 && strcmp(argv[2], "1") == 0); + if (!dry_run) + { + if (setuid(0) == -1) + { + perror("setuid"); + return EXIT_FAILURE; + } + if (setgid(0) == -1) + { + perror("setgid"); + return EXIT_FAILURE; + } + } + + if (strcmp(argv[1], "turn_off_gpu") == 0) + { + if (conf.enable_pm) + { + turn_off_gpu(&conf); + } + return 0; + } + else if (strcmp(argv[1], "turn_on_gpu") == 0) + { + if (conf.enable_pm) + { + turn_on_gpu(&conf); + } + return 0; + } + else if (strcmp(argv[1], "force_turn_off_gpu") == 0) + { + turn_off_gpu(&conf); + return 0; + } + else if (strcmp(argv[1], "force_turn_on_gpu") == 0) + { + turn_on_gpu(&conf); + return 0; + } + else if (strcmp(argv[1], "load_modules") == 0) + { + load_modules(&conf); + return 0; + } + else if (strcmp(argv[1], "unload_modules") == 0) + { + unload_modules(&conf); + return 0; + } + else if (strcmp(argv[1], "dump_conf") == 0) + { + conf_dump(&conf); + return 0; + } + else if (strcmp(argv[1], "start_from_X") == 0) + { + printf("start nvidia-xrun from existing X server\n"); + start_from_X(argc, argv); + return 0; + } + + usage(argc, argv); + + return EXIT_FAILURE; + } +}