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.
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.
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-treeipu7-driversbuild (which does include PSYS) is required, and why the two must not be mixed (see §8).
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.
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);
#endifThat 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_ACPIis off the Makefile buildsipu7-fpga-pdata.oasobj-y, which is an in-tree-only artifact (orphaned in an external/DKMS build). For out-of-tree builds the intended configuration isACPI=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-acpi → ipu-acpi-pdata → ipu-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.
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.0A 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.confafter a previousdkms 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
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 ldconfigWhy 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.
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 ldconfigWhy -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/).
cd ~/code/intel/icamerasrc
git checkout icamerasrc_slim_api # <-- REQUIRED; default 'master' has no build filesWhy: 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.
# 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 ldconfigconfigure 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/.
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 worksWhy: --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.)
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.
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 autoinstallonly writes files + runsdepmod; it does not reload already-running modules. So the running IPU driver was still staging.- Manually
modprobe intel-ipu7-psysloaded the DKMS PSYS module, but it had been built against the DKMSintel_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 rebootRule of thumb: after any
dkms install/autoinstallof these modules, reboot rather than hand-loading, to avoid staging-vs-DKMS mismatches.
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 needsLive 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.
/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.)
| # | 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 |
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"| 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:
dmesgis restricted (kernel.dmesg_restrict=1), so kernel probe logs needsudo dmesg | grep -iE 'ipu7|psys|isys'.