Skip to content

Commit

Permalink
nixos/xserver: Implement configuration of NVIDIA Optimus via PRIME
Browse files Browse the repository at this point in the history
This adds configuration options which automate the configuration of NVIDIA Optimus using PRIME. This allows using the NVIDIA proprietary driver on Optimus laptops, in order to render using the NVIDIA GPU while outputting to displays connected only to the integrated Intel GPU. It also adds an option for enabling kernel modesetting for the NVIDIA driver (via a kernel command line flag); this is particularly useful together with Optimus/PRIME because it fixes tearing on PRIME-connected screens.

The user still needs to enable the Optimus/PRIME feature and specify the bus IDs of the Intel and NVIDIA GPUs, but this is still much easier for users and more reliable. The implementation handles both the X configuration file as well as getting display managers to run certain necessary `xrandr` commands just after X has started.

Configuration of commands run after X startup is done using a new configuration option `services.xserver.displayManager.setupCommands`. Support for this option is implemented for LightDM, GDM and SDDM; all of these have been tested with this feature including logging into a Plasma session.

Note: support of `setupCommands` for GDM is implemented by making GDM run the session executable via a wrapper; the wrapper will run the `setupCommands` before execing. This seemed like the simplest and most reliable approach, and solves running these commands both for GDM's X server and user X servers (GDM starts separate X servers for itself and user sessions). An alternative approach would be with autostart files but that seems harder to set up and less reliable.

Note that some simple features for X configuration file generation (in `xserver.nix`) are added which are used in the implementation:
- `services.xserver.extraConfig`: Allows adding arbitrary new sections. This is used to add the Device section for the Intel GPU.
- `deviceSection` and `screenSection` within `services.xserver.drivers`. This allows the nvidia configuration module to add additional contents into the `Device` and `Screen` sections of the "nvidia" driver, and not into such sections for other drivers that may be enabled.
  • Loading branch information
ambrop72 authored and Rhys Adams committed Jul 9, 2018
1 parent 91b286c commit 46d5b2a
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 26 deletions.
119 changes: 115 additions & 4 deletions nixos/modules/hardware/video/nvidia.nix
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,135 @@ let
};

enabled = nvidia_x11 != null;

cfg = config.hardware.nvidia;
optimusCfg = cfg.optimus_prime;
in

{
options = {
hardware.nvidia.modesetting.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable kernel modesetting when using the NVIDIA proprietary driver.
Enabling this fixes screen tearing when using Optimus via PRIME (see
<option>hardware.nvidia.optimus_prime.enable</option>. This is not enabled
by default because it is not officially supported by NVIDIA and would not
work with SLI.
'';
};

hardware.nvidia.optimus_prime.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
This results in the NVIDIA GPU being used for rendering while outputting to
displays attached to the integrated Intel GPU.
Note that this option only has any effect if the "nvidia" driver is specified
in <option>services.xserver.videoDrivers</option>, and it should preferably
be the only driver there.
If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
specified (<option>hardware.nvidia.optimus_prime.nvidiaBusId</option> and
<option>hardware.nvidia.optimus_prime.intelBusId</option>).
If you enable this, you may want to also enable kernel modesetting for the
NVIDIA driver (<option>hardware.nvidia.modesetting.enable</option>) in order
to prevent tearing.
Note that this configuration will only be successful when a display manager
for which the <option>services.xserver.displayManager.setupCommands</option>
option is supported is used; notably, SLiM is not supported.
'';
};

hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
type = lib.types.string;
default = "";
example = "PCI:1:0:0";
description = ''
Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci
shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0".
'';
};

hardware.nvidia.optimus_prime.intelBusId = lib.mkOption {
type = lib.types.string;
default = "";
example = "PCI:0:2:0";
description = ''
Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci
shows the NVIDIA GPU at "00:02.0", set this option to "PCI:0:2:0".
'';
};
};

config = mkIf enabled {
assertions = [
{
assertion = config.services.xserver.displayManager.gdm.wayland;
message = "NVidia drivers don't support wayland";
}
{
assertion = !optimusCfg.enable ||
(optimusCfg.nvidiaBusId != "" && optimusCfg.intelBusId != "");
message = ''
When NVIDIA Optimus via PRIME is enabled, the GPU bus IDs must configured.
'';
}
];

services.xserver.drivers = singleton
{ name = "nvidia"; modules = [ nvidia_x11.bin ]; libPath = [ nvidia_x11 ]; };
# If Optimus/PRIME is enabled, we:
# - Specify the configured NVIDIA GPU bus ID in the Device section for the
# "nvidia" driver.
# - Add the AllowEmptyInitialConfiguration option to the Screen section for the
# "nvidia" driver, in order to allow the X server to start without any outputs.
# - Add a separate Device section for the Intel GPU, using the "modesetting"
# driver and with the configured BusID.
# - Reference that Device section from the ServerLayout section as an inactive
# device.
# - Configure the display manager to run specific `xrandr` commands which will
# configure/enable displays connected to the Intel GPU.

services.xserver.drivers = singleton {
name = "nvidia";
modules = [ nvidia_x11.bin ];
libPath = [ nvidia_x11 ];
deviceSection = optionalString optimusCfg.enable
''
BusID "${optimusCfg.nvidiaBusId}"
'';
screenSection =
''
Option "RandRRotation" "on"
${optionalString optimusCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
'';
};

services.xserver.screenSection =
services.xserver.extraConfig = optionalString optimusCfg.enable
''
Section "Device"
Identifier "nvidia-optimus-intel"
Driver "modesetting"
BusID "${optimusCfg.intelBusId}"
Option "AccelMethod" "none"
EndSection
'';
services.xserver.serverLayoutSection = optionalString optimusCfg.enable
''
Option "RandRRotation" "on"
Inactive "nvidia-optimus-intel"
'';

services.xserver.displayManager.setupCommands = optionalString optimusCfg.enable ''
# Added by nvidia configuration module for Optimus/PRIME.
${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource modesetting NVIDIA-0
${pkgs.xorg.xrandr}/bin/xrandr --auto
'';

environment.etc."nvidia/nvidia-application-profiles-rc" = mkIf nvidia_x11.useProfiles {
source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";
};
Expand All @@ -69,6 +178,8 @@ in
boot.kernelModules = [ "nvidia-uvm" ] ++
lib.optionals config.services.xserver.enable [ "nvidia" "nvidia_modeset" "nvidia_drm" ];

# If requested enable modesetting via kernel parameter.
boot.kernelParams = optional cfg.modesetting.enable "nvidia-drm.modeset=1";

# Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
services.udev.extraRules =
Expand Down
11 changes: 11 additions & 0 deletions nixos/modules/services/x11/display-managers/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ in
description = "List of arguments for the X server.";
};

setupCommands = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed just after the X server has started.
This option is only effective for display managers for which this feature
is supported; currently these are LightDM, GDM and SDDM.
'';
};

sessionCommands = mkOption {
type = types.lines;
default = "";
Expand Down
12 changes: 12 additions & 0 deletions nixos/modules/services/x11/display-managers/gdm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ let
cfg = config.services.xserver.displayManager;
gdm = pkgs.gnome3.gdm;

xSessionWrapper = if (cfg.setupCommands == "") then null else
pkgs.writeScript "gdm-x-session-wrapper" ''
#!${pkgs.bash}/bin/bash
${cfg.setupCommands}
exec "$@"
'';

in

{
Expand Down Expand Up @@ -112,6 +119,11 @@ in
GDM_SESSIONS_DIR = "${cfg.session.desktops}";
# Find the mouse
XCURSOR_PATH = "~/.icons:${pkgs.gnome3.adwaita-icon-theme}/share/icons";
} // optionalAttrs (xSessionWrapper != null) {
# Make GDM use this wrapper before running the session, which runs the
# configured setupCommands. This relies on a patched GDM which supports
# this environment variable.
GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
};
execCmd = "exec ${gdm}/bin/gdm";
};
Expand Down
6 changes: 6 additions & 0 deletions nixos/modules/services/x11/display-managers/lightdm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ let
${optionalString hasDefaultUserSession ''
user-session=${defaultSessionName}
''}
${optionalString (dmcfg.setupCommands != "") ''
display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
#!${pkgs.bash}/bin/bash
${dmcfg.setupCommands}
''}
''}
${cfg.extraSeatDefaults}
'';

Expand Down
4 changes: 3 additions & 1 deletion nixos/modules/services/x11/display-managers/sddm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ let
rm -fr /var/lib/sddm/.cache/sddm-greeter/qmlcache
${cfg.setupScript}
${dmcfg.setupCommands}
'';

Xstop = pkgs.writeScript "Xstop" ''
Expand Down Expand Up @@ -133,7 +134,8 @@ in
xrandr --auto
'';
description = ''
A script to execute when starting the display server.
A script to execute when starting the display server. DEPRECATED, please
use <option>services.xserver.displayManager.setupCommands</option>.
'';
};

Expand Down
10 changes: 10 additions & 0 deletions nixos/modules/services/x11/xserver.nix
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ in
description = "Contents of the first Monitor section of the X server configuration file.";
};

extraConfig = mkOption {
type = types.lines;
default = "";
description = "Additional contents (sections) included in the X server configuration file";
};

xrandrHeads = mkOption {
default = [];
example = [
Expand Down Expand Up @@ -733,6 +739,7 @@ in
Driver "${driver.driverName or driver.name}"
${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
${cfg.deviceSection}
${driver.deviceSection or ""}
${xrandrDeviceSection}
EndSection
Expand All @@ -744,6 +751,7 @@ in
''}
${cfg.screenSection}
${driver.screenSection or ""}
${optionalString (cfg.defaultDepth != 0) ''
DefaultDepth ${toString cfg.defaultDepth}
Expand Down Expand Up @@ -773,6 +781,8 @@ in
'')}
${xrandrMonitorSections}
${cfg.extraConfig}
'';

fonts.enableDefaultFonts = mkDefault true;
Expand Down
27 changes: 23 additions & 4 deletions pkgs/desktops/gnome-3/core/gdm/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,29 @@ stdenv.mkDerivation rec {
enableParallelBuilding = true;

# Disable Access Control because our X does not support FamilyServerInterpreted yet
patches = [ ./sessions_dir.patch
./gdm-x-session_extra_args.patch
./gdm-session-worker_xserver-path.patch
];
patches = [
# Change hardcoded paths to nix store paths.
(substituteAll {
src = ./fix-paths.patch;
inherit coreutils plymouth xwayland;
})

# The following patches implement certain environment variables in GDM which are set by
# the gdm configuration module (nixos/modules/services/x11/display-managers/gdm.nix).

# Look for session definition files in the directory specified by GDM_SESSIONS_DIR.
./sessions_dir.patch

# Allow specifying X server arguments with GDM_X_SERVER_EXTRA_ARGS.
./gdm-x-session_extra_args.patch

# Allow specifying a wrapper for running the session command.
./gdm-x-session_session-wrapper.patch

# Forwards certain environment variables to the gdm-x-session child process
# to ensure that the above two patches actually work.
./gdm-session-worker_forward-vars.patch
];

postInstall = ''
# Prevent “Could not parse desktop file orca-autostart.desktop or it references a not found TryExec binary”
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
diff --git a/daemon/gdm-session-worker.c b/daemon/gdm-session-worker.c
index 9ef4c5b..94da834 100644
--- a/daemon/gdm-session-worker.c
+++ b/daemon/gdm-session-worker.c
@@ -1515,6 +1515,16 @@ gdm_session_worker_load_env_d (GdmSessionWorker *worker)
g_object_unref (dir);
}

+static void
+gdm_session_worker_forward_var (GdmSessionWorker *worker, char const *var)
+{
+ char const *value = g_getenv(var);
+ if (value != NULL) {
+ g_debug ("forwarding %s= %s", var, value);
+ gdm_session_worker_set_environment_variable(worker, var, value);
+ }
+}
+
static gboolean
gdm_session_worker_accredit_user (GdmSessionWorker *worker,
GError **error)
@@ -1559,6 +1569,9 @@ gdm_session_worker_accredit_user (GdmSessionWorker *worker,
goto out;
}

+ gdm_session_worker_forward_var(worker, "GDM_X_SERVER_EXTRA_ARGS");
+ gdm_session_worker_forward_var(worker, "GDM_X_SESSION_WRAPPER");
+
gdm_session_worker_update_environment_from_passwd_info (worker,
uid,
gid,

This file was deleted.

40 changes: 40 additions & 0 deletions pkgs/desktops/gnome-3/core/gdm/gdm-x-session_session-wrapper.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
diff --git a/daemon/gdm-x-session.c b/daemon/gdm-x-session.c
index 88fe96f..b1b140a 100644
--- a/daemon/gdm-x-session.c
+++ b/daemon/gdm-x-session.c
@@ -664,18 +664,34 @@ spawn_session (State *state,
state->session_command,
NULL);
} else {
+ char const *session_wrapper;
+ char *eff_session_command;
int ret;
char **argv;

- ret = g_shell_parse_argv (state->session_command,
+ session_wrapper = g_getenv("GDM_X_SESSION_WRAPPER");
+ if (session_wrapper != NULL) {
+ char *quoted_wrapper = g_shell_quote(session_wrapper);
+ eff_session_command = g_strjoin(" ", quoted_wrapper, state->session_command, NULL);
+ g_free(quoted_wrapper);
+ } else {
+ eff_session_command = state->session_command;
+ }
+
+ ret = g_shell_parse_argv (eff_session_command,
NULL,
&argv,
&error);

+ if (session_wrapper != NULL) {
+ g_free(eff_session_command);
+ }
+
if (!ret) {
g_debug ("could not parse session arguments: %s", error->message);
goto out;
}
+
subprocess = g_subprocess_launcher_spawnv (launcher,
(const char * const *) argv,
&error);

0 comments on commit 46d5b2a

Please sign in to comment.