summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthew Bauer <mjbauer95@gmail.com>2018-10-03 22:56:53 -0500
committerGitHub <noreply@github.com>2018-10-03 22:56:53 -0500
commit1ffe83caa7c521366780233f02c1ae092a20a782 (patch)
tree7e44c6431dada15cff84c2ae5b677802c55aac9e
parent06961cccd08bf99fe757ad7162086e37b5b28bda (diff)
parentf26153754a1b6ac0d72adde9c75e1473463b4dbb (diff)
downloadnixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar.gz
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar.bz2
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar.lz
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar.xz
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.tar.zst
nixlib-1ffe83caa7c521366780233f02c1ae092a20a782.zip
Merge pull request #42846 from ambrop72/optimus-prime-config-master
nixos/xserver: Implement configuration of NVIDIA Optimus via PRIME
-rw-r--r--nixos/modules/hardware/video/nvidia.nix120
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix11
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix12
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix6
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix10
-rw-r--r--pkgs/desktops/gnome-3/core/gdm/default.nix16
-rw-r--r--pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_forward-vars.patch31
-rw-r--r--pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_xserver-path.patch17
-rw-r--r--pkgs/desktops/gnome-3/core/gdm/gdm-x-session_session-wrapper.patch40
10 files changed, 244 insertions, 23 deletions
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index eb1952280331..6944d1a4f76b 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -26,9 +26,73 @@ 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 {
+      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 = [
@@ -36,16 +100,62 @@ in
         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";
     };
@@ -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 =
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 357fa8ce8f36..26b79730dd38 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -222,6 +222,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 = "";
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index a16cbee3bb39..6cc30b218f4a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -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
 
 {
@@ -112,6 +119,11 @@ in
           GDM_SESSIONS_DIR = "${cfg.session.desktops}/share/xsessions";
           # 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";
       };
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index a34f2370649f..16f1ddea1a75 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -62,6 +62,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}
     '';
 
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 2a9826177737..522a0dc92d6f 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -20,6 +20,7 @@ let
   Xsetup = pkgs.writeScript "Xsetup" ''
     #!/bin/sh
     ${cfg.setupScript}
+    ${dmcfg.setupCommands}
   '';
 
   Xstop = pkgs.writeScript "Xstop" ''
@@ -137,7 +138,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>.
         '';
       };
 
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 75bfeaac1fa3..297e36311656 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -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 = [
@@ -754,6 +760,7 @@ in
             Driver "${driver.driverName or driver.name}"
             ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
             ${cfg.deviceSection}
+            ${driver.deviceSection or ""}
             ${xrandrDeviceSection}
           EndSection
 
@@ -765,6 +772,7 @@ in
             ''}
 
             ${cfg.screenSection}
+            ${driver.screenSection or ""}
 
             ${optionalString (cfg.defaultDepth != 0) ''
               DefaultDepth ${toString cfg.defaultDepth}
@@ -794,6 +802,8 @@ in
         '')}
 
         ${xrandrMonitorSections}
+
+        ${cfg.extraConfig}
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
diff --git a/pkgs/desktops/gnome-3/core/gdm/default.nix b/pkgs/desktops/gnome-3/core/gdm/default.nix
index 6c810eb46342..f6049c8bda8c 100644
--- a/pkgs/desktops/gnome-3/core/gdm/default.nix
+++ b/pkgs/desktops/gnome-3/core/gdm/default.nix
@@ -37,13 +37,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 = [
diff --git a/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_forward-vars.patch b/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_forward-vars.patch
new file mode 100644
index 000000000000..401b6aea0c28
--- /dev/null
+++ b/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_forward-vars.patch
@@ -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,
diff --git a/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_xserver-path.patch b/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_xserver-path.patch
deleted file mode 100644
index d020752fef3a..000000000000
--- a/pkgs/desktops/gnome-3/core/gdm/gdm-session-worker_xserver-path.patch
+++ /dev/null
@@ -1,17 +0,0 @@
-diff --git a/daemon/gdm-session-worker.c.orig b/daemon/gdm-session-worker.c
-index 7bbda49..592691d 100644
---- a/daemon/gdm-session-worker.c.orig
-+++ b/daemon/gdm-session-worker.c
-@@ -1557,6 +1557,12 @@ gdm_session_worker_accredit_user (GdmSessionWorker  *worker,
-                 goto out;
-         }
- 
-+        if (g_getenv ("GDM_X_SERVER_EXTRA_ARGS") != NULL) {
-+                g_debug ("forwarding GDM_X_SERVER_EXTRA_ARGS= %s", g_getenv("GDM_X_SERVER_EXTRA_ARGS"));
-+                gdm_session_worker_set_environment_variable (worker, "GDM_X_SERVER_EXTRA_ARGS",
-+                                                             g_getenv("GDM_X_SERVER_EXTRA_ARGS"));
-+        }
-+
-         gdm_session_worker_update_environment_from_passwd_info (worker,
-                                                                 uid,
-                                                                 gid,
diff --git a/pkgs/desktops/gnome-3/core/gdm/gdm-x-session_session-wrapper.patch b/pkgs/desktops/gnome-3/core/gdm/gdm-x-session_session-wrapper.patch
new file mode 100644
index 000000000000..58481f0730fa
--- /dev/null
+++ b/pkgs/desktops/gnome-3/core/gdm/gdm-x-session_session-wrapper.patch
@@ -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);