the perspicacious ramblings of droo

Triple Head with nVidia Optimus on Fedora 21 (includes Docking udev Rules for Systemd)

In this post, we’re going to look at how to setup a laptop (Lenovo T530, in this case) to utilize both the nVidia and Intel GPUs to drive a triple head or quadruple head system and to automatically configure those displays at boot, on docking, and undocking.

If you don’t have a Lenovo T530, aren’t using the ThinkPad Mini Dock Plus Series 3 dock, or aren’t running Fedora 21, you’ll need to alter the instructions to fit your needs.  As well, you’ll definitely need to alter the scripts for your display configurations.

Let’s begin!

First, let’s install the KMS and DDX drivers for nVidia.  You can go to nVidia’s website and get the installer, or you can just grab them from RPMFusion.  Personally, I prefer the latter.

To enable the RPMFusion Free and Non-Free yum repository, run the following command:

su -c 'yum localinstall --nogpgcheck \
http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm'

Next, install the nVidia drivers:

su -c 'yum install kmod-nvidia xorg-x11-drv-nvidia xorg-x11-drv-nvidia-libs'

Now, edit your xorg.conf file so it looks like so:

Section "ServerLayout"
        Identifier "layout"
        Screen     0 "nvidia"
        Inactive   "intel"

Section "Device"
        Identifier "nvidia"
        Driver     "nvidia"
        BusID      "PCI:1:0:0"

Section "Screen"
        Identifier "nvidia"
        Device     "nvidia"
        Option     "AllowEmptyInitialConfiguration"
        Option     "ConstrainCursor" "no"

Section "Device"
        Identifier "intel"
        Driver "modesetting"

Section "Screen"
        Identifier "intel"
        Device "intel"

Now for the more complicated part! Scripting the startup.

First, we need to create a script that will run after X initializes. Do note that this initializes after you’ve logged in, so your Display Manager login will only show up on the nVidia outputs. It’s a bit of a caveat that causes issues with logging in while undocked. We’ll discuss those in a minute though.

Create the following file:

# configure monitors using xrandr rather than xorg.conf

AT_HOME=$(xrandr | grep -q 3840x2160)$?
AT_WORK=$(xrandr | grep -q 1920x1080)$?

XRANDR="/usr/bin/xrandr -d :0"

$XRANDR --setprovideroutputsource modesetting NVIDIA-0
sleep 1
$XRANDR --auto
sleep 3

if [ $AT_HOME -eq 0 ]; then
        $XRANDR --output LVDS-1-0 --off --output DP-2 --rotate left --right-of DP-4
elif [ $AT_WORK -eq 0 ]; then
        $XRANDR --output LVDS-1-0 --off --output DP-2 --left-of DP-1 --output VGA-1-0 --left-of DP-2
elif [ $AT_REMOTE -eq 0 ]; then
        $XRANDR --output LVDS-1-0 --auto --output VGA-1-0 --off --output DP-1 --off --output DP-2 --off

I think the script is pretty self explanatory. The AT_HOME/WORK variables are set to 0 or 1 if they find the resolution specified. AT_REMOTE is set based on the previous two. The –setprovideroutputsource tells xrandr that the nVidia GPU is going to do the rendering for the displays connected to the Intel GPU. We sleep to make sure that happens. Now turn all the displays on with `xrandr –auto` and sleep while that updates. Next we configure the setup we want based on where we are.

Set the executable permissions bit on the file:

su -c 'chmod +x /etc/X11/xinit/xinitrc.d/40-xrandr.conf'

Now, if you restart your laptop while docked and login, it should work! If you’re not docked, however, you’ll be staring at a black screen. This is because the xorg configuration is outputting to the outputs connected to the nVidia GPU only (DisplayPort/DVI), so no output is going to the panel or VGA. So, how do we fix this? Well, you can memorize the keystrokes required to login when you’re facing a blank screen… or you can enable auto login. Let’s do the latter!

Assuming you’re using gdm, you’ll want to add the AutomaticLogin and AutomaticLoginEnable options to /etc/gdm/custom.conf so it looks like the following, replacing YOUR_USERNAME_HERE with your username:

# GDM configuration storage







Now we need to tell gdm that it’s A-OK to let you login without a password! You’re going to add a new line at the very top of /etc/pam.d/gdm-password so that it looks like so:

auth        sufficient    pam_succeed_if.so user ingroup nopasswdlogin
auth     [success=done ignore=ignore default=bad] pam_selinux_permit.so
auth        substack      password-auth
auth        optional      pam_gnome_keyring.so

account     required      pam_nologin.so
account     include       password-auth

password    substack       password-auth
-password   optional       pam_gnome_keyring.so use_authtok

session     required      pam_selinux.so close
session     required      pam_loginuid.so
session     optional      pam_console.so
-session    optional    pam_ck_connector.so
session     required      pam_selinux.so open
session     optional      pam_keyinit.so force revoke
session     required      pam_namespace.so
session     include       password-auth
session     optional      pam_gnome_keyring.so auto_start

This tells PAM that authentication is sufficient if the user belongs to the ‘nopasswdlogin’ group. But, that group doesn’t exist! Let’s create it, replacing YOUR_USERNAME_HERE with your username once again:

su -c 'groupadd nopasswdlogin'
su -c 'usermod -a -G nopasswdlogin YOUR_USERNAME_HERE'

Now when gdm loads, it will automatically login with your user! Now that we’re done throwing security out the window, let’s tackle the issue of docking and undocking events.

Systemd is the default in Fedora. If you’re opting to use acpid, you can check out this solution here. For this post, I’m going to assume you’re sticking with the defaults in Fedora. So, Systemd uses udev as its device manager and it has a really nice feature that lets you create rules for various devices you plug in. Let’s create a new rule for our dock:

SUBSYSTEM=="usb", DEVPATH=="/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8", RUN+="/etc/X11/xinit/xinitrc.d/40-xrandr.conf"

Update 2015-03-09: After an update somewhere in the last month or two, my udev rule above quit working properly due to a change in the dev path. As such, I’ve updated it to the following:

SUBSYSTEM=="drm", DEVPATH=="/devices/pci0000:00/0000:00:02.0/drm/card0", RUN+="/usr/local/bin/set_resolution.sh"

The rest of the article is based on the original rule and has not been updated.
// Update 2015-03-09

Now, you’re probably wondering how I came up with this rule. Let’s have a look at that!

Run the following command:

udevadm monitor

Now, undock your laptop and you should see:

KERNEL[8277.917714] remove   /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0 (usb)
KERNEL[8277.918837] remove   /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8 (usb)
UDEV  [8277.950128] remove   /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0 (usb)
KERNEL[8278.281632] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/ACPI0003:00/power_supply/AC (power_supply)
UDEV  [8278.283788] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/ACPI0003:00/power_supply/AC (power_supply)
KERNEL[8278.484895] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/PNP0C0A:00/power_supply/BAT0 (power_supply)
UDEV  [8278.486339] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/PNP0C0A:00/power_supply/BAT0 (power_supply)
UDEV  [8282.027590] remove   /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8 (usb)

Now, dock your laptop and you should see:

KERNEL[8291.748427] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8 (usb)
KERNEL[8291.748496] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0 (usb)
KERNEL[8292.945982] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/ACPI0003:00/power_supply/AC (power_supply)
UDEV  [8292.947872] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/ACPI0003:00/power_supply/AC (power_supply)
KERNEL[8293.047476] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/PNP0C0A:00/power_supply/BAT0 (power_supply)
UDEV  [8293.047953] change   /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:08/PNP0C09:00/PNP0C0A:00/power_supply/BAT0 (power_supply)
UDEV  [8296.184518] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8 (usb)
UDEV  [8296.185398] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0 (usb)

They may be a little out of order depending on when udev gets the signal. So, we see where the usb device path is… so, let’s check that out: (Control+C to quit the last command, if you weren’t aware)

[root@localhost ~]# udevadm info /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0
P: /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0
E: DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.8/2-1.8:1.0
E: DEVTYPE=usb_interface
E: ID_MODEL_FROM_DATABASE=ThinkPad Mini Dock Plus Series 3
E: MODALIAS=usb:v17EFp100Ad0000dc09dsc00dp02ic09isc00ip02in00
E: PRODUCT=17ef/100a/0
E: TYPE=9/0/2

Here we see the SUBSYSTEM and DEVPATH values we used in the udev rule! Keep in mind, not all of these attributes will work as expected. You can also add ‘ACTION=add’ or ‘ACTION=remove’ to the udev rule we created earlier if you want to have one rule for docking and one rule for undocking.

And that’s all there is to it! Hopefully this worked for you. If it didn’t, feel free to comment and I’ll do my best to give you a little guidance.


Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>