Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/xserver: Implement configuration of NVIDIA Optimus via PRIME #42846

Merged
merged 1 commit into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 116 additions & 4 deletions nixos/modules/hardware/video/nvidia.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,136 @@ let
nvidia_libs32 = (nvidiaForKernel pkgs_i686.linuxPackages).override { libsOnly = true; kernel = null; };

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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have hardware.nvidiaOptimus (currently only to disable optimus card). Perhaps you could combine namespaces into something like hardware.nvidia.optimus?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bumblebee might be worth integrating there too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this, I put the three options into the hardware.nvidia.optimus_prime section because one enables the Optimus/PRIME feature and the other two define required parameters for using it (bus IDs). I wouldn't want to have this and the existing options like hardware.nvidiaOptimus.disable (which I not familiar with) and hardware.bumblebee all thrown in the same place. Maybe these two could be moved to somewhere under hardware.nvidia since they are both NVidia related? Possibility: hardware.nvidia.disableOptimus, hardware.nvidia.bumblebee (also: Bumblebee is not a piece of hardware).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though we might indeed have an optimus subsection, ending up with:

  • hardware.nvidia.optimus.prime
  • hardware.nvidia.optimus.bumblebee
  • hardware.nvidia.optimus.disable (but maybe there is a better name for this)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for hardware.nvidia.optimus.prime. And then eventually move hardware.nvidiaOptimus.disable to hardware.nvidia.optimus.disable, and hardware.bumblebee to hardware.nvidia.bumblebee. We can wait on the latter part though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at https://wiki.archlinux.org/index.php/NVIDIA_Optimus it appears there is another option where you use the proprietary NVIDIA drivers.

type = lib.types.bool;
default = false;
description = ''
Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
If enabled, the NVIDIA GPU will be always on and used for all rendering,
while enabling output to displays attached only to the integrated Intel GPU
without a multiplexer.

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 Intel GPU. You can find it using lspci; for example if lspci
shows the Intel 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 @@ -62,6 +172,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 @@ -148,7 +149,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 @@ -374,6 +374,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 @@ -741,6 +747,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 @@ -752,6 +759,7 @@ in
''}

${cfg.screenSection}
${driver.screenSection or ""}

${optionalString (cfg.defaultDepth != 0) ''
DefaultDepth ${toString cfg.defaultDepth}
Expand Down Expand Up @@ -781,6 +789,8 @@ in
'')}

${xrandrMonitorSections}

${cfg.extraConfig}
'';

fonts.enableDefaultFonts = mkDefault true;
Expand Down
16 changes: 15 additions & 1 deletion pkgs/desktops/gnome-3/core/gdm/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,27 @@ stdenv.mkDerivation rec {

# Disable Access Control because our X does not support FamilyServerInterpreted yet
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
./gdm-session-worker_xserver-path.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
];

installFlags = [
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);