最近实在忍受不了 Windows 放着什么也不干功耗都能到达 10W,所以打算给 Surface Pro 8 装个 Arch (with ZFS on LUKS) 看看。目前看起来使用 linux surface kernel 之后除了摄像头和以外都可以得到支持。Surface Pen 也有相应的软件可以支持压感和手写笔记,并且换成 Linux 后机器的续航明显提高了!现在不干什么事情的话亮度在 30% 的时候一般是 5-6W,普通浏览网页是 7W 左右的功耗。

这篇文章应该会持续更新。

准备工作

首先需要在 Windows 上给将要安装的系统分出一定的空间,这可以直接用 Windows 自带的 Create and format hard disk partitions 来完成。接下来需要准备一个启动盘,可以直接用现成的 Arch LiveCD,也可以自己按照文档做一个。

准备好启动盘和分配好空闲的硬盘空间之后关机,插入启动盘然后开机进 BIOS,松开开机键之后立即按住音量上键,等微软的图标闪了一下之后就可以进入 Surface UEFI 界面。在此关闭安全启动,然后选择同 USB 设备启动,就可以进入安装环境了。

安装系统

安装的过程基本上按照 Arch Wiki 的操作来就行了1

准备分区和挂载对应目录

这里准备两个 zpool,分别是不加密的挂载到 /bootbpool2,和在 LUKS 上的 rpool。如下所示,此处新增加的两个分区是 /dev/nvme0n1p5/dev/nvme0n1p6

nvme0n1     259:0    0 953.9G  0 disk
├─nvme0n1p1 259:12   0   260M  0 part
├─nvme0n1p2 259:13   0    16M  0 part
├─nvme0n1p3 259:14   0 698.6G  0 part
├─nvme0n1p4 259:15   0     1G  0 part
├─nvme0n1p5 259:16   0     8G  0 part
└─nvme0n1p6 259:17   0   244G  0 part

接下来创建 LUKS 设备3

cryptsetup luksFormat --sector-size=4096 /dev/nvme0n1p6
cryptsetup open /dev/nvme0n1p6 root_crypt

然后创建 zpool:

zpool create \
    -o ashift=12 \
    -o autotrim=on \
    -o compatibility=grub2 \
    -o cachefile=/etc/zfs/zpool.cache \
    -O devices=off \
    -O acltype=posixacl -O xattr=sa \
    -O compression=lz4 \
    -O normalization=formD \
    -O relatime=on \
    -O canmount=off -O mountpoint=/boot -R /mnt \
    bpool /dev/nvme0n1p5

zpool create \
    -o ashift=12 \
    -o autotrim=on \
    -O acltype=posixacl -O xattr=sa -O dnodesize=auto \
    -O compression=zstd \
    -O normalization=formD \
    -O relatime=on \
    -O canmount=off -O mountpoint=/ -R /mnt \
    rpool /dev/mapper/root_crypt

zpool export bpool
zpool export rpool
zpool import bpool -R /mnt -d /dev/disk/by-id
zpool import rpool -R /mnt -d /dev/disk/by-id

接下来创建 zfs datasets:

zfs create -o canmount=off    -o mountpoint=none rpool/archlinux
zfs create -o canmount=off    -o mountpoint=none bpool/archlinux

zfs create -o canmount=noauto -o mountpoint=/    rpool/archlinux/root
zfs mount rpool/archlinux/root

# /boot
zfs create -o mountpoint=/boot bpool/archlinux/root

# /home and /root
zfs create -o mountpoint=/home rpool/home
zfs create -o mountpoint=/root rpool/home/root
chmod 700 /mnt/root

# /var
zfs create -o canmount=off     rpool/var
zfs create -o canmount=off     rpool/var/lib
zfs create                     rpool/var/lib/log
zfs create                     rpool/var/lib/libvirt

安装 Arch

接下来在 /mnt 安装必要的包并 chroot

pacstrap -K /mnt base base-devel linux-firmware
arch-chroot /mnt

然后将 ArchZFSLinuxSurface 的仓库加入 /etc/pacman.conf 并且加入相关 key,并且安装相关的包:

pacman -Sy
pacman -S linux-surface linux-surface-headers iptsd intel-ucode
pacman -S zfs-dkms zfs-utils

修改 /etc/mkinitcpio.conf,分别在 MODULES 和 HOOK 加入 zfs, luks 还有 surface 键盘相关模块,其中 surface 相关模块是为了在解密 LUKS 的时候可以用 Type Cover 键盘输入密码4

MODULES=(zfs surface_aggregator surface_aggregator_registry surface_aggregator_hub surface_hid_core 8250_dw surface_hid surface_kbd intel_lpss intel_lpss_pci pinctrl_tigerlake)
HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt zfs filesystems fsck)

修改完成后使用 mkinitcpio -P 重新生成 initramfs,并且启用下列服务:

zpool set cachefile=/etc/zfs/zpool.cache rpool
zpool set cachefile=/etc/zfs/zpool.cache bpool
systemctl enable zfs.target
systemctl enable zfs-import.target
systemctl enable zfs-import-cache.service
systemctl enable zfs-mount.service

安装引导

下面安装 Grub:

pacman -S grub efibootmgr

因为 Grub 可能无法自动识别根目录对应的 zpool,所以需要手动修改指定(这在每次更新 Grub 后都需要进行):

sed -i 's/rpool=/rpool="rpool" #/' /etc/grub.d/10_linux

接下来修改 /etc/default/grub 中的内核参数,指定启动时需要解密的 LUKS 设备:

GRUB_CMDLINE_LINUX="cryptdevice=UUID=<uuid of your LUKS partition>:root_crypt:discard zfs_import_dir=/dev/disk/by-id/"

之后创建 /boot/efi 并且暂时退出 chroot 环境挂载 EFI 分区(mount /dev/nvme0n1p1 /mnt/boot/efi),然后再回到 chroot 安装 Grub:

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

Update: 见评论区,root= 也可以直接指定。因为内核按照顺序解析参数,root= 交由 init/do_mounts.c 中的 root_dev_setup 处理,它干的事情只是把对应参数拷贝到某个缓冲区(就是一个 strscpy),因此重复的参数最终起效的会是后面的那个5

安装系统剩余部分

剩下的事情就是安装图形界面和创建账户了,这就和普通的安装过程没有区别了。

另外,如果觉得机器的内存不太够,也可以适当考虑打开 zram

Secure Boot

目前安装的系统是不支持 Secure Boot 的,如果需要启用 Secure Boot,需要进行额外的一些操作6。例如使用 shim-signed但是我暂时没有成功。实际是可以的,看了 menci 的这篇博文之后才发现我没认真读 archwiki,忘记把模块打包进 Grub 了7

按照 archwiki 的描述8,需要把 Grub 模块全部嵌入 grubx64.efi(具体的模块见 wiki),并且需要在该文件中包含 sbat section,假设 GRUB_MODULES 环境变量包含了所需的模块,那么前边的 Grub 安装需要改成

grub-install --target=x86_64-efi --efi-directory=esp --modules=${GRUB_MODULES} --sbat /usr/share/grub/sbat.csv

然后做一个 MOK 证书,用 sbsigngrubx64.efi 以及 zfs 模块签名即可7

用 TPM 来解锁 LUKS

clevis 可以不用每次都输入密码解锁 LUKS:

clevis luks bind -d /dev/nvme0n1p6 tpm2 '{"pcr_bank":"sha256","pcr_ids":"1,7"}'

完事后按照上面 wiki 安装 mkinitcpio-clevis-hook 并且修改 mkinitcpio.conf 加入相应 hook。

Hibernate

虽然 linux-surface 里 hibernate 的支持是 ?,但是实际上按照 archwiki 这部分是可以成功挂起硬盘的(需要 6.6.x 版本的内核,6.8.x 似乎有时候会失败)。此外,如果只想在 hibernate 的时候启用 swap,可以参考这里

功耗控制

在 Windows 上 SP8 有三种不同的性能模式,再加上是 AC 还是电池供电有六种情况,它们的 PL1 和 PL2 分别如下:

性能模式 PL1 / PL2 (AC) PL1 / PL2 (BAT)
Recommend 28W / 35W 13W / 35W
Performance 28W / 45W 28W / 35W
Maximum Performance 35W / 60W 28W / 45W

Linux 下面默认应该是 35W / 60W 这个状态,一不小心就过热了,可以在 crontab 写一个在启动时修改的脚本:

@reboot echo 28000000 > /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
@reboot echo 35000000 > /sys/class/powercap/intel-rapl:0/constraint_1_power_limit_uw

如果要在插入电源和使用电池间切换,那需要修改 udev 规则:首先编辑 /usr/local/bin/power_supply_change.sh

#!/usr/bin/bash

if [ $1 = "ac" ]; then
        echo 28000000 > /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
        echo 35000000 > /sys/class/powercap/intel-rapl:0/constraint_1_power_limit_uw
elif [ $1 = "bat" ]; then
        echo 15000000 > /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
        echo 35000000 > /sys/class/powercap/intel-rapl:0/constraint_1_power_limit_uw
fi

然后编辑 /etc/udev/rules.d/60-onbattery.rules

# Rule for when switching to battery
SUBSYSTEM=="power_supply",ENV{POWER_SUPPLY_ONLINE}=="0",RUN+="/usr/local/bin/power_supply_change.sh bat"
# Rule for when switching to AC
SUBSYSTEM=="power_supply",ENV{POWER_SUPPLY_ONLINE}=="1",RUN+="/usr/local/bin/power_supply_change.sh ac"

最后重新加载 udev 规则,并给相关脚本加入可执行权限:

chmod +x /usr/local/bin/power_supply_change.sh
udevadm control --reload-rules

此外,机器是不能长时间运行在 28W PL1 下的,过热的时候 surface 固件会把 CPU 频率设置为 200MHz 并且风扇开到最大,持续一段时间温度下来之后才会恢复9。可以参考这里的 issue 用 thermald 来控制温度,具体配置 /etc/thermald/thermal-conf.xml 如下(不确定和上面电池的 PL1/PL2 修改冲不冲突):

<?xml version="1.0"?>
<ThermalConfiguration>
<Platform>
        <Name>Surface Pro 8 Thermal</Name>
        <ProductName>*</ProductName>
        <Preference>QUIET</Preference>
        <ThermalZones>
                <ThermalZone>
                        <Type>cpu</Type>
                        <TripPoints>
                                <TripPoint>
                                        <SensorType>x86_pkg_temp</SensorType>
                                        <Temperature>65000</Temperature>
                                        <type>passive</type>
                                        <ControlType>SEQUENTIAL</ControlType>
                                        <CoolingDevice>
                                                <index>1</index>
                                                <type>rapl_controller</type>
                                                <influence>100</influence>
                                                <SamplingPeriod>10</SamplingPeriod>
                                        </CoolingDevice>
                                </TripPoint>
                        </TripPoints>
                </ThermalZone>
        </ThermalZones>
</Platform>
</ThermalConfiguration>

Plasma 相关设置

窗口修改 titlebar 为黑色

如果在浅色主题下想要修改 Konsole 的标题栏为黑色,可以打开 System Settings - Window Rules, 添加如下规则10

  • Window class (application): Exact Match - org.kde.konsole
  • Appearance & Fixes: Titlebar color scheme - Force - Breeze Dark

VSCode 也可以类似操作,然后在其内部修改 window.menuBarVisibility 为不可见就可以关闭菜单栏。

Konsole 修改 Tab Bar 菜单为深色

编辑 Consigure - Tab Bar / Splitters 下方的 Miscellaneous,选择 Use user-defined stylesheet 并且使用如下 CSS 文件:

/* Based on codemedic's work (https://gist.github.com/codemedic/f11cc460b8d9544f9afc) */
QWidget, QTabWidget::pane, QTabWidget::tab-bar {
    background-color: #383c4a;
}
QTabBar::tab {
    color: #777; 
    background-color: #383c4a; /* Pick from current color scheme from System Settings > Color > Modify > Colors > Window Background */
    font-size: 11px;
    padding: 6px;
}
QTabBar::tab:selected, QTabBar::tab:hover {
    color: #d3dae3; /* Pick from current color scheme from System Settings > Color > Modify > Colors > Window Text */
    background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #d3dae3, stop: 0.001 #d3dae3, stop: 0.07 #383c4a);
                                                             /* Same as color         Same as color  Same as background-color*/
}

Super + Tab

如果要实现类似 Windows 下 Super + Tab 的功能,需要修改 System Settings - Desktop Effects 下面 Present Windows 项的快捷键。

VA-API 加速视频解码

参照 ArchWiki,需要安装 intel-media-driver。之后可以播放视频并且通过 intel_gpu_top 查看 ENGINES 下方 Video 部分是否非零(这个程序在 intel-gpu-tools 里)。 对于 Microsoft Edge 还需要添加 --enable-features=VaapiVideoDecodeLinuxGL11。对于 OBS Studio,硬件加速编码需要额外安装 onevpl-intel-gpu,具体参考相关 issue

字体配置

编辑 ~/.config/fontconfig/fonts.conf

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <!-- Serif -->
  <alias>
    <family>serif</family>
    <prefer>
      <family>DejaVu Serif</family>
      <family>Source Han Serif CN</family>
      <family>Noto Serif CJK SC</family>
    </prefer>
  </alias>
  <!-- Sans-serif -->
  <alias>
    <family>sans-serif</family>
    <prefer>
      <family>DejaVu Sans</family>
      <family>Source Han Sans CN</family>
      <family>Noto Sans CJK SC</family>
    </prefer>
  </alias>
  <!-- Monospace -->
  <alias>
    <family>monospace</family>
    <prefer>
      <family>DejaVu Sans Mono</family>
      <family>Source Han Sans CN</family>
      <family>Noto Sans CJK SC</family>
    </prefer>
  </alias>
</fontconfig>

另外,建议安装 ttf-hack-nerd,会有一些图标的 patch。

中文输入

安装 fcitx5-im 并且按照这里的文档处理。如果使用 Microsoft Edge 或者 Google Chrome 需要增加如下参数,否则触控板是无法通过手势放大和缩小网页的。中文输入法安装 fcitx5-chinese-addons 即可。还有些例如 Sogou 输入法和中文维基百科的词库,可以参考 ArchWiki

microsoft-edge-stable  --enable-features=UseOzonePlatform,VaapiVideoDecodeLinuxGL --ozone-platform=wayland --enable-wayland-ime

Windows 上的相关软件

  • 微信:现在有原生的 Linux 版本了,参见 ArchWiki
  • Office:用 WPS,显示效果比较正常,如果使用 fcitx 可以参考这里的 fix。
  • 笔记软件:Xournal++,支持 Surface Pen 以及压感,挺不错的
  • EmbyTheater:这东西全屏的时候老是会拿窗口焦点,让人干不了别的事情,可以把对应代码删掉
    sed -i 's/mainWindow.focus();//' /usr/lib/emby-theater/resources/app/main.js
    

Surface Pen 以及触摸屏相关

Surface Pen 距离屏幕比较近的时候一般关闭触摸屏功能会比较合适,这可以修改 /etc/iptsd.conf 的如下参数:

##
## Ignore all touch inputs if a stylus is in proximity.
##
DisableOnStylus = true

支持压感的笔记和 PDF 标注软件推荐 Xournal++。另外,ArchWiki 还有一些其它的推荐12

LED 彩蛋

虽然摄像头不能工作,但是摄像头的指示灯还是可以被系统识别的,这里的 INT33BE_00::privacy_ledOVTID858_00::privacy_led 分别是前后两个摄像头旁边的灯。

$ ls /sys/class/leds/
input32::capslock  input32::scrolllock      OVTID858_00::privacy_led  SMO55F0_00::privacy_led
input32::numlock   INT33BE_00::privacy_led  phy0-led

既然可以控制它,那就可以用它来干点好玩的事情,比如用来显示网卡的工作情况:

led="INT33BE_00::privacy_led"
modprobe ledtrig-netdev
echo netdev > "/sys/class/leds/$led/trigger" 
echo 1 > "/sys/class/leds/$led/tx" 
echo 1 > "/sys/class/leds/$led/rx" 
echo 1 > "/sys/class/leds/$led/link" 
echo wlp0s20f3 > "/sys/class/leds/$led/device_name" 

参考资料