Setup a USB-C Alt Mode dock on linux

Setup a USB-C Alt Mode dock on linux

Introduction

I've recently bought an ASUS Zephyrus M to replace my ZenBook Pro as laptop & my Z820 in the second room, so the idea is to use a docking to instantly connect my screens/keyboard/mouse/lan, i choosed the Kensington SD4500.

The SD4500 is working great out of the box on Windows, but on linux it's an other story.

The problem is, the built-in screen is linked to the iGPU (through a multiplexer) but the USB-C screens are directly linked to the discrete GPU.
Which means, when the laptop is connected to the dock, the nvidia gpu will be always on.

Environment

The setup is made on Archlinux, xf86-video-intel & nvidia driver with bumblebee, gnome3 on xorg.

Installation

First off all, we need to setup xorg to use intel virtual screens

/etc/X11/xorg.conf.d/20-intel.conf
Section "Device"
    Identifier "intelgpu0"
    Driver "intel"
    Option "VirtualHeads" "2"
EndSection

Replace the content of the bumblebee xorg.conf file with this config

/etc/bumblebee/xorg.conf.nvidia
Section "ServerLayout"
    Identifier  "Layout0"
    Option      "AutoAddDevices" "true"
    Option      "AutoAddGPU" "false"
EndSection

Section "Device"
    Identifier  "DiscreteNvidia"
    Driver      "nvidia"
    VendorName  "NVIDIA Corporation"
    Option "ProbeAllGpus" "false"
    Option "NoLogo" "true"
    Option "AllowEmptyInitialConfiguration"
EndSection

Section "Screen"
    Identifier "Screen0"
    Device "DiscreteNVidia"
EndSection

At this state, you can try if everything is working as it should be by manually starting intel-virtual-output as a normal user (considering your user is in the bumblebee group).

intel-virtual-output -f

This command will start a second xorg screen :8 on the discrete gpu with bumblebee and redirect the content to the virtual screens. (Correct me if i'm wrong).

If the previous command works great, we need to configure systemd to automatically start the intel-virtual-output service when we connect the dock.

This involve udev & systemd.

On my setup i've created a udev rule to rename the interface from eth0 to dock0 (better management with NetworkManager in the future and avoid false-positive if we use usb-ethernet adapter in portable mode).

/etc/udev/rules.d/99-docking.rules
# Replace 00:00:00:00:00:00 with your current MAC address
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:00:00:00:00:00",ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="dock0", SYMLINK="dock0", TAG+="systemd"

Check if the udev rule is working by plugging your dock, the interface should appear as dock0

ip a

...
5: dock0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
...

Check if systemd correctly see the interface

systemctl | grep dock0

sys-subsystem-net-devices-dock0.device   loaded active plugged   K38230_03

Create the systemd unit files as a normal user

~/.local/share/systemd/user/docked.target
[Unit]
Description=dock to SD4500
Requisite=sys-subsystem-net-devices-dock0.device
BindsTo=sys-subsystem-net-devices-dock0.device
After=sys-subsystem-net-devices-dock0.device
JobTimeoutSec=3

[Install]
WantedBy=sys-subsystem-net-devices-dock0.device
~/.local/share/systemd/user/intel-virtual-output.service
[Unit]
Description=intel-virtual-output for external displays
Requisite=docked.target
After=docked.target
PartOf=docked.target
Conflicts=sleep.target
Before=sleep.target

[Service]
Type=forking
ExecStartPre=/usr/bin/nmcli radio wifi off
ExecStart=/usr/bin/intel-virtual-output -d :0
ExecStopPost=/usr/bin/nmcli radio wifi on
ExecStop=/usr/bin/sudo /usr/local/sbin/killbumblebee.sh

[Install]
WantedBy=docked.target

As you can see in the intel-virtual-output service, i use Pre & Post extra commands to disable the wifi when the dock is connected.

I created a small script in /usr/local/sbin/killbumblebee.sh to kill the remaining xorg screen (:8) after i disconnect the laptop.

/usr/local/sbin/killbumblebee.sh
#!/bin/bash

export DISPLAY=:0
export XAUTHORITY=/run/user/1000/gdm/Xauthority
export XDG_RUNTIME_DIR="/run/user/1000"
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"

# Kill useless xorg server
PIDOFXORG=$(ps aux | grep xorg.conf.nvidia | grep -v grep | awk '{ print $2 }')
kill $PIDOFXORG

# Disconnect virtual screen (to force gnome going back to one screen only)
xrandr --output VIRTUAL1 --off --output VIRTUAL2 --off --output VIRTUAL3 --off --output VIRTUAL4 --off --output VIRTUAL5 --off

As the script need to be run as root, you'll need to allow this command to be run as root without password

/etc/sudoers.d/dock
ALL ALL=(ALL) NOPASSWD: /usr/local/sbin/killbumblebee.sh

Enable the target & service

systemctl --user daemon-reload
systemctl --user enable docked.target
systemctl --user enable intel-virtual-output.service

Disconnect/Reconnect your dock and your external screens should turn on few seconds after.

Sources

Diego Fernandez's implementation of ThinkPad Mini Dock Plus