OneXPlayer 2 Pro (8840) With Arch Linux

Installation with disk encryption

Partition with encryption

In case of after-sales service, I kept windows partitions. I deleted D: partition and shrink C: partition. I create two partitions: /boot and /. I will use btrfs on /, so instead of using paritions, I will use subvolume for /home.

Encryption was applied exclusively to / partition, and I enabled Secure Boot to make EFI partition and /boot partition safe.

  • Encrypt / partition.
    # Don't forget to back up the password.
    cryptsetup luksFormat /dev/nvme0n1p5
    cryptsetup open /dev/nvme0n1p5 root
    
  • Create a btrfs file system and mount it to /mnt.
    mkfs.btrfs /dev/mapper/root
    mount /dev/mapper/root /mnt
    

After that, follow Installation Guide.

Bootloader

As a old linux user, I have used GRUB for a long time. In this installation, I still use GRUB. OneXPlayer 2 Pro is bit heavy. I don't want to bring the keyboard cover everywhere. So, I need the partition to be automatically decrypted when booting from GRUB by integrating the use of Secure Boot and TPM.

At first, I used GRUB. But at the time I am writing this article, I found my plan is not secure enough. According to this article, an attacker can get the decryption key of the partition.

A rogue partition with metadata copied from the real root filesystem (such as partition UUID) can mimic the original partition. Then, initramfs will attempt to mount the rogue partition as the root filesystem (decryption failure will fall back to password entry), leaving pre-boot PCRs unchanged. The rogue root filesystem with files controlled by an attacker is still able to receive the decryption key for the real root partition.

To prevent this attack, we need to make sure: 1. PCRs are changed before leaving initramfs. 2. only trusted initramfs can be booted.

With a signed unified kernel image (UKI), we can make sure that initramfs can't be modified. systemd provides systemd-pcrphase-initrd.service to modify the value of PCR 11 in the state of entering and leaving initramfs. The service works in a systemd based initramfs.

Why not GRUB? UKI is actually a EFI bootloader. GRUB can only load Secure Boot keys signed EFI bootloaders. But, we can't load our own Secure Boot to a OneXPlayer 2 Pro. After researching, I found that rEFInd supports Secure Boot keys and MOK keys signed EFI bootloaders.

Enable Secure Boot

There are three methods to enable Secure Boot for a linux system: CA Keys and Shim and Preloader.

  • CA Keys: Create a pair of private and public keys, then load the public key into the BIOS in setup mode. After that, only bootloaders signed by the private key can be booted.
  • Shim: A binary signed by Microsoft. Since the shim binary is signed by Microsoft, it is validated and accepted by the firmware when verifying against certificates already present in firmware. It uses MOKManager to enroll a custom user public key, and then a user can use the corresponding private key to sign the bootloader and the kernel. It can also enroll the hash of a file.
  • Preloader: Like Shim, but it verifies the bootloader and the kernel with a hash instead of a signature.

On a OneXPlayer 2 Pro, each time you change Secure Boot into setup mode, it will load the default certificates after start. So, we can't use CA Keys.

Enable Secure Boot with Shim

  1. Install shim-signed from AUR.

  2. Mount ESP.

    mkdir /boot-efi
    # the default efi partition is /dev/nvme0n1p1 on a OneXPlayer 2 Pro
    mount /dev/nvme0n1p1 /boot-efi
    
  3. Copy the efi files to ESP.

    cp /usr/share/shim-signed/shimx64.efi /boot-efi/EFI/BOOT/
    cp /usr/share/shim-signed/mmx64.efi /boot-efi/EFI/BOOT/
    
  4. Create a NVRAM entry for Shim.

    efibootmgr --unicode --disk /dev/nvme0n1 --part 1 --create --label "Shim" --loader /EFI/BOOT/shimx64.efi
    
  5. Install rEFInd.

    pacman -S refind
    
    refind-install --shim /usr/share/shim-signed/shimx64.efi --localkeys
    
  6. Use openssl to create the mok keypair.

    cd /etc/refind.d/keys/
    openssl req -newkey rsa:2048 -nodes -keyout refind_local.key -new -x509 -sha256 -days 3650 -subj "/CN=onexplayer2pro-fortime/" -out refind_local.crt
    openssl x509 -outform DER -in refind_local.crt -out refind_local.cer
    cp refind_local.cer /boot/
    
  7. Create a custom config of mkinitcpio.

    cat <<EOF > /etc/mkinitcpio.conf.d/lkus.conf
    # allow usb keyboard to input password for decryption in boot time.
    MODULES=(usbhid xhci_hcd)
    HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole plymouth block sd-encrypt filesystems fsck)
    EOF
    
  8. Create the config file of ukify.

    cat <<EOF >/etc/kernel/ukify.conf
    [UKI]
    Microcode=/boot/amd-ucode.img
    OSRelease=@/etc/os-release
    SecureBootPrivateKey=/etc/refind.d/keys/refind_local.key
    SecureBootCertificate=/etc/refind.d/keys/refind_local.crt
    EOF
    
  9. Create cmdline files for kernel parameters.

    # rd.luks.name: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=name, Specify the name of the mapped device after the LUKS partition is open, where XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is the UUID of the LUKS partition.
    # rd.luks.options: Set options for the device specified by it UUID or, if not specified, for all UUIDs not specified elsewhere (e.g., crypttab). 
    # resume: Specify the root device for hibernation.
    # resume_offset: Specify the offset of the swap file. The offset of swap file in a btrfs file system should be calculated by `btrfs inspect-internal map-swapfile`. Refer to [Hibernation](#Hibernation)
    # video: Set the video kernel parameter to rotate the builtin screen from portrait to landscape in the start of the system.
    # usbcore.autosuspend: USB Ports / Controller stops working after Device Disconnect.
    cat <<EOF >/etc/kernel/cmdline
    rd.luks.name=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=root rd.luks.options=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=tpm2-device=auto root=/dev/mapper/root rw resume=/dev/mapper/root resume_offset=yyyyyyy quiet splash video=eDP-1:panel_orientation=left_side_up usbcore.autosuspend=-1
    EOF
    
  10. Create a mkinitcpio post hook.

    cat <<EOF >/etc/initcpio/post/ukify
    #!/usr/bin/env bash
    
    kernel="\$1"
    initramfs="\$2"
    [ -n "\$kernel" ] || exit 0
    [ -n "\$initramfs" ] || exit 0
    
    initramfs_filename=\$(basename "\$initramfs")
    
    cmdline=""
    uki=""
    ukify_log=""
    case "\$initramfs_filename" in
        "initramfs-linux.img")
            cmdline=/etc/kernel/cmdline
            uki="/boot/arch-linux.efi"
            ukify_log="/etc/refind.d/keys/ukify.log"
            ;;
        "initramfs-linux-fallback.img")
            cmdline=/etc/kernel/cmdline-fallback
            uki="/boot/arch-linux-fallback.efi"
            ukify_log="/etc/refind.d/keys/ukify-fallback.log"
            ;;
        *)
            exit 0
            ;;
    esac
    
    # it is hard to measure the value of PCR 11 with the uki file, we keep the log for getting the value of PCR 11. 
    ukify build --config=/etc/kernel/ukify.conf --linux="\$kernel" --initrd="\$initramfs" --cmdline="@\$cmdline" --measure --output="\$uki" >"\$ukify_log" 2>&1
    
    if [ "\$initramfs_filename" = "initramfs-linux.img" ]
    then
      pcr11=\$(grep "11:sha256" "\$ukify_log" | sed '1!d;s/.*=//')
      if [ \$? -eq 0 ]
      then
          # use PCR 7, PCR 11 and PCR 14 to protect the key in tpm2, and use a recovery key to decrypt the partition.
          systemd-cryptenroll /dev/nvme0n1p5 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs="7+11:sha256=\$pcr11+14" --unlock-key-file=/etc/refind.d/keys/disk-encrypt.txt
      fi
    fi
    EOF
    
    chmod 700 /etc/initcpio/post/ukify
    EOF
    
  11. Sign the grub bootloader and the kernel.

    refind-install --shim /usr/share/shim-signed/shimx64.efi --localkeys
    
  12. Enroll the refind_local.cer.

Reboot and enable Secure Boot. If shim does not find the certificate grubx64.efi is signed with in MokList it will launch MokManager (mmx64.efi).

In MokManager select Enroll key from disk, find refind-local.cer and add it to MokList. When done select Continue boot and your boot loader will launch and it will be capable launching any binary signed with your Machine Owner Key.

Secure path

tpm2-luks.png

Hibernation

To enable hibernation, we should create a swap partition or a swap file. Using swap file in btrfs is different with other file system. We should use utils provided by btrfs to create a swap file.

  • Create a swap file.
    btrfs filesystem mkswapfile /var/cache/swapfile -s 16g
    
  • Calculate the offset of the swapfile.
    btrfs inspect-internal map-swapfile /var/cache/swapfile
    
  • Add a swap item in /etc/fstab.
    # swapfile for hiberation
    /var/cache/swapfile     none            swap            defaults        0 0
    
  • Add resume and resume_offset to kernel cmdline.

TPM issue in hibernation

Measurement of PCR 11 in hibernation is different from normal boot. In hibernation, there is no measurement of leave-initrd before leaving initrd. The leaving of initrd happens in systemd-hibernate-resume.service.

I created a temporarily fix by stopping systemd-pcrphase-initrd.service before systemd-hibernate-resume.service.

  • Create a initcpio install hook, create /etc/initcpio/install/pcrphase-fix.
    #!/bin/bash
    
    build() {
        printf '[Service]\nExecStartPre=/usr/bin/systemctl stop systemd-pcrphase-initrd.service' | add_systemd_drop_in systemd-hibernate-resume.service override
    }
    
    help() {
        cat << EOF
    Fix the issue of no stop of systemd-pcrphase-initrd.service in resume.
    EOF
    }
    
  • Update the custom config of mkinitcpio.
    cat <<EOF > /etc/mkinitcpio.conf.d/lkus.conf
    # allow usb keyboard to input password for decryption in boot time.
    MODULES=(usbhid xhci_hcd)
    HOOKS=(base systemd pcrphase-fix autodetect microcode modconf kms keyboard sd-vconsole plymouth block sd-encrypt filesystems fsck)
    EOF
    

Mapping buttons

There is no volume up/down button of this machine. As a handheld game console, this is unforgivable. So, I want to map the x1/x2 button to volume up/down button. After using evtest monitoring the key events, these two buttons trigger a key combo instead of a single keycode. I can't use hwdb to map keys. Here, i use keyd to finish this work.

  • Install and enable keyd.
    pacman -S keyd
    systemctl enable keyd
    
  • Create a file /etc/keyd/oxp2p.conf.
    [ids]
    0001:0001
    
    [main]
    leftmeta+d = volumedown
    leftcontrol+leftmeta+o = volumeup
    
  • Restart keyd.
    systemctl start keyd
    

Foldable bluetooth keyboard

The key in the removable keyboard cover is too small. I buy a foldable bluetooth keyboard. Everything is ok, but Fn keys are function keys by default. I use Fn keys a lot in vim. It makes me very inconvenient. So, I add a hwdb file to switch Fn key and function key.

  • Add a hwdb file /etc/udev/hwdb.d/90-custom-keyboard.hwdb.
    evdev:input:b0005v0A5Cp8503e011B*
      KEYBOARD_KEY_700e2=leftmeta
      KEYBOARD_KEY_700e3=leftalt
      KEYBOARD_KEY_700e6=rightmeta
      KEYBOARD_KEY_700e7=rightalt
      KEYBOARD_KEY_c0223=esc
      KEYBOARD_KEY_70029=homepage
      KEYBOARD_KEY_c0221=f1
      KEYBOARD_KEY_7003a=search
      KEYBOARD_KEY_7003b=back
      KEYBOARD_KEY_7003c=forward
      KEYBOARD_KEY_7003d=brightnessdown
      KEYBOARD_KEY_7003e=brightnessup
      KEYBOARD_KEY_c00b6=f6
      KEYBOARD_KEY_7003f=previoussong
      KEYBOARD_KEY_c00cd=f7
      KEYBOARD_KEY_70040=playpause
      KEYBOARD_KEY_c00b5=f8
      KEYBOARD_KEY_70041=nextsong
      KEYBOARD_KEY_c00b5=f8
      KEYBOARD_KEY_70041=nextsong
      KEYBOARD_KEY_c00e2=f9
      KEYBOARD_KEY_70042=mute
      KEYBOARD_KEY_c00ea=f10
      KEYBOARD_KEY_70043=volumedown
      KEYBOARD_KEY_c00e9=f11
      KEYBOARD_KEY_70044=volumeup
      KEYBOARD_KEY_c0030=f12
      KEYBOARD_KEY_70045=power
    
  • But its f2, f3, f4 and f5 send a key combo instead of a single key. I use keyd again. Add a keyd config file.
    [ids]
    0a5c:8503
    
    [main]
    leftalt+a = f2
    leftalt+c = f3
    leftalt+v = f4
    leftalt+x = f5
    

Auto rotation

The accelerator sensor showed in Windows device manager is bmi160. But the bmi160 driver in Linux can't work. After some research, I found that the bmi260 driver also matches the acpi device id 10EC5280. So, I try this driver, and it works.
Install bmi260-dkms from AUR.

# iio-sensor-proxy: Proxies sensor devices (accelerometers, light sensors, compass) to applications through D-Bus.
paru -S bmi260-dkms iio-sensor-proxy

Wrong direction

After installing bmi260-dkms, the auto-rotate works, but the screen is 90 degrees clockwise rotated. We can use udev rules to fix it.

  • Create a file /etc/udev/hwdb.d/90-custom-oxp.hwdb with following content.
    sensor:modalias:acpi:10EC5280*:dmi:*:rnONEXPLAYER2PROARP23P:rvrVersion1.0:*
      ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
    
  • Update hwdb and trigger udev to update the device.
    sudo systemd-hwdb update && sudo udevadm trigger
    

KDE touch mode

Be default, kde disables touch mode if there is any pointer device. I use keyd to map buttons, and keyd creates a virtual pointer device which will make kde disables touch mode all the time. After reviewing the source code of kwin, I found we can add a tag kwin-ignore-tablet-mode to the keyd pointer device to let kde ignore the device.

  • Create a file /etc/udev/rules.d/90-tablet-mode.rules.
    ACTION=="remove", GOTO="kwin_ignore_tablet_mode_end"
    
    SUBSYSTEM=="input", KERNEL=="event*", ATTRS{id/vendor}=="0fac", ATTRS{id/product}=="1ade", TAG+="kwin-ignore-tablet-mode"
    
    LABEL="kwin_ignore_tablet_mode_end"
    

Audio

TODO

Multi-touch

TODO

Drop of signal with external monitor

I have two external monitors, a portable monitor and a Philips 279C9. Both of them have signal drop issue when I'm playing games.

The portable monitor

I occasionally turn off Free Sync of the portable monitor, and the issue disappeared. In a later research, I found the cause. I'm using wayland and I often connect to other monitors, overriding edid is not a good enough solution, so I still keep Free Sync off.

The Philips 279C9

At first, I think this is the same as the portable monitor. But there are no setting of Free Sync in the menu the monitor. After some research, I found that there will be a dropdown option in KDE Display Configuration if the monitor supports Free Sync.

To check if the monitor supports Free Sync, I install wxedid to inspect the edid info the monitor.

sudo get-edid -b ${bus_id} > /tmp/philips-edid.bin
wxedid /tmp/philips-edid.bin

When I was checking the edid info, I noticed that only 4k@30 is used even if the monitor supports 4k@60. Why? So I enable the drm debug log.

# enable all kinds of drm debug log
echo 0x1BF > /sys/module/drm/parameters/debug

# disable all drm debug log
echo 0x0 > /sys/module/drm/parameters/debug

# what does 0x1BF mean, it is flags of debug category.
modinfo -p drm

In the debug log, it showed something like:

[drm:create_validate_stream_for_sink [amdgpu]] Mode 3840x2160 (clk 533250) failed DC validation with error 10 (No DP link bandwidth)

All modelines with pixel clock of 533250 are rejected. But when I switched to Windows, 4k@60 is supported. It shouldn't be a quality issue of the cable. When I was trying to add more debug log, I occasionally found a comment of Philips 279C9. It said that 4k@60 can only be used after switching usb3.2 to usb2.0 in the monitor menu when it is connected to a Mac OS. I tried this and the option of 4k@60 appeared. When I'm using 4k@60, the signal drop issue disappear too. I think it is because it will reduce the estimated bandwidth of a DP link if the type-c port is used as a usb3.2 hub.
I can’t believe I spent 10 hours troubleshooting during national day vacation! And it can be solved just by changing a setting like this. At last, I still don't know why this cause the signal drop issue, and why 4k@60 can be used in Windows even if usb3.2 is using. ~~If the signal drop issue exists if I am using 4k@30 and usb2.0.~~The signal drop issue disappers after switching to usb2.0.

Reference