I am writing this on a Microsoft Surface Book, running Ubuntu natively, and there isn’t any Windows option - I blew away, the Windows partition, and there isn’t any other OS on it.

Why some of you might think? Well, why not. :) For me the motive is two fold: one am a geek and love to hack what works and cannot work - how else will one learn? And two, explore and see which AI frameworks, tools, and runtimes works better on Linux natively

Well, I must say, this experiment has been a pleasant surprise and much more successful that I originally thought of. Most of the things are working quite well on Surface with Ubuntu - including touch and pen (both seem like mouse clicks). As the screenshot below shows, Ubuntu is running quite nicely - including most of the features. There are a few things that quite don’t - I have them listed later in the post.

Ubuntu desktop

So much so, that Visual Studio code is running natively and whilst I haven’t had a chance to use it much (yet), the fact that it can even so much was something I wasn’t expecting without running some containers or VM’s or the likes.

Visual Studio code running on Ubuntu

So, how does one go about doing this? It is quite simple these days, to be honest. Below are the steps I had followed. I do think the real magic is the hard work that JakeDay has done to get the kernel and firmware supported.

Disclaimer: My experience outlined here is related to the Surface Book - it can also run and be supported on other Surface devices, and the exact nature of what works or doesn’t work would be a little different.

  1. Hardware - Have a USB keyboard and mouse handy just in case; and if you are on a Surface Pro or something with only one usb port, then a usb hub. And you of course would need a USB drive to boot Ubuntu off.

  2. Disable Secure boot - without this getting the bootloader sequence would be challenging. If you aren’t sure how, then check out the instructions here to disable secure boot.

  3. Delete / Shrink the windows partition: If you don’t care about Windows and have a copy of the license somewhere to get back you might want to just delete this. If you do want to shrink it (say this is your primary machine and you want to get back at some point, then goto Disk Management in Windows and resize the partition - keep this to at least 50 GB.

  4. Ubuntu USB drive - if you don’t have one already, create a ubuntu bootable usb drive. You can get more instructions here . And if you are on Windows,  I would recommend using Rufus .

  5. Install Ubuntu - Boot off the usb drive you created, and before that make sure you have disabled secure boot. I would pick most of the default options for Ubuntu for now.

  6. Patched Kernel - Once you have ubuntu running, I would recommend installing the patched kernel and headers that allows for Surface support. Steps for these are outlined below and need to be execute in a terminal.

    • Install Dependencies: sudo apt install git curl wget sed

    • Clone the repo: git clone https://github.com/jakeday/linux-surface.git ~/linux-surface

    • Change working directory: cd ~/linux-surface

    • Run setup: sudo sh setup.sh

    • Reboot on the patched kernel

Change boot kernel: Finally, after you have rebooted, the odds of Ubuntu booting off the ‘right’ kernel is quite slim and best to manually pick this. You can of course use the grub, or what I find better - install the grub customizer , and then choose the correct option as shown below. Once picked and you had hit save, you also need to run the following in a terminal to make these persist: sudo update-grub

Grub Customizer

And that is all to it for getting the base install and customization running.

If you are super curious about what that setup script does, the code is below (also listed on github). What is interesting to see the various hardware models supported.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
LX\_BASE=""
LX\_VERSION=""

if \[ -r /etc/os-release \]; then
    . /etc/os-release
	if \[ $ID = arch \]; then
		LX\_BASE=$ID
    elif \[ $ID = ubuntu \]; then
		LX\_BASE=$ID
		LX\_VERSION=$VERSION\_ID
	elif \[ ! -z "$UBUNTU\_CODENAME" \] ; then
		LX\_BASE="ubuntu"
		LX\_VERSION=$VERSION\_ID
    else
		LX\_BASE=$ID
		LX\_VERSION=$VERSION
    fi
else
    echo "Could not identify your distro. Please open script and run commands manually."
	exit
fi

SUR\_MODEL="$(dmidecode | grep "Product Name" -m 1 | xargs | sed -e 's/Product Name: //g')"
SUR\_SKU="$(dmidecode | grep "SKU Number" -m 1 | xargs | sed -e 's/SKU Number: //g')"

echo "\\nRunning $LX\_BASE version $LX\_VERSION on a $SUR\_MODEL.\\n"

read -rp "Press enter if this is correct, or CTRL-C to cancel." cont;echo

echo "\\nContinuing setup...\\n"

echo "Coping the config files under root to where they belong...\\n"
cp -Rb root/\* /

echo "Making /lib/systemd/system-sleep/sleep executable...\\n"
chmod a+x /lib/systemd/system-sleep/sleep

read -rp "Do you want to replace suspend with hibernate? (type yes or no) " usehibernate;echo

if \[ "$usehibernate" = "yes" \]; then
	if \[ "$LX\_BASE" = "ubuntu" \] && \[ 1 -eq "$(echo "${LX\_VERSION} >= 17.10" | bc)" \]; then
		echo "Using Hibernate instead of Suspend...\\n"
		ln -sfb /lib/systemd/system/hibernate.target /etc/systemd/system/suspend.target && sudo ln -sfb /lib/systemd/system/systemd-hibernate.service /etc/systemd/system/systemd-suspend.service
	else
		echo "Using Hibernate instead of Suspend...\\n"
		ln -sfb /usr/lib/systemd/system/hibernate.target /etc/systemd/system/suspend.target && sudo ln -sfb /usr/lib/systemd/system/systemd-hibernate.service /etc/systemd/system/systemd-suspend.service
	fi
else
	echo "Not touching Suspend\\n"
fi

read -rp "Do you want use the patched libwacom packages? (type yes or no) " uselibwacom;echo

if \[ "$uselibwacom" = "yes" \]; then
	echo "Installing patched libwacom packages..."
		dpkg -i packages/libwacom/\*.deb
		apt-mark hold libwacom
else
	echo "Not touching libwacom"
fi

if \[ "$SUR\_MODEL" = "Surface Pro 3" \]; then
	echo "\\nInstalling i915 firmware for Surface Pro 3...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_bxt.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Pro" \]; then
	echo "\\nInstalling IPTS firmware for Surface Pro 2017...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v102.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Pro 2017...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_kbl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Pro 4" \]; then
	echo "\\nInstalling IPTS firmware for Surface Pro 4...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v78.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Pro 4...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_skl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Pro 2017" \]; then
	echo "\\nInstalling IPTS firmware for Surface Pro 2017...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v102.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Pro 2017...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_kbl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Pro 6" \]; then
	echo "\\nInstalling IPTS firmware for Surface Pro 6...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v102.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Pro 6...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_kbl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Laptop" \]; then
	echo "\\nInstalling IPTS firmware for Surface Laptop...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v79.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Laptop...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_skl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Book" \]; then
	echo "\\nInstalling IPTS firmware for Surface Book...\\n"
	mkdir -p /lib/firmware/intel/ipts
	unzip -o firmware/ipts\_firmware\_v76.zip -d /lib/firmware/intel/ipts/

	echo "\\nInstalling i915 firmware for Surface Book...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_skl.zip -d /lib/firmware/i915/
fi

if \[ "$SUR\_MODEL" = "Surface Book 2" \]; then
	echo "\\nInstalling IPTS firmware for Surface Book 2...\\n"
	mkdir -p /lib/firmware/intel/ipts
	if \[ "$SUR\_SKU" = "Surface\_Book\_1793" \]; then
		unzip -o firmware/ipts\_firmware\_v101.zip -d /lib/firmware/intel/ipts/
	else
		unzip -o firmware/ipts\_firmware\_v137.zip -d /lib/firmware/intel/ipts/
	fi

	echo "\\nInstalling i915 firmware for Surface Book 2...\\n"
	mkdir -p /lib/firmware/i915
	unzip -o firmware/i915\_firmware\_kbl.zip -d /lib/firmware/i915/

	echo "\\nInstalling nvidia firmware for Surface Book 2...\\n"
	mkdir -p /lib/firmware/nvidia/gp108
	unzip -o firmware/nvidia\_firmware\_gp108.zip -d /lib/firmware/nvidia/gp108/
fi

if \[ "$SUR\_MODEL" = "Surface Go" \]; then
	echo "\\nInstalling ath10k firmware for Surface Go...\\n"
	mkdir -p /lib/firmware/ath10k
	unzip -o firmware/ath10k\_firmware.zip -d /lib/firmware/ath10k/
fi

echo "Installing marvell firmware...\\n"
mkdir -p /lib/firmware/mrvl/
unzip -o firmware/mrvl\_firmware.zip -d /lib/firmware/mrvl/

read -rp "Do you want to set your clock to local time instead of UTC? This fixes issues when dual booting with Windows. (type yes or no) " uselocaltime;echo

if \[ "$uselocaltime" = "yes" \]; then
	echo "Setting clock to local time...\\n"

	timedatectl set-local-rtc 1
	hwclock --systohc --localtime
else
	echo "Not setting clock"
fi

read -rp "Do you want this script to download and install the latest kernel for you? (type yes or no) " autoinstallkernel;echo

if \[ "$autoinstallkernel" = "yes" \]; then
	echo "Downloading latest kernel...\\n"

	urls=$(curl --silent "https://api.github.com/repos/jakeday/linux-surface/releases/latest" | grep '"browser\_download\_url":' | sed -E 's/.\*"(\[^"\]+)".\*/\\1/')

	resp=$(wget -P tmp $urls)

	echo "Installing latest kernel...\\n"

	dpkg -i tmp/\*.deb
	rm -rf tmp
else
	echo "Not downloading latest kernel"
fi

echo "\\nAll done! Please reboot."

Lastly, below are the things not working for me - none of these are deal breakers but something to be aware of.

  • Cameras are not supported - either of the two.
  • Dedicated GPU (if you have one). I was a little bummed out as I got the dedicated GPU for some of the #MachineLearning experimentation, but then this whole thing is a different type of experimentation, so am OK.
  • Can control the volume using the speaker widget thing on the top right corner, but the volume buttons on top aren’t.
  • Sleep / Hibernation - It has some issues and for now I have sleep disabled but have hibernation setup.
  • Detaching the screen will immediately terminate everything and power off the machine (not a clean poweroff) - I am guessing it cannot transition between the two batteries of the base and the screen. However if already detached then it will work without any issues.

Happy hacking! 🖐️