Skip to content

Instantly share code, notes, and snippets.

@miku
Created May 26, 2026 23:21
Show Gist options
  • Select an option

  • Save miku/fa95a587ea040d8386ecd49ea87ab964 to your computer and use it in GitHub Desktop.

Select an option

Save miku/fa95a587ea040d8386ecd49ea87ab964 to your computer and use it in GitHub Desktop.
Fix Camera on Lunar Lake Lenovo X1 Carbon Gen 13 Laptop (debian 13)

Enabling the MIPI Camera on a Lunar Lake ThinkPad (Intel IPU7)

A step-by-step record of how the front MIPI camera was brought up on an Intel Lunar Lake laptop, including the problems hit on a modern toolchain and the reasoning behind each fix. Anyone reproducing this on a similar machine should be able to follow it top to bottom.


1. Background: what the IPU7 camera stack is

Unlike a USB webcam (which is a single self-contained UVC device), a laptop MIPI camera is a pipeline of cooperating parts:

  • A bare image sensor (here an OmniVision OV08X40) connected over a MIPI CSI-2 bus. It emits raw Bayer pixels and exposes controls (exposure, gain, …) over I²C. It produces no usable image on its own.
  • Intel's IPU7 (Image Processing Unit), a PCI device (8086:645d) with two halves:
    • ISYS (input system) — receives the CSI-2 stream and writes raw frames to memory.
    • PSYS (processing system) — the ISP that turns raw Bayer into a viewable image (demosaic, white balance, noise reduction, NV12/YUY2 output). PSYS is driven by firmware and proprietary tuning data.
  • A userspace HAL (libcamhal) plus 3A/imaging libraries that run the auto-exposure / auto-white-balance / auto-focus loops and program the ISP.
  • A GStreamer source element (icamerasrc) so ordinary applications can consume frames.

Because of that split, Intel ships the camera support as four repositories, and all four are required for a working picture:

Repo Provides Layer
ipu7-drivers kernel modules for IPU + sensor kernel
ipu7-camera-bins IPU firmware + proprietary imaging .sos kernel + userspace
ipu7-camera-hal libcamhal image-processing HAL userspace
icamerasrc GStreamer icamerasrc plugin userspace

Data flows: sensor → CSI-2 → IPU7 ISYS → IPU7 PSYS (ISP) → libcamhal → icamerasrc → your app.


2. Test environment

These exact versions matter, because most of the fixes below exist only because the toolchain is newer than what the 2022-era Intel code expects.

Component Version / value
Machine Lenovo ThinkPad, Intel Lunar Lake
IPU IPU7, PCI 00:05.0 8086:645d (rev 04)
Sensor OmniVision OV08X40 (kernel driver ov08x40, config ov08x40-uf.json, I²C 13-0010 → CSI port 0)
Distro kernel 7.0.9+deb14-amd64 (Debian)
Secure Boot disabled (so module signing is moot)
kernel.dmesg_restrict 1 (so dmesg needs sudo)
DKMS 3.2.2
CMake 4.3.2
GCC 15.2.0
GStreamer 1.28.3

All four repos were checked out under ~/code/intel/: ipu7-drivers, ipu7-camera-bins, ipu7-camera-hal, icamerasrc.

Note on the staging driver. Kernel 7.0.9 already ships an in-tree staging copy of intel_ipu7 + intel_ipu7_isys (drivers/staging/media/ipu7/), but it does not include PSYS. So the staging driver can capture raw frames but cannot run the ISP. That is why the out-of-tree ipu7-drivers build (which does include PSYS) is required, and why the two must not be mixed (see §8).


3. Repo 1 — ipu7-drivers (kernel modules via DKMS)

3.1 Fix A: build break on kernel 7.0 (v4l2_subdev_stream_config)

The first dkms build failed with:

ipu7-isys-video.c: error: invalid use of undefined type 'struct v4l2_subdev_stream_config'
  &state->stream_configs.configs[i];

Why: In recent kernels the V4L2 streams API made struct v4l2_subdev_stream_config private — it was moved out of the public <media/v4l2-subdev.h> into a core-internal header, leaving only a forward declaration. Out-of-tree drivers can no longer poke at state->stream_configs.configs[i].pad/.stream.

get_remote_pad_stream() only wanted "the stream id carried on a given source pad." The public, stable way to get that is the routing table (state->routing + the for_each_active_route() iterator), which has existed since the streams API was introduced (v6.3) and is already used elsewhere in this very file. So the fix is a like-for-like rewrite, not a behavioural change.

drivers/media/pci/intel/ipu7/ipu7-isys-video.c, in get_remote_pad_stream():

 	struct v4l2_subdev_state *state;
+	struct v4l2_subdev_route *route;
 	struct v4l2_subdev *sd;
 	u32 stream_id = 0;
-	unsigned int i;

 	sd = media_entity_to_v4l2_subdev(r_pad->entity);
 	state = v4l2_subdev_lock_and_get_active_state(sd);
 	if (!state)
 		return 0;

-	for (i = 0; i < state->stream_configs.num_configs; i++) {
-		struct v4l2_subdev_stream_config *cfg =
-			&state->stream_configs.configs[i];
-		if (cfg->pad == r_pad->index) {
-			stream_id = cfg->stream;
-			break;
-		}
-	}
+	for_each_active_route(&state->routing, route) {
+		if (route->source_pad == r_pad->index) {
+			stream_id = route->source_stream;
+			break;
+		}
+	}

 	v4l2_subdev_unlock_state(state);

The remote pad here is a source pad of the upstream subdevice, so it matches a route's source_pad, and the value we want is that route's source_stream.

3.2 Fix B: incomplete dkms.conf (missing ACPI helper modules)

After Fix A the modules compiled, but once the freshly built intel_ipu7 actually loaded (after a reboot — see §8) it failed with:

intel_ipu7: Unknown symbol ipu_get_acpi_devices (err -2)

Why: The repo Makefile builds with CONFIG_INTEL_IPU7_ACPI = m, so ipu7.c compiles in a call to ipu_get_acpi_devices():

#if IS_ENABLED(CONFIG_INTEL_IPU7_ACPI)
	ipu_get_acpi_devices(&dev->platform_data);
#endif

That symbol is exported by drivers/media/platform/intel/ipu-acpi.c. The build produces six modules, but the stock dkms.conf only installs three (intel-ipu7, intel-ipu7-isys, intel-ipu7-psys) — it never ships the three ACPI helpers (ipu-acpi, ipu-acpi-common, ipu-acpi-pdata). So intel_ipu7 referenced a symbol that no installed module provided.

Could we instead disable ACPI? No. When CONFIG_INTEL_IPU7_ACPI is off the Makefile builds ipu7-fpga-pdata.o as obj-y, which is an in-tree-only artifact (orphaned in an external/DKMS build). For out-of-tree builds the intended configuration is ACPI=m. The real bug is purely the missing install entries, so the correct fix is to package the three ACPI modules.

Append to dkms.conf:

BUILT_MODULE_NAME[3]="ipu-acpi"
BUILT_MODULE_LOCATION[3]="drivers/media/platform/intel"
DEST_MODULE_LOCATION[3]="/updates"

BUILT_MODULE_NAME[4]="ipu-acpi-common"
BUILT_MODULE_LOCATION[4]="drivers/media/platform/intel"
DEST_MODULE_LOCATION[4]="/updates"

BUILT_MODULE_NAME[5]="ipu-acpi-pdata"
BUILT_MODULE_LOCATION[5]="drivers/media/platform/intel"
DEST_MODULE_LOCATION[5]="/updates"

The three are interdependent (ipu-acpiipu-acpi-pdataipu-acpi-common); depmod computes the load order, and since intel_ipu7 now depends on ipu_get_acpi_devices, the ACPI modules autoload before it at boot.

3.3 Build & install

cd ~/code/intel/ipu7-drivers
# (apply Fix A to ipu7-isys-video.c and Fix B to dkms.conf first)

sudo dkms add .
sudo dkms autoinstall ipu7-drivers/0.0.0

A successful run signs and installs six .kos into /lib/modules/$(uname -r)/updates/dkms/ and runs depmod. Because DKMS "archives for uninstallation" the in-kernel staging intel-ipu7*.ko, its modules now take precedence over staging.

If you edited dkms.conf after a previous dkms add, refresh the registered copy and rebuild:

sudo cp ~/code/intel/ipu7-drivers/dkms.conf /usr/src/ipu7-drivers-0.0.0/dkms.conf
sudo dkms remove ipu7-drivers/0.0.0 --all
sudo dkms install ipu7-drivers/0.0.0

4. Repo 2 — ipu7-camera-bins (firmware + imaging libraries)

This repo is prebuilt binaries; you "install" it by copying files into the system. Per its README "Deployment" section:

cd ~/code/intel/ipu7-camera-bins

# Firmware (read at IPU probe time)
sudo mkdir -p /lib/firmware/intel/ipu
sudo cp -r lib/firmware/intel/ipu/*.bin /lib/firmware/intel/ipu/

# Proprietary imaging libraries (libia_*, libbroxton_ia_pal, ...) — preserve symlinks with -P
sudo cp -P lib/lib* /usr/lib/

# Headers + pkg-config metadata
sudo mkdir -p /usr/include/ipu7 /usr/lib/pkgconfig
sudo cp -r include/* /usr/include/
sudo cp -r lib/pkgconfig/* /usr/lib/pkgconfig/

sudo ldconfig

Why all of it matters: A common trap is to copy only the firmware (so the kernel side works) and forget the libraries. The HAL then fails to link with errors like cannot find -lia_cca-ipu7x / -lia_log-ipu7x. The .pc files hard-code prefix=/usr, so the libraries must land in /usr/lib; copying the .pc files into /usr/lib/pkgconfig also means you don't have to fiddle with PKG_CONFIG_PATH. -P preserves the libfoo.so → libfoo.so.0 symlinks the linker needs.


5. Repo 3 — ipu7-camera-hal (libcamhal)

5.1 Fix C: CMake 4.x rejects the old minimum version

cd ~/code/intel/ipu7-camera-hal
mkdir -p build && cd build

cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib \
      -DBUILD_CAMHAL_ADAPTOR=ON -DBUILD_CAMHAL_PLUGIN=ON \
      -DIPU_VERSIONS="ipu7x;ipu75xa" \
      -DUSE_STATIC_GRAPH=ON -DUSE_STATIC_GRAPH_AUTOGEN=ON \
      ..

cmake --build . -j"$(nproc)"
sudo cmake --install .
sudo ldconfig

Why -DCMAKE_POLICY_VERSION_MINIMUM=3.5: The project declares cmake_minimum_required(VERSION 2.8). CMake 4.x removed compatibility with policy versions below 3.5 and aborts configuration. This flag tells CMake to assume a 3.5 policy baseline and continue. (The build itself produces only warnings under GCC 15 — no source changes needed.)

-DIPU_VERSIONS="ipu7x;ipu75xa" builds tuning for both IPU7 variants; -DBUILD_CAMHAL_ADAPTOR=ON installs the adaptor as libcamhal.so.

Result: /usr/lib/libcamhal.so (+ libcamhal/plugins/).


6. Repo 4 — icamerasrc (GStreamer plugin)

6.1 Fix D: use the correct branch

cd ~/code/intel/icamerasrc
git checkout icamerasrc_slim_api      # <-- REQUIRED; default 'master' has no build files

Why: The README explicitly calls for the icamerasrc_slim_api branch. The default master branch does not even contain the autotools build files; the slim-API branch is the one wired up for the IPU7 HAL.

6.2 Build deps and build

# GStreamer dev was already present (pkg-config found gstreamer-1.0); the CLI
# tools are a separate package:
sudo apt install gstreamer1.0-tools          # provides gst-inspect-1.0 / gst-launch-1.0

cd ~/code/intel/icamerasrc
export CHROME_SLIM_CAMHAL=ON
./autogen.sh
./configure --prefix=/usr
make -j"$(nproc)"
sudo CHROME_SLIM_CAMHAL=ON make install
sudo ldconfig

configure should report libcamhal ... yes and CHROME_SLIM_CAMHAL is ON, confirming it found the HAL from §5. Output: the plugin libgsticamerasrc.so, installed to /usr/lib/gstreamer-1.0/.

6.3 Fix E: make GStreamer actually find the plugin

After install, gst-inspect-1.0 icamerasrc reported No such element or plugin 'icamerasrc', even though loading the file directly worked:

gst-inspect-1.0 /usr/lib/gstreamer-1.0/libgsticamerasrc.so   # <-- this works

Why: --prefix=/usr installs to /usr/lib/gstreamer-1.0/, but on Debian GStreamer only scans the multiarch directory /usr/lib/x86_64-linux-gnu/gstreamer-1.0/. The plugin was simply outside the search path. Fix it permanently with a symlink (so reinstalls stay current, and GUI apps launched outside your shell also find it):

sudo ln -s /usr/lib/gstreamer-1.0/libgsticamerasrc.so \
           /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgsticamerasrc.so

gst-inspect-1.0 icamerasrc | head    # now resolves by name

(Alternative: export GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0 — but that only helps processes that inherit your shell environment.)


7. First capture attempt — the PSYS device problem

With everything built and installed, the first pipeline still failed:

CamHAL[ERR] PSysDevice: Failed to open psys device No such file or directory
CamHAL[ERR] PipeLine: createPipeStages: failed to initialize psys device
... not-negotiated (-4)

Good news inside that log: the HAL did find the sensor — Load config file: sensors/ov08x40-uf.json, I2CBus:13-0010 <=> CSI Port:0. So sensor, ISYS and the imaging libraries were all fine; only the PSYS path was dead.

libcamhal opens a hard-coded node — src/core/PSysDevice.cpp:

static const char* DRIVER_NAME = "/dev/ipu7-psys0";

…which did not exist, because the intel_ipu7_psys module wasn't loaded/bound.


8. Why a reboot was required (mixed module sets)

The PSYS auxiliary device existed on the bus (/sys/bus/auxiliary/devices/intel_ipu7.psys.40), but no driver was bound to it. The reason was a mixed set of modules:

  • The machine had booted with the in-kernel staging intel_ipu7 / intel_ipu7_isys (which have no PSYS).
  • dkms autoinstall only writes files + runs depmod; it does not reload already-running modules. So the running IPU driver was still staging.
  • Manually modprobe intel-ipu7-psys loaded the DKMS PSYS module, but it had been built against the DKMS intel_ipu7, not the running staging one — so its probe couldn't fully wire up and created no node.

The clean fix is a reboot, so the whole DKMS set (intel_ipu7 + _isys + _psys + the three ipu-acpi*) loads cold as one consistent, matched set. (This is also when Fix B's "Unknown symbol" surfaced and had to be resolved — the DKMS intel_ipu7 only attempts to load on a cold boot once it has displaced staging.)

sudo reboot

Rule of thumb: after any dkms install/autoinstall of these modules, reboot rather than hand-loading, to avoid staging-vs-DKMS mismatches.


9. Working state — verification

After the reboot, all six modules load and PSYS comes up:

$ lsmod | grep -iE 'ipu7|ipu_acpi'
intel_ipu7_psys     ...
intel_ipu7_isys     ...
intel_ipu7          ... intel_ipu7_isys,intel_ipu7_psys
ipu_acpi            ... 1 intel_ipu7
ipu_acpi_pdata      ... 1 ipu_acpi
ipu_acpi_common     ... 1 ipu_acpi_pdata
ipu_bridge          ... 2 intel_ipu7_isys,intel_ipu7

$ cat /proc/devices | grep psys
235 psys

$ ls -l /dev/ipu7-psys0
crw------- 1 root root 235, 0 ... /dev/ipu7-psys0      # <-- the node libcamhal needs

Live preview:

sudo -E gst-launch-1.0 icamerasrc \
    ! video/x-raw,format=NV12,width=1280,height=720 \
    ! videoconvert ! autovideosink

a picture. Full pipeline confirmed working end to end.


10. Running without sudo (optional)

/dev/ipu7-psys0 is crw------- root root, which is why the test needs sudo. For normal desktop apps to use the camera, grant the video group access via a udev rule:

# /etc/udev/rules.d/99-ipu7-psys.rules
KERNEL=="ipu7-psys[0-9]*", GROUP="video", MODE="0660"
sudo udevadm control --reload-rules && sudo udevadm trigger
# ensure your user is in the 'video' group:  id -nG | grep video  ||  sudo usermod -aG video "$USER"

(You may also need the same treatment for the relevant /dev/video* and /dev/media* nodes depending on the application.)


11. Summary of changes & fixes

# Repo / area Problem Fix
A ipu7-drivers source v4l2_subdev_stream_config now private (kernel ≥ ~6.x / 7.0) rewrite get_remote_pad_stream() to use for_each_active_route() / state->routing
B ipu7-drivers dkms.conf intel_ipu7 needs ipu_get_acpi_devices, ACPI modules not installed add ipu-acpi, ipu-acpi-common, ipu-acpi-pdata to dkms.conf
ipu7-camera-bins HAL link errors (-lia_cca-ipu7x…) copy lib* / headers / pkgconfig into /usr (not just firmware)
C ipu7-camera-hal CMake 4.x rejects cmake_minimum_required(2.8) add -DCMAKE_POLICY_VERSION_MINIMUM=3.5
D icamerasrc master has no build files check out icamerasrc_slim_api
icamerasrc tooling gst-inspect-1.0: command not found apt install gstreamer1.0-tools
E icamerasrc install plugin not in Debian's multiarch scan dir symlink into /usr/lib/x86_64-linux-gnu/gstreamer-1.0/
kernel modules staging vs DKMS mismatch → no PSYS node reboot to load the DKMS set cold

Don't forget to commit the driver-repo changes

Fixes A and B live in your ipu7-drivers working tree (uncommitted). Commit them so a future kernel upgrade — which re-triggers the DKMS build — doesn't reintroduce the failures:

cd ~/code/intel/ipu7-drivers
git add dkms.conf drivers/media/pci/intel/ipu7/ipu7-isys-video.c
git commit -m "Fix DKMS build for kernel 7.0 and install ipu-acpi modules"

12. Quick troubleshooting reference

Symptom Likely cause Section
invalid use of undefined type 'struct v4l2_subdev_stream_config' private kernel struct §3.1
intel_ipu7: Unknown symbol ipu_get_acpi_devices ACPI modules not installed §3.2
HAL link error cannot find -lia_cca-ipu7x camera-bins libs not in /usr/lib §4
CMake aborts: "Compatibility with CMake < 3.5 has been removed" CMake 4.x §5.1
icamerasrc configure can't find libcamhal HAL not installed / ldconfig not run §5, §6.2
gst-inspect-1.0: command not found missing gstreamer1.0-tools §6.2
No such element or plugin 'icamerasrc' (but file loads by path) plugin outside multiarch scan dir §6.3
PSysDevice: Failed to open psys device / not-negotiated (-4) /dev/ipu7-psys0 missing (psys not bound) §7, §8
psys loaded but no /dev/ipu7-psys0 staging vs DKMS module mismatch §8
pipeline works only with sudo psys node is root-only §10

Tip: dmesg is restricted (kernel.dmesg_restrict=1), so kernel probe logs need sudo dmesg | grep -iE 'ipu7|psys|isys'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment