Skip to content

Instantly share code, notes, and snippets.

@kinncj
Last active March 28, 2026 15:51
Show Gist options
  • Select an option

  • Save kinncj/0ec034c614df42d02c905a4cb7a66667 to your computer and use it in GitHub Desktop.

Select an option

Save kinncj/0ec034c614df42d02c905a4cb7a66667 to your computer and use it in GitHub Desktop.
HP ZBOOK ULTRA G1A - ARCH/CACHYOS
.claude
linux-*

HP ZBook Ultra G1a tools

Scripts for the HP ZBook Ultra G1a on Arch, CachyOS, and Omarchy.

ISP4 sleep hook for CachyOS 6.19.10+

CachyOS 6.19.10 backports the AMD ISP4 capture driver (v10, from the 7.0 patchset by Bin Du / Pratap Nirujogi), so the camera modules are there. But CachyOS did not include the i2c resume-probe race fix for i2c-designware-amdisp.c. On resume, pm_runtime_get_sync() fires the amdisp i2c runtime resume before probe finishes, NULL dereference, laptop hangs. The bug has been there since the driver was first merged — it just went unnoticed until 7.0.

The isp-suspend hook sidesteps this: unload the four ISP modules before sleep, reload after resume.

Install the hook (no custom kernel needed)

sudo cp isp-suspend /usr/lib/systemd/system-sleep/isp-suspend
sudo chmod +x /usr/lib/systemd/system-sleep/isp-suspend

The hook cycles these modules: amd_isp4_capture, i2c_designware_amdisp, pinctrl_amdisp, amd_isp4.

Check the log after a suspend/resume cycle:

cat /tmp/isp-test.log

Setup

mkdir -p ~/.tools
git clone https://gist.github.com/0ec034c614df42d02c905a4cb7a66667.git ~/.tools/kernel
cd ~/.tools/kernel

Kernel patcher

Builds Linux v7.0-rc5 with AMD Strix Halo ISP4 camera patches. Auto-detects GRUB or Limine. Installs deps, applies patches via b4, builds against your running config, installs modules + kernel + bootloader entry. Also drops in the isp-suspend sleep hook and decompresses ISP4 firmware if needed. Asks for sudo once.

chmod +x patcher.sh
./patcher.sh

Virtual surround (Dolby Atmos)

Sets up 7.1 HeSuVi binaural virtual surround through PipeWire. Downloads the HRIR impulse response, writes the filter-chain config, and sets the virtual sink as default. Safe to run again - only touches what's changed.

chmod +x atmos-setup.sh
./atmos-setup.sh

Verify with pw-cli ls Node | grep vss and pw-top while playing audio.

Files

  • patcher.sh - kernel build and install
  • isp-suspend - systemd sleep hook, cycles ISP modules on suspend/resume
  • atmos-setup.sh - PipeWire 7.1 HeSuVi virtual surround setup
#!/usr/bin/env bash
set -euo pipefail
# PipeWire 7.1 HeSuVi Virtual Surround Setup
# Idempotent - safe to run multiple times
# -----------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
HRIR_DIR="${HOME}/.config/pipewire/hrir"
CONF_DIR="${HOME}/.config/pipewire/pipewire.conf.d"
HRIR_FILE="${HRIR_DIR}/atmos.wav"
CONF_FILE="${CONF_DIR}/sink-virtual-surround.conf"
HRIR_SRC="${SCRIPT_DIR}/pipewire_hrir_atmos.wav"
log() { echo -e "\n\033[1;32m==> $1\033[0m\n"; }
# --- Directories ---
mkdir -p "$HRIR_DIR" "$CONF_DIR"
# --- HRIR file ---
if [ -f "$HRIR_FILE" ] && cmp -s "$HRIR_SRC" "$HRIR_FILE"; then
log "HRIR file already present"
else
log "Copying HeSuVi HRIR file"
cp "$HRIR_SRC" "$HRIR_FILE"
fi
# --- PipeWire config ---
read -r -d '' CONF_CONTENT << CONF || true
context.modules = [
{ name = libpipewire-module-filter-chain
flags = [ nofail ]
args = {
node.description = "Virtual Surround Sink"
media.name = "Virtual Surround Sink"
filter.graph = {
nodes = [
# duplicate inputs
{ type = builtin label = copy name = copyFL }
{ type = builtin label = copy name = copyFR }
{ type = builtin label = copy name = copyFC }
{ type = builtin label = copy name = copyRL }
{ type = builtin label = copy name = copyRR }
{ type = builtin label = copy name = copySL }
{ type = builtin label = copy name = copySR }
{ type = builtin label = copy name = copyLFE }
# apply hrir - HeSuVi 14-channel WAV
{ type = builtin label = convolver name = convFL_L config = { filename = "${HRIR_FILE}" channel = 0 } }
{ type = builtin label = convolver name = convFL_R config = { filename = "${HRIR_FILE}" channel = 1 } }
{ type = builtin label = convolver name = convSL_L config = { filename = "${HRIR_FILE}" channel = 2 } }
{ type = builtin label = convolver name = convSL_R config = { filename = "${HRIR_FILE}" channel = 3 } }
{ type = builtin label = convolver name = convRL_L config = { filename = "${HRIR_FILE}" channel = 4 } }
{ type = builtin label = convolver name = convRL_R config = { filename = "${HRIR_FILE}" channel = 5 } }
{ type = builtin label = convolver name = convFC_L config = { filename = "${HRIR_FILE}" channel = 6 } }
{ type = builtin label = convolver name = convFR_R config = { filename = "${HRIR_FILE}" channel = 7 } }
{ type = builtin label = convolver name = convFR_L config = { filename = "${HRIR_FILE}" channel = 8 } }
{ type = builtin label = convolver name = convSR_R config = { filename = "${HRIR_FILE}" channel = 9 } }
{ type = builtin label = convolver name = convSR_L config = { filename = "${HRIR_FILE}" channel = 10 } }
{ type = builtin label = convolver name = convRR_R config = { filename = "${HRIR_FILE}" channel = 11 } }
{ type = builtin label = convolver name = convRR_L config = { filename = "${HRIR_FILE}" channel = 12 } }
{ type = builtin label = convolver name = convFC_R config = { filename = "${HRIR_FILE}" channel = 13 } }
# treat LFE as FC
{ type = builtin label = convolver name = convLFE_L config = { filename = "${HRIR_FILE}" channel = 6 } }
{ type = builtin label = convolver name = convLFE_R config = { filename = "${HRIR_FILE}" channel = 13 } }
# stereo output
{ type = builtin label = mixer name = mixL }
{ type = builtin label = mixer name = mixR }
]
links = [
# input
{ output = "copyFL:Out" input="convFL_L:In" }
{ output = "copyFL:Out" input="convFL_R:In" }
{ output = "copySL:Out" input="convSL_L:In" }
{ output = "copySL:Out" input="convSL_R:In" }
{ output = "copyRL:Out" input="convRL_L:In" }
{ output = "copyRL:Out" input="convRL_R:In" }
{ output = "copyFC:Out" input="convFC_L:In" }
{ output = "copyFR:Out" input="convFR_R:In" }
{ output = "copyFR:Out" input="convFR_L:In" }
{ output = "copySR:Out" input="convSR_R:In" }
{ output = "copySR:Out" input="convSR_L:In" }
{ output = "copyRR:Out" input="convRR_R:In" }
{ output = "copyRR:Out" input="convRR_L:In" }
{ output = "copyFC:Out" input="convFC_R:In" }
{ output = "copyLFE:Out" input="convLFE_L:In" }
{ output = "copyLFE:Out" input="convLFE_R:In" }
# output
{ output = "convFL_L:Out" input="mixL:In 1" }
{ output = "convFL_R:Out" input="mixR:In 1" }
{ output = "convSL_L:Out" input="mixL:In 2" }
{ output = "convSL_R:Out" input="mixR:In 2" }
{ output = "convRL_L:Out" input="mixL:In 3" }
{ output = "convRL_R:Out" input="mixR:In 3" }
{ output = "convFC_L:Out" input="mixL:In 4" }
{ output = "convFC_R:Out" input="mixR:In 4" }
{ output = "convFR_R:Out" input="mixR:In 5" }
{ output = "convFR_L:Out" input="mixL:In 5" }
{ output = "convSR_R:Out" input="mixR:In 6" }
{ output = "convSR_L:Out" input="mixL:In 6" }
{ output = "convRR_R:Out" input="mixR:In 7" }
{ output = "convRR_L:Out" input="mixL:In 7" }
{ output = "convLFE_R:Out" input="mixR:In 8" }
{ output = "convLFE_L:Out" input="mixL:In 8" }
]
inputs = [ "copyFL:In" "copyFR:In" "copyFC:In" "copyLFE:In" "copyRL:In" "copyRR:In", "copySL:In", "copySR:In" ]
outputs = [ "mixL:Out" "mixR:Out" ]
}
capture.props = {
node.name = "effect_input.virtual-surround-7.1-hesuvi"
media.class = Audio/Sink
audio.channels = 8
audio.position = [ FL FR FC LFE RL RR SL SR ]
}
playback.props = {
node.name = "effect_output.virtual-surround-7.1-hesuvi"
node.passive = true
audio.channels = 2
audio.position = [ FL FR ]
}
}
}
]
CONF
NEEDS_RESTART=false
if [ -f "$CONF_FILE" ] && [ "$(cat "$CONF_FILE")" = "$CONF_CONTENT" ]; then
log "PipeWire config already up to date"
else
log "Writing PipeWire virtual surround config"
echo "$CONF_CONTENT" > "$CONF_FILE"
NEEDS_RESTART=true
fi
# --- Activate ---
if [ "$NEEDS_RESTART" = true ]; then
log "Restarting PipeWire"
systemctl --user restart pipewire wireplumber
sleep 1
fi
pw-metadata -n settings 0 clock.force-rate 48000 2>/dev/null || true
CURRENT_SINK=$(pactl get-default-sink 2>/dev/null || true)
if [ "$CURRENT_SINK" != "effect_input.virtual-surround-7.1-hesuvi" ]; then
log "Setting virtual surround as default sink"
pactl set-default-sink effect_input.virtual-surround-7.1-hesuvi
pw-metadata -n settings 0 default.configured.audio.sink '{"name":"effect_input.virtual-surround-7.1-hesuvi"}' 2>/dev/null || true
else
log "Virtual surround sink already default"
fi
log "Done. Verify with: pw-cli ls Node | grep vss"
#!/bin/bash
# /usr/lib/systemd/system-sleep/isp-suspend.sh
# List of modules to cycle
MODULES="amd_isp4_capture i2c_designware_amdisp pinctrl_amdisp amd_isp4"
LOGFILE="/tmp/isp-test.log"
echo "" > $LOGFILE
# Loop through modules for pre (unload) and post (load)
for mod in $MODULES; do
case "$1" in
pre)
echo "$(date) - PRE: attempting to remove $mod" >> $LOGFILE
if modinfo $mod >/dev/null 2>&1; then
modprobe -rq $mod 2>&1 | tee -a $LOGFILE
else
echo "$(date) - PRE: module $mod not found" >> $LOGFILE
fi
;;
post)
echo "$(date) - POST: attempting to load $mod" >> $LOGFILE
if modinfo $mod >/dev/null 2>&1; then
modprobe -q $mod 2>&1 | tee -a $LOGFILE
else
echo "$(date) - POST: module $mod not found" >> $LOGFILE
fi
;;
esac
done
echo "$(date) - $1 $2 hook finished" >> $LOGFILE
#!/usr/bin/env bash
set -euo pipefail
# HP ZBook Ultra G1a — Linux 7.0-rc5 kernel build
# Optimized for AMD Strix Halo ISP4 Support
# Supports: GRUB (Arch/CachyOS) and Limine (Omarchy)
# -------------------------------------------------------
SKIP_TO_BOOTLOADER=false
if [[ "${1:-}" == "--skip-to-bootloader" ]]; then
SKIP_TO_BOOTLOADER=true
fi
KERNEL_TAG="v7.0-rc5"
BRANCH_NAME="hp-zbook-ultra-g1a"
LOCALVERSION="-hp_zbook_ultra_g1a"
BUILD_DIR="${HOME}/.tools/kernel/linux-${BRANCH_NAME}"
NPROC=$(nproc)
PATCHES=(
"20260320201302.3490570-1-pratap.nirujogi@amd.com" # i2c resume-probe race fix
"20260311174251.3121654-1-pratap.nirujogi@amd.com" # ISP segfault fix
"20260318034842.1216536-1-pratap.nirujogi@amd.com" # ACPI bus MFD modprobe fix
"5986516.DvuYhMxLoT@rafael.j.wysocki" # ACPI video auxiliary bus
"20260320084146.200988-1-Bin.Du@amd.com" # AMD ISP4 capture driver (7 patches)
)
log() { echo -e "\n\033[1;32m==> $1\033[0m\n"; }
error() { echo -e "\n\033[1;31m[!] $1\033[0m\n"; exit 1; }
# --- Elevate once, keep sudo alive for the duration ---
log "Requesting sudo credentials"
sudo -v || error "sudo authentication failed"
( while kill -0 $$ 2>/dev/null; do sudo -n -v; sleep 60; done ) &
SUDO_KEEPALIVE_PID=$!
trap 'kill $SUDO_KEEPALIVE_PID 2>/dev/null' EXIT
# --- Install build dependencies ---
sudo pacman -S --needed --noconfirm b4 python-requests base-devel bc
# --- Detect bootloader ---
if command -v limine-mkinitcpio >/dev/null 2>&1; then
BOOTLOADER="limine"
elif command -v grub-mkconfig >/dev/null 2>&1; then
BOOTLOADER="grub"
else
error "No supported bootloader found (expected grub-mkconfig or limine-mkinitcpio)"
fi
log "Detected bootloader: ${BOOTLOADER}"
if [ "$SKIP_TO_BOOTLOADER" = true ]; then
log "Skipping to bootloader install"
[ -d "$BUILD_DIR" ] || error "Build directory not found: ${BUILD_DIR}"
cd "$BUILD_DIR"
export LOCALVERSION=""
KERNELRELEASE=$(make -s kernelrelease)
log "Target Kernel Release: ${KERNELRELEASE}"
else
# --- Cleanup Old Attempts ---
log "Cleaning up old custom kernel files"
sudo rm -f /boot/*"${LOCALVERSION}"*
if [ "$BOOTLOADER" = "limine" ]; then
sudo rm -f /boot/EFI/Linux/*_"${BRANCH_NAME}"*.efi
sudo limine-entry-tool --remove-uki "${BRANCH_NAME}" --quiet 2>/dev/null || true
fi
if [ -d "/usr/lib/modules" ]; then
for d in /usr/lib/modules/*"${LOCALVERSION}" /usr/lib/modules/.old/*"${LOCALVERSION}"*; do
[ -d "$d" ] && sudo rm -rf "$d"
done
fi
# --- Prerequisites ---
log "Checking prerequisites"
for cmd in git b4 make gcc mkinitcpio zstd; do
command -v "$cmd" >/dev/null || error "Missing dependency: $cmd"
done
# --- Clone/Setup ---
if [ -d "$BUILD_DIR" ]; then
log "Build directory exists. Re-cloning for a clean slate..."
rm -rf "$BUILD_DIR"
fi
log "Cloning Linux ${KERNEL_TAG}"
git clone --depth 1 --branch "$KERNEL_TAG" \
https://github.com/torvalds/linux.git "$BUILD_DIR" || \
git clone --depth 1 https://github.com/torvalds/linux.git "$BUILD_DIR"
cd "$BUILD_DIR"
git checkout -b "$BRANCH_NAME" "$KERNEL_TAG" || git checkout -b "$BRANCH_NAME"
# --- Apply patches ---
log "Applying patches via b4"
for msgid in "${PATCHES[@]}"; do
echo "--- Fetching and applying: $msgid"
# Using pipe to avoid mbox file management
if b4 am --no-cover -o - "$msgid" | git am; then
echo " Applied successfully."
else
echo " Conflict or already applied — skipping."
git am --abort 2>/dev/null || true
fi
done
# --- Configure ---
log "Configuring kernel"
RUNNING_KERNEL=$(uname -r)
if [ -f "/usr/lib/modules/${RUNNING_KERNEL}/build/.config" ]; then
cp "/usr/lib/modules/${RUNNING_KERNEL}/build/.config" .config
elif [ -f /proc/config.gz ]; then
zcat /proc/config.gz > .config
else
error "No base config found."
fi
# Clean and set localversion
sed -i '/CONFIG_LOCALVERSION/d' .config
echo "CONFIG_LOCALVERSION=\"${LOCALVERSION}\"" >> .config
echo "CONFIG_LOCALVERSION_AUTO=n" >> .config
echo "CONFIG_VIDEO_AMD_ISP4_CAPTURE=m" >> .config
# Prevent '+' suffix due to patched (dirty) git state
# Setting LOCALVERSION in env (even empty) tells scripts/setlocalversion
# to skip the "+" append for untagged commits (line 201-209).
# CONFIG_LOCALVERSION in .config already carries our suffix.
touch .scmversion
export LOCALVERSION=""
make olddefconfig
KERNELRELEASE=$(make -s kernelrelease)
log "Target Kernel Release: ${KERNELRELEASE}"
# --- Build ---
log "Building kernel (${NPROC} threads)"
make -j"${NPROC}"
fi # end SKIP_TO_BOOTLOADER
# --- Install ---
PRE_ID=""
if command -v snapper >/dev/null; then
log "Creating pre-install snapshot"
# Fix: Correctly capture the snapshot number
PRE_ID=$(sudo snapper create -d "before ${KERNELRELEASE} install" -t pre --print-number)
fi
log "Installing modules"
sudo make modules_install
if [ "$BOOTLOADER" = "limine" ]; then
log "Installing kernel image (vmlinuz for limine UKI)"
sudo install -Dm644 arch/x86/boot/bzImage "/usr/lib/modules/${KERNELRELEASE}/vmlinuz"
log "Creating pkgbase for custom kernel"
echo "${BRANCH_NAME}" | sudo tee "/usr/lib/modules/${KERNELRELEASE}/pkgbase" > /dev/null
# limine-mkinitcpio skips kernels not owned by pacman (pacman -Qqo check).
# Build the UKI and register the boot entry manually instead.
ESP_PATH=$(bootctl --print-esp-path 2>/dev/null || echo "/boot")
MACHINE_ID=$(cat /etc/machine-id)
UKI_DIR="${ESP_PATH}/EFI/Linux"
UKI_PATH="${UKI_DIR}/${MACHINE_ID}_${BRANCH_NAME}.efi"
TMP_UKI="/tmp/staging_custom_uki.efi"
log "Building UKI for custom kernel"
sudo install -dm700 "${UKI_DIR}"
# Get cmdline from the default kernel's existing entry
CMDLINE=$(sudo grep -m1 'cmdline:' /boot/limine.conf | sed 's/.*cmdline:\s*//')
echo "${CMDLINE}" | sudo tee /tmp/custom-kernel-cmdline > /dev/null
sudo mkinitcpio --kernel "${KERNELRELEASE}" --uki "${TMP_UKI}" --cmdline /tmp/custom-kernel-cmdline
sudo mv -f "${TMP_UKI}" "${UKI_PATH}"
sudo rm -f /tmp/custom-kernel-cmdline
log "UKI stored in ${UKI_PATH}"
log "Registering boot entry with limine-entry-tool"
sudo limine-entry-tool --add-uki "${BRANCH_NAME}" "${UKI_PATH}" \
--comment "Kernel version: ${KERNELRELEASE}"
log "Setting custom kernel as default boot entry"
LIMINE_CONF="${ESP_PATH}/limine.conf"
if [ -f "${LIMINE_CONF}" ]; then
# Find the 1-based index of our entry (last /:/ section in the config)
ENTRY_COUNT=$(sudo grep -c '^/:' "${LIMINE_CONF}")
sudo sed -i "s/^default_entry:.*/default_entry:${ENTRY_COUNT}/" "${LIMINE_CONF}"
if ! sudo grep -q '^default_entry:' "${LIMINE_CONF}"; then
sudo sed -i "1i default_entry:${ENTRY_COUNT}" "${LIMINE_CONF}"
fi
fi
else
log "Installing kernel image"
sudo cp arch/x86/boot/bzImage "/boot/vmlinuz-${KERNELRELEASE}"
log "Generating initramfs"
sudo mkinitcpio -k "${KERNELRELEASE}" -g "/boot/initramfs-${KERNELRELEASE}.img"
log "Updating GRUB"
sudo grub-mkconfig -o /boot/grub/grub.cfg
fi
if [ -n "$PRE_ID" ]; then
log "Creating post-install snapshot (linked to ID: $PRE_ID)"
sudo snapper create -d "after ${KERNELRELEASE} install" -t post --pre-number "$PRE_ID"
fi
# --- ISP suspend/resume hook ---
ISP_SUSPEND_SRC="${HOME}/.tools/kernel/isp-suspend"
ISP_SUSPEND_DST="/usr/lib/systemd/system-sleep/isp-suspend"
if [ ! -f "$ISP_SUSPEND_DST" ] || ! cmp -s "$ISP_SUSPEND_SRC" "$ISP_SUSPEND_DST"; then
log "Installing ISP suspend/resume hook"
sudo cp "$ISP_SUSPEND_SRC" "$ISP_SUSPEND_DST"
sudo chmod +x "$ISP_SUSPEND_DST"
else
log "ISP suspend/resume hook already up to date"
fi
# --- Firmware Check ---
log "Checking for ISP4 firmware"
if [ -f "/usr/lib/firmware/amdgpu/isp_4_1_1.bin.zst" ] && [ ! -f "/usr/lib/firmware/amdgpu/isp_4_1_1.bin" ]; then
echo "Decompressing ISP firmware for better compatibility..."
sudo zstd -d /usr/lib/firmware/amdgpu/isp_4_1_1.bin.zst -o /usr/lib/firmware/amdgpu/isp_4_1_1.bin
fi
log "Done! Reboot and select ${KERNELRELEASE} in ${BOOTLOADER^^}."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment