Created
July 7, 2026 17:47
-
-
Save patcon/a80adb97920dc5378b1bd265ba683450 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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