So I bought a Lenovo ThinkPad X9-15 Gen 1. Beautiful machine. Fast. Thin. Running Ubuntu 24.04 LTS like a dream — except for one thing: the webcam was completely dead. Black screen. No device. Nothing.
Turns out Intel decided that standard USB webcams are too easy, and built a brand-new MIPI camera architecture into this laptop that needs a completely non-standard software stack just to produce a mediocre 30fps image. Fantastic. Here’s the full story of what it took to get it working.
Why the Camera Doesn’t Work Out of the Box
The X9-15 Gen 1 (model 21Q6) uses a Sony IMX471 sensor wired through an Intel IPU7 (Image Processing Unit) and an IVSC (Intel Visual Sensing Controller) privacy bridge. This is nothing like a normal USB webcam, which just appears as /dev/video0 and works with any app.
The full data path looks like this:
IMX471 sensor
|
I²C/CSI lanes
|
IVSC (privacy controller)
|
Intel IPU7 (PCI device)
|
v4l2 media graph (subdevs + capture nodes)
|
libcamhal (Intel proprietary userspace HAL)
|
gstreamer icamerasrc
|
v4l2-relayd (pumps the stream into a v4l2loopback device)
|
/dev/video0 ← what your apps actually see
Every single layer needs to be present and correctly configured. If one piece is missing or loads in the wrong order, the camera fails silently. And by default, most of these pieces are either absent or misconfigured on a standard Ubuntu install.
The Standard Recipe — Necessary but Not Sufficient
The official starting point that most guides point to goes like this:
- Install the OEM meta package:
oem-sutton-dana-meta - Add the Lenovo Canonical archive repository
- Install
ubuntu-oem-keyringandlibcamhal-ipu7x - Reboot
I ran this. Rebooted. Camera still showed a black screen, then the privacy LED briefly flickered, and the camera app closed itself. Progress? Kind of. Actually no.
Debugging: Layer by Layer
The HAL Knew Nothing About the Sensor
Running gst-inspect-1.0 icamerasrc revealed the first real clue:
CamHAL: getAvailableSensors, Found IPU: IPU7
CamHAL: parseSensors: No sensors available
The HAL knew it was on an IPU7 system, but found zero sensors. The IMX471 was invisible to it.
PipeWire Was Also Complaining
The WirePlumber log had this gem:
SPA handle 'api.libcamera.enum.manager' could not be loaded; is it installed?
Fix: install libspa-0.2-libcamera. This didn’t fix the black screen directly, but it was needed for PipeWire to properly enumerate libcamera devices later down the line.
Missing Kernel Module Packages
Here’s where it gets annoying. The oem-sutton-dana-meta package recommends — but does not depend on — several critical kernel module packages. By default, apt doesn’t install Recommends in all configurations, and even when it does, some were simply absent:
linux-modules-vision-oem-24.04d— containsivsc_csiandivsc_ace, the IVSC bridge driverslinux-modules-usbio-oem-24.04d— some sensors need this
Installing them helped, but the IVSC modules still didn’t auto-load because no hardware match fired on boot. Manual modprobe ivsc_csi finally got the HAL to advance:
CamHAL: parseSensors: I will Load config file: sensors/imx471-uf.json
CamHAL: parseSensors, sensors/imx471-uf.json loaded!
So the sensor profile was already on disk (installed by libcamhal-ipu7x-common). The problem was driver bind and module load order — not missing HAL config.
The IMX471 Driver Lives in the IPU6 Package
This is the most baffling thing I encountered. Searching for the sensor driver:
apt-file search imx471.ko
Result:
linux-modules-ipu6-6.17.0-1017-oem: /lib/modules/.../ipu6/imx471.ko.zst
The IMX471 driver — for an IPU7 laptop — is packaged inside linux-modules-ipu6-oem-24.04d. Canonical packaged all the i2c sensor drivers (imx*, ov*, hi*, etc.) in the IPU6 modules package, regardless of which IPU generation the laptop actually uses. Install the wrong generation’s modules, and you simply have no sensor driver at all.
After installing linux-modules-ipu6-oem-24.04d and rebooting: the privacy LED lit up when the camera app opened. The sensor was finally being powered on! But the app still went black and crashed. Getting closer.
Module Load Order — The Final Boss
The kernel logs were now showing this, on repeat:
imx471 i2c-SONY471A:00: Start streaming
imx471 i2c-SONY471A:00: imx471 power off
The driver was binding at the i2c layer, but the v4l2 media link was never established. The reason: ipu_bridge (the component that stitches the sensor into the IPU7 media graph) does its enumeration at probe time. If imx471, ivsc_csi, and ivsc_ace aren’t already loaded when intel_ipu7 probes, the bridge moves on and the window closes. Any modules that load later get bound at the i2c layer but never wired into the media graph.
The fix: force all three modules to load early, before the IPU7 driver probes, by adding them to /etc/modules-load.d/ and to the initramfs:
# /etc/modules-load.d/ipu7-camera.conf
ivsc_ace
ivsc_csi
imx471
Then rebuild the initramfs with sudo update-initramfs -u and reboot.
The Camera Was Working — the Test App Wasn’t
After all of the above, I was still seeing a black screen in the GNOME camera app. I spent more time staring at HAL log errors before realising I should just test in a real application. Opened Google Meet.
It worked immediately.
The GNOME camera app simply doesn’t handle the v4l2loopback device correctly on this stack. It opens it, fails to negotiate a format, and exits. Browser-based apps and anything that goes through PipeWire work fine. Half my debugging time after step 6 was wasted staring at errors from a broken test tool, not a broken camera.
Oh, and It Was Upside Down
The IMX471 is physically mounted inverted in the X9-15 chassis. The image was rotated 180 degrees. Not ideal for video calls.
The fix lives in /etc/v4l2-relayd.d/default.conf — not in /etc/default/v4l2-relayd (that file is ignored by the actual running process) and not in a systemd drop-in override (the base unit is a oneshot /bin/true). The real config is read by a systemd generator that creates per-file service instances:
# /etc/v4l2-relayd.d/default.conf
VIDEOSRC=icamerasrc buffer-count=7 ! videoflip method=rotate-180
Restart v4l2-relayd@default.service and the image is the right way up.
What Was Actually Missing
To summarise what a vanilla post-install with the standard recipe was missing:
| Missing piece | Package / fix |
|---|---|
| IMX471 sensor driver | linux-modules-ipu6-oem-24.04d (yes, IPU6 on an IPU7 machine) |
| IVSC bridge driver | linux-modules-vision-oem-24.04d |
| USBIO driver | linux-modules-usbio-oem-24.04d |
| PipeWire libcamera SPA | libspa-0.2-libcamera |
| Early module load order | /etc/modules-load.d/ipu7-camera.conf + initramfs |
| Upside-down image | videoflip method=rotate-180 in /etc/v4l2-relayd.d/default.conf |
The Complete Fix Script
If you just want to run one script and be done with it, here it is. Run it as your normal user (it will sudo when needed), then reboot once.
#!/usr/bin/env bash
# Complete camera fix for Lenovo ThinkPad X9-15 Gen 1 (21Q6) on Ubuntu 24.04.
# Run once, then reboot.
set -euo pipefail
if [ "$(id -u)" -eq 0 ]; then
echo "Run as your normal user (script will sudo when needed)." >&2
exit 1
fi
echo "==> [1/7] System update..."
sudo apt update
sudo apt upgrade -y
echo "==> [2/7] Lenovo OEM metapackage + archive..."
sudo apt install -y oem-sutton-dana-meta
if ! grep -rq 'lenovo.archive.canonical.com' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null; then
sudo add-apt-repository -y "deb http://lenovo.archive.canonical.com/ noble sutton"
fi
sudo apt install -y ubuntu-oem-keyring
sudo apt update
echo "==> [3/7] IPU7 camera HAL + sensor drivers + PipeWire libcamera SPA..."
sudo apt install -y \
libcamhal-ipu7x \
libspa-0.2-libcamera \
linux-modules-ipu7-oem-24.04d \
linux-modules-ipu6-oem-24.04d \
linux-modules-vision-oem-24.04d \
linux-modules-usbio-oem-24.04d
echo "==> [4/7] Force IVSC + IMX471 to load early at boot..."
sudo tee /etc/modules-load.d/ipu7-camera.conf >/dev/null <<'EOF'
ivsc_ace
ivsc_csi
imx471
EOF
echo "==> [5/7] Add modules to initramfs..."
INITRAMFS_TARGET=/etc/initramfs-tools/modules
if [ -d /etc/initramfs-tools/modules.d ]; then
sudo tee /etc/initramfs-tools/modules.d/ipu7-camera >/dev/null <<'EOF'
ivsc_ace
ivsc_csi
imx471
EOF
else
for m in ivsc_ace ivsc_csi imx471; do
grep -qx "$m" "$INITRAMFS_TARGET" 2>/dev/null || echo "$m" | sudo tee -a "$INITRAMFS_TARGET" >/dev/null
done
fi
echo "==> [6/7] Configure v4l2-relayd to flip 180 (camera is physically inverted)..."
DEFAULT_CONF=/etc/v4l2-relayd.d/default.conf
if [ -f "$DEFAULT_CONF" ] && ! grep -q 'videoflip' "$DEFAULT_CONF"; then
sudo cp "$DEFAULT_CONF" "${DEFAULT_CONF}.bak"
sudo sed -i 's|^VIDEOSRC=.*|VIDEOSRC=icamerasrc buffer-count=7 ! videoflip method=rotate-180|' "$DEFAULT_CONF"
fi
echo "==> [7/7] Rebuild initramfs..."
sudo update-initramfs -u
echo
echo "All done. Reboot now, then test in Google Meet or a browser — not the GNOME camera app."
echo "Note: 30 fps is the hardware cap for this sensor."
Two Gotchas Worth Highlighting
Don’t test with the GNOME camera app. It fails on this v4l2loopback stack. Use Google Meet, Microsoft Teams, or any browser-based video call. Those work correctly.
30 fps is the ceiling. The IMX471’s HAL profile only declares 15 fps and 30 fps modes. The sensor itself is an 8MP stills sensor — its video modes top out at 30 fps. Don’t bother trying to push it higher.

Leave a Reply