After about 3 years it was time to refresh my hardware. Though I’ve long used MacBook Pro’s as my daily drivers the new MBP with touchbar wasn’t getting me excited and the new keyboard feels downright awful to me. So, I decided this was going to be the year of the Linux Desktop and I’ve switched to a Dell XPS 13 (9360, Kaby Lake) Developer Edition (comes pre-loaded with Ubuntu).

Though Ubuntu is entirely fine for servers it tends to get on my nerves as my daily driver since it’s hard to get up to date software. Arch Linux is exactly the opposite, bleeding edge every day.

I decided to risk Arch Linux b/c in general I like the concept, knowing full well I might end up spending some time fighting bugs. I did, a few times over (and I’m still dealing with one) but overall it’s been rather pleasant.

This post is basically going to get you through an Arch Linux installation, with full disk encryption, hibernate and hybrid sleep support.

Warning: The 4.15 kernel series is extremely wonky on this laptop. Don’t know why, and don’t really feel like digging into it now that 4.16 is out and everything works just fine. If you can’t get to 4.16, anything from 4.12 through 4.14 works fine too. Just skip 4.15.


Update 2018-04-21:

  • Remove the intel_iommu=igfx_off setting as we’re now running the 4.16 kernel. It shouldn’t have had any effect even on 4.15, but it was easily observable that it did.

Update 2018-04-16:

  • Change i915 options to enable_guc_loading=-1 enable_guc_submission=-1. RC6 is automatically enabled, PSR doesn’t seem to do much and neither does modeset. However, the kernel still defaults to not turning GuC on. Setting it to -1 puts it in auto mode however, letting the system detect if the firmware is there and load it. For some reason this still causes the kernel to be marked as tainted b/c the option is considered dangerous. Don’t force frame buffer compression, doesn’t seem to do much in the way of power savings and can cause some weird rendering issues.
  • Add intel_iommu=igfx_off to kernel parameters. Without it I can successfully boot a >= 4.15 kernel but the whole system becomes unresponsive once we start a GUI. This disables Intel VT-d for the graphics card. If you’re running into this error you’ll see something along the lines of: DMAR: [INTR-REMAP] Request device [f0:1f.0] fault index 0 [fault reason 37] Blocked a compatibility format interrupt request in the journal. The purpose of Intel IOMMU/VT-d and DMAR is to allow direct assignment of hardware to virtualised guests, more or less PCI passthrough. This option disables it for the graphics card, which is fine since I have no intention of giving it to a guest.

Once you’ve loaded the Arch Linux LiveCD and booted from it you’ll find yourself at a prompt. Graphical installations is not a thing Arch Linux does, so this is where we’ll start.

The prompt should say root@archiso ~ # to indicate you’re at a root prompt. I’ll be shortening that to $ in this guide, because # trips up the syntax highlighting.

First things first, lets ensure we can actually read the console:

$ setfont latarcyrheb-sun32

Now, configure the network. I connected over WiFi, which you can set up by launching wifi-menu.

The hostname of my system is monoceros, aka unicorn. You’ll see this come back in a few places, especially when setting up the volumes in LVM. Swap it out for your own :). Or don’t use the hostname at all but some generic like myvol or archlinux.

Once that’s done, we can start building up to the installation.

Disk partitioning

My layout is as follows:

  • /dev/nvme0n1p1: /boot
  • /dev/nvmen0n1p2: LUKS
    • /dev/mapper/cryptlvm: lvm
      • /dev/mapper/monoceros-swap: swap
      • /dev/mapper/monoceros-root: /
      • /dev/mapper/monoceros-home: /home

This results in a fully encrypted system, aside from the boot partition. This is good enough for my threat model, which is mostly to ensure my data doesn’t land on the street if someone steals my laptop. It’ll give most law enforcement agencies a run for their money too, though if you want to protect from nation state actors you’ll probably need to come up with something more.

I did this with parted:

$ parted /dev/nvme0n1

    (parted) mklabel gpt  # wipes out existing partitioning
    (parted) mkpart ESP fat32 1MiB 513MiB  # create the UEFI boot partition
    (parted) set 1 boot on  # mark the first partition as bootable
    (parted) mkpart primary  # turn the remaining space in one big partition
      File system type: ext2  # don't worry about this, we'll format it after anyway
      Start: 514MiB
      End: 100%

If you now check the layout:

    (parted) print
      Model: Unknown (unknown)
      Disk /dev/nvme0n1: 512GB
      Sector size (logical/physical): 512B/512B
      Partition Table: gpt
      Disk Flags: 
      
      Number  Start   End    Size   File system  Name  Flags
       1      1049kB  538MB  537MB  fat32              boot, esp
       2      539MB   512GB  512GB  ext2

    (parted) exit

Setting up disk encryption

This will encrypt the second partition, which we’ll then hand off to LVM to manage the rest of our partitions. Doing it this way means everything is protected by a single password. This is good enough for me, especially since I don’t share this machine with anyone else.

$ cryptsetup luksFormat /dev/nvme0n1p2
    WARNING!
    ========
    This will overwrite data on /dev/nvme0n1p2 irrevocably.
    
    Are you sure? (Type uppercase yes): YES
    Enter passphrase: 
    Verify passphrase:

Now we need to open the encrypted disk so LVM can do its thing:

$ cryptsetup open /dev/nvme0n1p2 cryptlvm

Enter passphrase for /dev/nvme0n1p2: 

LVM

Time to setup LVM.

$ pvcreate /dev/mapper/cryptlvm  # create the physical volume
Physical volume "/dev/mapper/cryptlvm" successfully created.

$ vgcreate monoceros /dev/mapper/cryptlvm  # create the volume group
 Volume group "monoceros" successfully created

$ lvcreate -L 60G monoceros -n root  # create a 60GB root partition
 Logical volume "root" created.

$ lvcreate -L 18G monoceros -n swap  # create a RAM+2GB swap, must be bigger than RAM for hibernate
 Logical volume "swap" created.

$ lvcreate -l 100%FREE monoceros -n home  # assign the rest to home
 Logical volume "home" created.

You can check the layout by running lvs:

$ lvs
  LV   VG    Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home monoceros -wi-a----- 398.43g                                                    
  root monoceros -wi-a-----  60.00g                                                    
  swap monoceros -wi-a-----  18.00g                                                    

Format all the partitions

Now we’re going to format all the partitions we’ve created so we can actually use them.

First the root partition:

$ mkfs.ext4 /dev/mapper/monoceros-root
mke2fs 1.43.6 (29-Aug-2017)
Creating filesystem with 15728640 4k blocks and 3932160 inodes
Filesystem UUID: 055d8ed0-19c3-4c20-bcfe-b296939f7b9b
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
	4096000, 7962624, 11239424

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (65536 blocks): done
Writing superblocks and filesystem accounting information: done

Now our home partition:

$ mkfs.ext4 /dev/mapper/monoceros-home
mke2fs 1.43.6 (29-Aug-2017)
Creating filesystem with 104446976 4k blocks and 26116096 inodes
Filesystem UUID: 71738ad7-a620-496b-98a1-2b1e27b6a5e7
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
	4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 
	102400000

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

Swap:

$ mkswap /dev/mapper/monoceros-swap
Setting up swapspace version 1, size = 18 GiB (19327348736 bytes)
no label, UUID=8d7a92ae-0c61-4105-aaf0-71aa61082124

And boot. This must be a FAT32 formatted partition b/c UEFI:

$ mkfs.fat -F32 /dev/nvme0n1p1
mkfs.fat 4.1 (2017-01-24)

Installing the base system

It’s time to install the base system, which we can then chroot into and further customise our installation.

Mount all the partitions

Before we can install the OS we need to mount all the partitions and then chroot into the moutpoint of the root partition.

$ mount /dev/mapper/monoceros-root /mnt
$ mount /dev/mapper/monoceros-home /mnt/home
$ mount /dev/nvme0n1p1 /mnt/boot
$ swapon /dev/mapper/monoceros-swap

Setting up the mirrorlist

Last step is to edit /etc/pacman.d/mirrorlist and put the mirrors closest to you at the top. This’ll help speed up the installation. You can also generate a mirrorlist. I’d highly recommend doing so and ensure you unckeck the http checkbox so you only use mirrors you can fetch from over https.

Installing base

Now that everything is set up we need to bootstrap the OS:

$ pacstrap -i /mnt base base-devel

It’ll now prompt you to confirm your package selection and then start with the installation of the base system.

Configuring the new installation

Now that the base system is there, we can chroot into it to customise our installation and finish it.

$ genfstab -U /mnt >> /mnt/etc/fstab
$ arch-chroot /mnt

Your prompt will now change to: [root@archiso /]#.

The first thing I do is install vim so I can edit and customise my the configuration:

$ pacman -Sy vim

Locale

In order to setup your locale edit /etc/locale.gen and uncomment the locales you want.

After that execute the following:

$ locale-gen
$ echo LANG=en_US.UTF-8 > /etc/locale.conf
$ export LANG=en_US.UTF-8

Timezone

Set your timezone by running:

$ tzselect

Once you’ve selected your timezone we need to update a few more things. First override the /etc/localtime file and symlink it to your timezone:

$ ln -sf /usr/share/zoneinfo/<continent>/<location> /etc/localtime

Sync the clock settings and set the hardware clock to UTC:

$ hwclock --systohc --utc

vconsole

Set the keyboard layout and font to be used by default for the virtual console. Create /etc/vconsole.conf:

FONT=latarcyrheb-sun32
KEYMAP=colemak

Careful with the keymap, you probably don’t want to set that to colemak on your system. If you don’t set it you’ll get the default of us.

Hostname

Time to give your system a name by adding that to /etc/hostname.

Also, add a line for that same hostname to /etc/hosts:

127.0.1.1 monoceros.localdomain monoceros

And tell systemd about it:

$ hostnamectl set-hostname monoceros

Graphics card power saving options

Create /etc/modprobe.d/i915.conf with the following content:

options i915 enable_guc_loading=-1 enable_guc_submission=-1

mkinitcpio

mkinitcpio is what is used to generate the initramfs you’ll soon boot form. However, due to the hardware in this laptop and our disk partitioning we have to update it a bit. This configuration will use a full systemd based boot stack.

  • set MODULES to: (nvme i915 intel_agp)
  • set HOOKS to: (base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt sd-lvm2 filesystems fsck)

Now regenerate the initramfs:

$ mkinitcpio -p linux

==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux.img
==> Starting build: 4.13.9-1-ARCH
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [autodetect]
  -> Running build hook: [keyboard]
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [sd-lvm2]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux-fallback.img -S autodetect
==> Starting build: 4.13.9-1-ARCH
  -> Running build hook: [base]
  -> Running build hook: [systemd]
  -> Running build hook: [keyboard]
  -> Running build hook: [sd-vconsole]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: wd719x
==> WARNING: Possibly missing firmware for module: aic94xx
  -> Running build hook: [sd-encrypt]
  -> Running build hook: [sd-lvm2]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux-fallback.img
==> Image generation successful

Don’t worry about those two warnings, the XPS 13 doesn’t have any hardware on board that needs those drivers.

Microcode

Sometimes bugs are discovered in processors for which microcode updates are released. These are loaded together with the initramfs when your system boots but we need to install the package for it first:

$ pacman -Sy intel-ucode

Setting up the bootloader

First, we need to tell bootctl to install the necessary things onto /boot:

$ bootctl install --path=/boot

In the future you won’t need to call install, but update instead.

Now, edit /boot/loader/loader.conf and make it look like this:

timeout 10
default arch
editor 1

One note, by setting editor 1 it’s possible for anyone to edit the kernel boot parameters, add init=/bin/bash and become root on your system. However, since the disk is still encrypted at this point they can’t do much with it and I find it rather convenient to be able to edit those options when something does go wrong.

We now need to create the boot entry named arch. To that end, create the file /boot/loader/entries/arch.conf with the following content:

title Arch Linux
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
options rd.luks.uuid=$UUID rd.luks.name=$UUID=cryptlvm root=/dev/mapper/monoceros-root rw resume=/dev/mapper/monoceros-swap ro intel_iommu=igfx_off

Replace $UUID with the value from cryptsetup luksUUID /dev/nvme0n1p2.

I usually also create another entry that allows me to boot with resume support disabled, in case that’s broken. To that end, create a file like /boot/loader/entries/arch-noresume.conf with the same content as above, but simply omit the resume=/dev/mapper/monceros-swap ro option.

sudo

I prefer using sudo over changing to root. In order to do so we need to install the sudo package and update the configuration:

$ pacman -Sy sudo

Now that the package is installed update the configuration and uncomment the line that reads %wheel ALL=(ALL) ALL:

$ EDITOR=vim visudo

Creating a user account

Create a user account for yourself and ensure you’re added to the wheel group:

$ useradd -m -G wheel,users -s /bin/bash daenney
$ passwd daenney
    New password: 
    Retype new password: 
    passwd: password updated successfully

Installing GNOME

I like to use GNOME as my DE so it’s time to install that:

$ pacman -Sy gnome gnome-extra dhclient iw wpa_supplicant dialog network-manager-applet networkmanager xf86-input-libinput

I explicitly install dhclient because dhcpd isn’t very good at dealing with non-spec compliant DHCP implementations. Especially if you have a D-Link router or might encounter one, install this package. It also avoids some issues I’ve had on large networks like at the office, Eduroam etc.

Now, enable GDM and Network Manager:

$ systemctl enable gdm
$ systemctl enable NetworkManager

Boot into the new installation

We’re actually done now. What a ride. First, exit the chroot by issuing exit. Now, unmount our filesystems: umount -R /mnt.

And finally, reboot.

Fan control

One of the things you’ll notice pretty quickly is that the fans kick in really early. The fans are controlled by the BIOS and Dell has set some rather low thresholds. It seems they try to keep the system below 40 degrees Celcius at all times.

However, it’s possible to fix this, but it comes with a few warnings. Doing so essentially takes away control from the BIOS by writing directly to the BIOS using an I/O port. It’s hinky but I’ve verified it works correctly on my laptop. When you do this the keys on the keyboard that allow you to control the brightness of the display no longer work, but you can still use the controls in GNOME to do the same thing.

In order to be able to install the necessary tooling you’ll need to first get access to the Archlinux User Repository as that’s where we’ll need to install the utilities from. I use a helper called pacaur, there’s a number of installation guides out there you can follow.

Once you have an AUR helper installed, we need to install i8kutils:

$ pacaur -Sy i8kutils

With that done, go and edit /etc/modprobe.d/dell-smm-hwmon.conf and append ignore_dmi=1 to that line. Right now the module checks the DMI to figure out if it can load and support for the 9360 hasn’t been added yet.

You can now modprobe dell-smm-hwmon and in a couple of seconds you’ll get a /proc/i8k file you can cat which returns information about the fans.

If you install a GNMOE extension like Freon you’ll be able to keep tabs on the fan speed, but not control it.

Patching dell-smm-hwmon

In order to be able to control the fans we need to patch the dell-smm-hwmon kernel module. This is not for the faint of heart.

There is an outdated package on AUR that we can use to help us do this. First, fetch the PKGBUILD:

$ curl -O https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=dell-smm-hwmon-i8kutils
$ curl -O https://aur.archlinux.org/cgit/aur.git/plain/dell-smm-hwmon-i8kutils.install?h=dell-smm-hwmon-i8kutils 

Rename the output to PKGBUILD and edit the pkgver and pkgrel variables to reflect the output in uname -r (at the time of writing 4.13.11 and 1). Also rename the other file to dell-smm-hwmon-i8kutils.install.

Change the source and md5sums lines too:

source=("https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-$pkgver.tar.xz"
	'dell-smm-hwmon.patch::https://aur.archlinux.org/cgit/aur.git/plain/dell-smm-hwmon.patch?h=dell-smm-hwmon-i8kutils'
	'smm.c::https://aur.archlinux.org/cgit/aur.git/plain/smm.c?h=dell-smm-hwmon-i8kutils'
	)
md5sums=('<md5sum of kernel tarbal>'
         'ccfe8a74d9cbf587d5a9cb395cae2834'
         'a5e33f4c632c3055bd51853edeef51fc')

Now, build the package: makepkg. This’ll take a while but it should result in a dell-smm-hwmon-i8kutils-4.13.11-1-any.pkg.tar.xz in the current directory.

Install the package with pacman -U dell-smm-hwmon-i8kutils-4.13.11-1-any.pkg.tar.xz.

Enabling i8kmon

In order to monitor and control the fans, we need to update the i8kmon configuraiton. i8kmon was installed as part of i8kutils.

Edit /etc/i8kmon/i8kmon.conf and add:

# Control the fans
set config(auto) 	1

Next, update the “Temperature thresholds” section like this:

{% raw %}

set config(0)  {{0 0}  -1  55  -1  55}
set config(1)  {{1 1}  45  75  45  75}
set config(2)  {{2 2}  65 128  65 128}

{% endraw %}

If you’re wondering how those numbers work, read the page linked above and see man i8kmon. I’ve also remvoed the set status lines since I’d rather i8kmon probe for those at startup.

Finally, create a systemd unit file in /etc/systemd/system/i8kmon.service:

[Unit]
Description=Dell laptop thermal monitoring
Documentation=man:i8kmon
ConditionPathExists=/proc/i8k

[Service]
ExecStartPre=/usr/bin/smm 30a3
ExecStopPost=/usr/bin/smm 31a3
ExecStart=/usr/bin/i8kmon --nouserconfig
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

And enable the service: systemctl enable i8kmon.service.

When we go into sleep, hybrid sleep or hibernate we should hand the fan control back to the BIOS. In order to do that, create an executable shell script in /usr/lib/systemd/system-sleep named i8kmon with the following content:

#!/bin/sh
if [ "${1}" == "pre" ]; then
	echo "Handing back fan control to BIOS" | systemd-cat
	systemctl stop i8kmon
elif [ "${1}" == "post" ]; then
	echo "Taking over fan control from BIOS" | systemd-cat
	systemctl start i8kmon
fi

Don’t forget to chmod +x the script or nothing will happen.

Now it’s time to reboot and if everything went well you should see something like this in journalctl -u i8kmon:

-- Reboot --
nov 11 16:58:24 monoceros systemd[1]: Starting Dell laptop thermal monitoring...
nov 11 16:58:24 monoceros systemd[1]: Started Dell laptop thermal monitoring.

When you go into suspend and afterwards wake up, you should find the following entries in journalctl:

nov 11 01:42:43 monoceros systemd[1]: Starting Suspend...
nov 11 01:42:43 monoceros cat[22921]: Handing back fan control to BIOS
nov 11 01:42:43 monoceros systemd[1]: Stopping Dell laptop thermal monitoring...
nov 11 01:42:43 monoceros systemd[1]: Stopped Dell laptop thermal monitoring.
nov 11 01:42:43 monoceros systemd-sleep[22917]: Suspending system...

nov 11 16:35:47 monoceros systemd-sleep[22917]: System resumed.
nov 11 16:35:47 monoceros unknown[23048]: Taking over fan control from BIOS
nov 11 16:35:47 monoceros systemd[1]: Starting Dell laptop thermal monitoring...
nov 11 16:35:47 monoceros systemd[1]: Started Dell laptop thermal monitoring.