Skip to content

Instantly share code, notes, and snippets.

@patcon
Created July 7, 2026 17:47
Show Gist options
  • Select an option

  • Save patcon/a80adb97920dc5378b1bd265ba683450 to your computer and use it in GitHub Desktop.

Select an option

Save patcon/a80adb97920dc5378b1bd265ba683450 to your computer and use it in GitHub Desktop.
#!/bin/bash
# =============================================================================
# Surface Pro 6 (linux-surface, Ubuntu 24.04) — IPU3 camera fix + Chrome bridge
# =============================================================================
#
# SYMPTOM: v4l2-ctl --list-devices shows ipu3-imgu / Intel IPU3 CIO2 devices,
# but `cam -l` (libcamera) reports zero cameras, and browsers/apps report
# "no camera found" — despite sensors, firmware, and drivers all loading
# cleanly in dmesg.
#
# ROOT CAUSE: kernel regression in the ipu_bridge code that builds the
# sensor -> CSI-2 receiver media links from ACPI data. Confirmed via
# `media-ctl -d /dev/media0 -p`: sensor entities show "0 link" — the
# link object is never created, so libcamera's IPU3 pipeline handler has
# nothing to register, regardless of libcamera version (tested & ruled
# out: apt's libcamera0.2, a 0.7.1 PPA build, and libcamera built fresh
# from git master — all fail identically on the affected kernel).
#
# CONFIRMED BROKEN ON: 6.19.8-surface-3
# CONFIRMED WORKING ON: 6.17.13-surface-2
#
# This script installs the known-working kernel, holds it against being
# superseded, installs stock libcamera packages (no PPA/source build
# needed), and sets up a Chrome-specific bridge since Chrome has no
# libcamera/PipeWire camera support at all.
#
# Run section by section rather than blindly executing top to bottom —
# especially the kernel install, which requires a reboot partway through.
# =============================================================================
set -e
## ---------------------------------------------------------------------------
## SECTION 1: Install the known-working kernel
## ---------------------------------------------------------------------------
KVER="6.17.13-surface-2"
KTAG="debian-6.17.13-2"
BASEURL="https://github.com/linux-surface/linux-surface/releases/download/${KTAG}"
mkdir -p ~/surface-kernel-fix && cd ~/surface-kernel-fix
wget "${BASEURL}/linux-image-${KVER}_${KVER}_amd64.deb"
wget "${BASEURL}/linux-headers-${KVER}_${KVER}_amd64.deb"
wget "${BASEURL}/linux-image-surface_${KVER}_amd64.deb"
wget "${BASEURL}/linux-headers-surface_${KVER}_amd64.deb"
sudo dpkg -i ./*"${KVER}"*.deb
sudo update-grub
echo ">>> Installed ${KVER}. Reboot now and select it (it should be the new"
echo ">>> default). Confirm with 'uname -r', THEN continue with section 2."
echo ">>> Suggested: sudo reboot"
## ---------------------------------------------------------------------------
## SECTION 2: Hold this kernel so apt/kernel updates can't silently swap it
## ---------------------------------------------------------------------------
# Run this AFTER rebooting into 6.17.13-surface-2 and confirming via uname -r.
# Replace <NEWER_VERSION> with whatever kernel version(s) you want to prevent
# apt from defaulting back to (e.g. 6.19.8-surface-3).
# sudo apt-mark hold linux-image-surface linux-headers-surface \
# linux-image-<NEWER_VERSION>-surface-3 linux-headers-<NEWER_VERSION>-surface-3
# Also pin GRUB's persistent default (not just position 0, which shifts as
# kernels are added/removed):
# awk -F"'" '/menuentry / {print $2}' /boot/grub/grub.cfg # find exact entry name
# sudo sed -i 's/^GRUB_DEFAULT=.*/GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.17.13-surface-2"/' /etc/default/grub
# sudo update-grub
## ---------------------------------------------------------------------------
## SECTION 3: Stock libcamera packages (no PPA / no source build required)
## ---------------------------------------------------------------------------
sudo apt update
sudo apt install -y libcamera-tools libcamera-ipa pipewire-libcamera
systemctl --user restart pipewire wireplumber
echo ">>> Verify with: cam -l"
echo ">>> Should list your internal front/back cameras. IPA 'yaml not found'"
echo ">>> warnings are cosmetic (uncalibrated tuning) and can be ignored."
## ---------------------------------------------------------------------------
## SECTION 4: Chrome bridge (Chrome has no libcamera/PipeWire camera support)
## ---------------------------------------------------------------------------
sudo apt install -y v4l2loopback-dkms gstreamer1.0-tools \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad
# Auto-load the virtual camera device at boot:
echo 'options v4l2loopback video_nr=42 card_label="VirtualCam" exclusive_caps=1' \
| sudo tee /etc/modprobe.d/v4l2loopback.conf
echo 'v4l2loopback' | sudo tee /etc/modules-load.d/v4l2loopback.conf
sudo modprobe v4l2loopback video_nr=42 card_label="VirtualCam" exclusive_caps=1
# Bridge script. NOTE the double backslash before _SB_ — gst-launch's parser
# eats one level of escaping, so this must be \\_SB_... to survive as \_SB_...
# when it reaches libcamera. Get your exact camera-name string from `cam -l`.
mkdir -p ~/.local/bin
cat > ~/.local/bin/virtualcam-bridge.sh << 'SCRIPT_EOF'
#!/bin/bash
exec gst-launch-1.0 libcamerasrc camera-name='\\_SB_.PCI0.I2C2.CAMF' ! \
video/x-raw,width=1280,height=720,framerate=30/1,format=NV12 ! videoconvert ! \
video/x-raw,format=YUY2 ! videoconvert ! v4l2sink device=/dev/video42
SCRIPT_EOF
chmod +x ~/.local/bin/virtualcam-bridge.sh
# systemd --user service to keep the bridge running automatically at login:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/virtualcam-bridge.service << 'SERVICE_EOF'
[Unit]
Description=Virtual camera bridge (libcamera -> v4l2loopback) for Chrome
After=graphical-session.target
[Service]
ExecStart=%h/.local/bin/virtualcam-bridge.sh
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.target
SERVICE_EOF
systemctl --user daemon-reload
systemctl --user enable --now virtualcam-bridge.service
echo ">>> Verify: systemctl --user status virtualcam-bridge.service"
echo ">>> Then in Chrome: chrome://settings/content/camera -> select 'VirtualCam'"
## ---------------------------------------------------------------------------
## Diagnostic commands used to isolate the root cause (kept for reference)
## ---------------------------------------------------------------------------
# dmesg | grep -iE "ipu3|ov5693|ov8865|cio2|regulator"
# media-ctl -d /dev/media0 -p # shows sensor entities with "0 link"
# LIBCAMERA_LOG_LEVELS=0 cam -l # pipeline handler match trace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment