Apply the IPU7 camera's ISP tuning, fix its gain, range and exposure, harden PSYS pinning, and support Lunar Lake (#723)

* Apply the Intel IPU7 camera's ISP tuning and fix its gain, range and exposure

The XPS 14 / 16 webcam looked soft and grainy because almost none of the
image pipeline was tuned:

- 0011: the graph asks for ISP tuning mode 4, which the OV08X40 tuning
  does not carry, so the generic AIC found no tunings and noise
  reduction, TNR and sharpening ran on library defaults. Fall back to
  the tuning's default ISP container.
- 0010: AIQ emits the raw analog gain register code, which the in-tree
  ov08x40 driver halves, so the sensor ran at twice the gain AE and the
  ISP noise model assumed.
- 0008: the HAL ignored the requested YUV range and always produced
  full-range frames that consumers decode as limited range.
- 0009 + relay config: icamerasrc pinned auto exposure to 1/30 s; a new
  fps-range property lets AE lengthen frames in dim light, with gain
  capped at 27 dB.

* Guard fps-range against NULL and correct two descriptions

Setting icamerasrc's new fps-range property to NULL, its own default, handed NULL to gst_camerasrc_parse_range, which crashed in strlen(). NULL now leaves the last range in effect, because the HAL has no way to drop a range once set. The value the relay sets goes through unchanged.

The relay comment said AE raises gain past 27 dB once frames reach 15 fps, but gain-range becomes a hard ISO ceiling (manual_iso_max), so gain never goes past it. The 0008 message credited the sensor JSON's yuvColorRangeMode, which only the mock HAL reads.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Free fps-range when icamerasrc is destroyed

finalize never freed the string the fps-range setter allocates, so every icamerasrc element that had the property set leaked it. The other string properties leak the same way upstream and are left to an upstream fix for all of them.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Harden IPU7 PSYS userptr pinning

(cherry picked from commit 5f1d0013b0e37d9229dfb906806dee0e98199035)

* Avoid unverified IPU7 permission assurances

(cherry picked from commit 9ce97d9788f2be508743fe785f8434ccfec7842e)

* Check IPU7 against Omarchy headers without a runtime dependency

Use linux-omarchy-headers only for check(), avoiding Arch headers on installed Omarchy systems and matching the supported kernel build configuration.

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit d0d382c07ea99eca5bb5c52240841db779d24b14)

* Test IPU7 build-only headers and kernel-tree selection

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit eb37cc828fac201a360c35aa1e87a1daf2d5fed1)

* Restrict the IPU7 PSYS node to root

Intel's PSYS driver has two buffer-lifecycle bugs that this package does not fix: a GETBUF that is never mapped leaves the buffer owned by both the PSYS handle and the exported dma-buf, so closing the two is a use-after-free and a double free, and UNMAPBUF drops its mapping reference before clearing the attachment, racing a concurrent dma-buf release. Any account that can open /dev/ipu7-psys0 can reach both.

Nothing but v4l2-relayd@ipu7, which runs as root, opens the node; applications use the v4l2loopback device. With the node at 0600 root:root the camera streams the same, so the video group and the seat user lose nothing and the bugs need root. GROUP and MODE are explicit so the upgrade's change event also tightens nodes on running machines.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Drop the DKMS Intel CVS driver now that linux-omarchy ships it

linux-omarchy and linux-omarchy-bore build drivers/media/i2c/cvs in-tree (CONFIG_VIDEO_INTEL_CVS=m) and carry the same wake-IRQ fix as our 0007 (0542), plus the Nova Lake ACPI ID (0541) that our copy lacks. The DKMS module has the same name and installs under updates/, so on every 7.2+ kernel it displaced the kernel's signed module with an unsigned, older copy: on Nova Lake that copy cannot bind the CVS device and the camera fails, and any later fix in the kernel's driver would be masked. Krzysztof Wilczyński reported the conflict on #723.

Skipping the build only on kernels that carry the driver (BUILD_EXCLUSIVE_CONFIG) would have kept it for stock Arch kernels, but the pacman dkms hook reports every such skip as "exited 77", so every Omarchy kernel update would print a warning. A stock Arch kernel left installed as the fallback boot entry now has no camera.

On upgrade the dkms hook removes intel-cvs and restores the kernel's original module. vision-drivers still covers kernels before 7.2.

* intel-ipu7-camera: build the Lunar Lake HAL plugin and stop rotating its frames

Two Panther Lake assumptions in this package break Lunar Lake boards, where it
is installed by the same hardware detection.

Build both HAL platforms. libcamhal picks its plugin by platform, and a Lunar
Lake machine asks for one that was never built:

  CamHAL[ERR] HalAdaptor: load_camera_hal_library, failed to open library:
    /usr/lib/libcamhal/plugins/ipu7x.so: No such file or directory
  CamHAL[WAR] CameraParserInvoker: parseSensors: No sensors available

so the camera cannot work at all. The proprietary side of it is already
shipped -- libia_aic-ipu7x.so and the rest of that set come from
ipu7-camera-bins today; only the plugin the HAL loads was missing. Upstream
builds both platforms from one configure run and `make install` lays down
/etc/camera/ipu7x/ alongside ipu75xa, including the tuning this hardware
wants (OV08X40_BBG802N3_LNL.aiqb, gcss/OV08X40_BBG802N3_LNL.IPU7X.bin), so the
change is the IPU_VERSIONS list plus 0008, the ipu7x twin of 0006: the
"Intel CVS" pad formats that 1.0.6 added to the ipu75xa sensor config are
needed in the ipu7x one for the same reason, or link validation fails at
stream-on behind the Linux 7.2 bridge entity.

Pick the relay pipeline per board. v4l2-relayd-ipu7.conf rotates every frame
180 degrees, which is right where the sensor is mounted inverted and wrong
here: the sensor reports camera_sensor_rotation = 0 and camera_orientation =
Front, and the picture arrives upside down in every application. camera-init
now writes VIDEOSRC to /run based on the bridge ACPI id and the relay drop-in
reads it. Only INTC10DE takes the new path; INTC10E1, INTC10CF, INTC10E0 and
anything unrecognised keep the packaged pipeline byte for byte, and a boot
where camera-init did not run falls back to it as well.

Verified on a Dell Pro 14 Premium PA14250 (Lunar Lake, INTC10DE, OV08X40 +
HM1092) against intel-ipu7-camera 1.0.6-2 on linux-omarchy 7.2.5-3: with the
package's own intel-cvs DKMS the sensor joins the graph behind "Intel CVS",
the ipu7x plugin built from the pinned commit with 0005 and 0008 resolves
ov08x40-uf on CSI port 0, v4l2-relayd streams 30 fps to /dev/video50 and the
image is upright in a browser.

Note for reviewers: necessary but not sufficient on Lunar Lake. Three more
things this board needs are not in this PR: the sensor probe races the
bridge's runtime suspend (camera-init's `sleep 2` lands while the I2C bus is
still owned by the bridge firmware and ov08x40 reads its chip id as -110),
the in-tree cvs driver's quirk for the Synaptics SVP7500 (06cb:0701) hands
the privacy LED to the host so it never lights, and ipu-bridge before 7.3
does not know the HM1092 IR sensor. Details in #366.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 9be51b47104da9d115a7d79e04bc2ae5445809fc)

* Derive the Lunar Lake relay pipeline from the packaged one

camera-videosrc-init wrote VIDEOSRC for every board, with a copy of the pipeline the relay config carried when it was written, and its file is read after /etc/v4l2-relayd.d/ipu7.conf. On Panther Lake that replaced this branch's fps-range, gain-range and color-range settings with the old sharpness=80 ev=-1 saturation=10 pipeline. It now writes nothing unless the board is Lunar Lake, and there it takes the packaged pipeline and drops only the rotation, so a later change to the relay config reaches both platforms. It also removes a file left by an earlier run, which camera-init's restart on resume would otherwise keep.

The ipu7x CVS format patch becomes 0012: 0008 is already the YUV range patch.

* Regenerate the Lunar Lake pipeline on every relay start

camera-videosrc-init ran from camera-init.service and sourced /etc/v4l2-relayd.d/ipu7.conf as bash. A config that is valid for systemd but not for bash, such as an unquoted VIDEOSRC, made it fail and Lunar Lake fell back to the rotated pipeline; and because camera-init stays active, editing the config and restarting only the relay kept the override cached since boot.

It now runs as the relay's ExecStartPre, where systemd hands it VIDEOSRC already parsed from the relay's own environment files, and writes the override quoted for systemd's parser. ExecStart re-reads the drop-in's EnvironmentFile and ExecStopPost removes it, so each start sees the current config.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Scale the Lunar Lake ov08x40's analog gain codes too

The Lunar Lake tuning (OV08X40_BBG802N3_LNL.aiqb) carries the same CMC gain table as the Panther Lake one, 1x = code 256, and both platforms use the same in-tree ov08x40 driver, which takes 1x = 128. Without the shift a 4x request ran the Lunar Lake sensor at 8x, the mismatch 0010 fixes on Panther Lake. 0010 now sets analogGainCodeShift in the ipu7x sensor config as well.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Remove the Lunar Lake override before the generator runs

systemd passes ExecStartPre the unit's environment files as they stand when
that command starts, so the generator saw a /run override left behind by a
crash or SIGKILL (anything that skipped ExecStopPost) and took its VIDEOSRC
for the configured one: an edited /etc/v4l2-relayd.d/ipu7.conf would lose to
the stale file. Removing the file in its own ExecStartPre first means the
generator's environment is re-read without it.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Author the HAL and icamerasrc patches from the Omarchy address

---------

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Kolbas <pkolbas@pm.me>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-02 21:27:53 -05:00
1 parent f6b9267b3b
commit 97925fbc84
23 files changed
+979 -180

No files matched your search

+2
View File
@@ -50,6 +50,8 @@ jobs:
pacman -Syu --noconfirm git jq python libarchive neovim tmux
python tests/oma-service-removal.py
python tests/upstream-watch.py
bash tests/ipu7-install.sh
bash tests/ipu7-headers.sh
./bin/sync-upstream self-test
./bin/sync-rebuilds --self-test
./bin/omarchy-pkgs self-test
@@ -0,0 +1,2 @@
# Diff context has a marker space before upstream tabs and blank lines.
*.patch whitespace=-blank-at-eol,-space-before-tab
@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Afonso Oliveira <afonso.oliveira707@gmail.com>
Date: Tue, 1 Sep 2026 21:50:00 +0100
Subject: [PATCH] media: intel/ipu7: harden PSYS userptr pinning
Reject invalid and oversized userptr ranges before allocating their page
arrays. Keep the page count in size_t, use overflow-checked allocation,
remove FOLL_FORCE, and require the entire range to be pinned before
publishing attachment state. Clear the attachment state after releasing
it. Clear failed buffer attachments before the exported fd can later
release the surviving buffer.
Upstream-Status: Not submitted
---
drivers/media/pci/intel/ipu7/psys/ipu-psys.c | 71 lines changed
1 file changed, 40 insertions(+), 31 deletions(-)
diff --git a/drivers/media/pci/intel/ipu7/psys/ipu-psys.c b/drivers/media/pci/intel/ipu7/psys/ipu-psys.c
index fee9ee82465b..03fe0b1d054b 100644
--- a/drivers/media/pci/intel/ipu7/psys/ipu-psys.c
+++ b/drivers/media/pci/intel/ipu7/psys/ipu-psys.c
@@ -13,6 +13,7 @@
#include <linux/kthread.h>
#include <linux/mm.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/pm_runtime.h>
#include <linux/poll.h>
#include <uapi/linux/sched/types.h>
@@ -49,63 +50,63 @@
static int ipu7_psys_get_userpages(struct ipu7_dma_buf_attach *attach)
{
- struct vm_area_struct *vma;
- unsigned long start, end;
- int npages, array_size;
+ unsigned long start, last;
+ size_t npages;
struct page **pages;
struct sg_table *sgt;
int ret = -ENOMEM;
- int nr = 0;
+ long nr;
u32 flags;
+ if (WARN_ON_ONCE(attach->pages || attach->npages || attach->sgt))
+ return -EINVAL;
+
+ if (!attach->userptr || !attach->len || attach->len > MAX_RW_COUNT)
+ return -EINVAL;
+
start = (unsigned long)attach->userptr;
- end = PAGE_ALIGN(start + attach->len);
- npages = PHYS_PFN(end - (start & PAGE_MASK));
- array_size = npages * sizeof(struct page *);
+ if (check_add_overflow(start, (unsigned long)attach->len - 1, &last))
+ return -EOVERFLOW;
+
+ npages = (((last & PAGE_MASK) - (start & PAGE_MASK)) >> PAGE_SHIFT) + 1;
+ if (!npages || npages > INT_MAX)
+ return -E2BIG;
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
if (!sgt)
return -ENOMEM;
- WARN_ON_ONCE(attach->npages);
-
- pages = kvzalloc(array_size, GFP_KERNEL);
+ pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL);
if (!pages)
goto free_sgt;
- mmap_read_lock(current->mm);
- vma = vma_lookup(current->mm, start);
- if (unlikely(!vma)) {
- ret = -EFAULT;
- goto error_up_read;
- }
- mmap_read_unlock(current->mm);
-
- flags = FOLL_WRITE | FOLL_FORCE | FOLL_LONGTERM;
+ flags = FOLL_WRITE | FOLL_LONGTERM;
nr = pin_user_pages_fast(start & PAGE_MASK, npages,
flags, pages);
- if (nr < npages)
- goto error;
-
- attach->pages = pages;
- attach->npages = npages;
+ if (nr < 0) {
+ ret = nr;
+ goto unpin_pages;
+ }
+ if ((size_t)nr != npages) {
+ ret = -EFAULT;
+ goto unpin_pages;
+ }
ret = sg_alloc_table_from_pages(sgt, pages, npages,
start & ~PAGE_MASK, attach->len,
GFP_KERNEL);
if (ret < 0)
- goto error;
-
+ goto unpin_pages;
+
+ attach->pages = pages;
+ attach->npages = npages;
attach->sgt = sgt;
return 0;
-error_up_read:
- mmap_read_unlock(current->mm);
-error:
- if (nr)
+unpin_pages:
+ if (nr > 0)
unpin_user_pages(pages, nr);
-
kvfree(pages);
free_sgt:
kfree(sgt);
@@ -126,6 +127,8 @@
sg_free_table(attach->sgt);
kfree(attach->sgt);
+ attach->pages = NULL;
+ attach->npages = 0;
attach->sgt = NULL;
}
@@ -580,6 +583,11 @@
return -EINVAL;
}
+ if (!buf->len || buf->len > MAX_RW_COUNT) {
+ dev_err(dev, "Invalid userptr buffer length\n");
+ return -EINVAL;
+ }
+
kbuf = kzalloc(sizeof(*kbuf), GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
@@ -782,6 +790,7 @@
dma_buf_detach(kbuf->dbuf, kbuf->db_attach);
attach_fail:
+ kbuf->db_attach = NULL;
list_del(&kbuf->list);
if (!kbuf->userptr)
kfree(kbuf);
@@ -1,83 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Junjie Cao <junjie.cao@intel.com>
Date: Tue, 08 Sep 2026 18:57:17 +0800
Subject: [PATCH] media: i2c: cvs: Get the wake IRQ without claiming the GPIO
Message-ID: <20260908105717.496232-1-junjie.cao@intel.com>
Link: https://patchwork.linuxtv.org/project/linux-media/patch/20260908105717.496232-1-junjie.cao@intel.com/
The wake line is only used as an IRQ source, yet the driver requests
it with devm_gpiod_get() before the I2C handshake. Where ipu-bridge
does not expose the CSI endpoints, CSI init returns -EPROBE_DEFER and
every retry claims the line again for the length of the handshake.
On the Dell XPS 14 DA14260 (Panther Lake) the four CS35L57 amplifiers
read their speaker ID from one GpioIo (DSDT decoded in the second
link):
GpioIo (Shared, PullNone, 0, 0, IoRestrictionInputOnly,
"\_SB.GPI1", 0, ResourceConsumer,,) {20}
A request that lands while another consumer holds the line fails, and
cs35l56 does not retry:
cs35l56 sdw:0:2:01fa:3557:01:2: error -EBUSY: Failed to get spk-id-gpios
All four fail on Fedora 7.1.13, the first Fedora 7.1 kernel with the
driver enabled; the same board on 7.1.12 without it creates the card.
The second link shows the same failure on openSUSE 7.2.2, whose
config also enables the driver.
The INTC10E1 _CRS of this machine has not been decoded. The vendor
driver in intel/vision-drivers requests req, resp and rst the same
way but maps wake to an IRQ with acpi_dev_gpio_irq_get_by() without
requesting it, and on another DA14260 (board 0VRKYR, BIOS 1.8.2) a
build of it is bound while the amplifiers probe. The wake entry is
the line that differs.
Take the IRQ from the GpioInt entry the same way, as the I2C core
does for client->irq; this also applies the trigger type from _CRS.
The driver binds as a platform device too, hence the explicit lookup.
Fixes: 8e2b43d2c10b ("media: i2c: cvs: Add driver of Intel Computer Vision Sensing Controller(CVS)")
Cc: stable@vger.kernel.org
Link: https://bugzilla.redhat.com/show_bug.cgi?id=2529031
Link: https://github.com/thesofproject/sof/issues/11152
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
drivers/media/i2c/cvs/core.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c
index d4a3b9c3bab1e..8d857bbd8ab51 100644
--- a/drivers/media/i2c/cvs/core.c
+++ b/drivers/media/i2c/cvs/core.c
@@ -725,8 +725,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c)
}
if (ctx->res == ICVS_FULLCAP) {
- struct gpio_desc *wake;
-
ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH);
if (IS_ERR(ctx->rst)) {
ret = dev_err_probe(dev, PTR_ERR(ctx->rst),
@@ -734,14 +732,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c)
goto err_put_ipu;
}
- wake = devm_gpiod_get(dev, "wake", GPIOD_IN);
- if (IS_ERR(wake)) {
- ret = dev_err_probe(dev, PTR_ERR(wake),
- "failed to get wake GPIO\n");
- goto err_put_ipu;
- }
-
- ctx->irq = gpiod_to_irq(wake);
+ /*
+ * Do not request the line: another device's _CRS may list
+ * the same pin, and its driver would then fail with -EBUSY.
+ */
+ ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev),
+ "wake", 0);
if (ctx->irq < 0) {
ret = dev_err_probe(dev, ctx->irq,
"failed to get wake IRQ\n");
@@ -0,0 +1,35 @@
From d358e3b1bcf9f26638ae326e979661d0f2c740e2 Mon Sep 17 00:00:00 2001
From: Spencer Bull <spencer@omarchy.org>
Date: Tue, 29 Sep 2026 00:19:50 -0500
Subject: [PATCH] IpuPacAdaptor: honor the reduced YUV color range
applyMediaFormat() always hands the PAL media_format_legacy, so the ISP
emits full-range (0-255) YUV no matter what the application asks for
through setYuvColorRangeMode() (icamerasrc color-range=reduced).
Consumers of an NV12 webcam assume limited range (16-235), so full-range
frames are shown with crushed shadows and clipped highlights.
Select media_format_legacy_narrow when the reduced range is requested.
The full-range default is unchanged.
---
src/core/IpuPacAdaptor.cpp | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/core/IpuPacAdaptor.cpp b/src/core/IpuPacAdaptor.cpp
index a8aad3f..cbcd8eb 100644
--- a/src/core/IpuPacAdaptor.cpp
+++ b/src/core/IpuPacAdaptor.cpp
@@ -285,7 +285,12 @@ void IpuPacAdaptor::applyMediaFormat(const AiqResult* aiqResult,
auto cameraContext = CameraContext::getInstance(mCameraId);
auto dataContext = cameraContext->getDataContextBySeq(sequence);
- *mediaFormat = media_format_legacy;
+ // Honor the requested YUV range: reduced range needs the narrow variant,
+ // otherwise the ISP always emits full-range data.
+ *mediaFormat =
+ (dataContext->mAiqParams.yuvColorRangeMode == CAMERA_REDUCED_MODE_YUV_COLOR_RANGE)
+ ? media_format_legacy_narrow
+ : media_format_legacy;
if (dataContext->mAiqParams.tonemapMode == TONEMAP_MODE_GAMMA_VALUE) {
const float gamma = dataContext->mAiqParams.tonemapGamma;
if (std::fabs(gamma - 1.0F) < std::numeric_limits<float>::epsilon()) {
@@ -0,0 +1,110 @@
From 2d2f5f56e4f4033df3bd1e51fdc519c59ddb6f09 Mon Sep 17 00:00:00 2001
From: Spencer Bull <spencer@omarchy.org>
Date: Tue, 29 Sep 2026 00:19:50 -0500
Subject: [PATCH] Add an fps-range property for auto exposure
icamerasrc passes the caps frame rate to the HAL with setFrameRate(), and
the HAL turns that into an AE frame-time range of exactly [fps, fps]. At
30 fps auto exposure can never expose longer than 33 ms, so in dim rooms
all extra brightness comes from analog plus digital gain, which is where
the grain comes from.
The HAL already supports an AE frame rate range (setFpsRange); expose it
as "fps-range" (e.g. "15~30") so AE may stretch the frame time in low
light and return to the full rate when there is enough light.
---
src/gstcamerasrc.cpp | 25 +++++++++++++++++++++++++
src/gstcamerasrc.h | 2 ++
2 files changed, 27 insertions(+)
diff --git a/src/gstcamerasrc.cpp b/src/gstcamerasrc.cpp
index 61683e5..f392fc1 100644
--- a/src/gstcamerasrc.cpp
+++ b/src/gstcamerasrc.cpp
@@ -120,6 +120,7 @@ enum
PROP_AF_REGION,
PROP_EXPOSURE_TIME_RANGE,
PROP_GAIN_RANGE,
+ PROP_FPS_RANGE,
PROP_CONVERGE_SPEED,
PROP_CONVERGE_SPEED_MODE,
/* Backlight Settings*/
@@ -902,6 +903,9 @@ gst_camerasrc_finalize (Gstcamerasrc *camerasrc)
delete camerasrc->isp_control_tags;
camerasrc->isp_control_tags = NULL;
+ g_free(camerasrc->man_ctl.fps_range);
+ camerasrc->man_ctl.fps_range = NULL;
+
for (int i = 0; i < GST_CAMERASRC_MAX_STREAM_NUM; i++) {
camerasrc->streams[i].activated = FALSE;
}
@@ -1137,6 +1141,11 @@ gst_camerasrc_class_init (GstcamerasrcClass * klass)
g_param_spec_string("gain-range","AE gain range","AE gain range",
DEFAULT_PROP_GAIN_RANGE, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property(gobject_class,PROP_FPS_RANGE,
+ g_param_spec_string("fps-range","AE frame rate range",
+ "Frame rate range AE may use, e.g. 15~30: lets auto exposure lengthen frames in low light",
+ DEFAULT_PROP_FPS_RANGE, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
g_object_class_install_property (gobject_class, PROP_CUSTOM_AIC_PARAMETER,
g_param_spec_string("custom-aic-param","Custom Aic Parameter","Custom Aic Parameter",
DEFAULT_PROP_CUSTOM_AIC_PARAMETER, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
@@ -1884,6 +1893,7 @@ gst_camerasrc_set_property (GObject * object, guint prop_id,
camera_range_t cct_range;
camera_range_t exposure_time_range;
camera_range_t gain_range;
+ camera_range_t fps_range;
camera_coordinate_t white_point;
unsigned int custom_aic_param_len = 0;
camera_crop_region_t crop_region;
@@ -2150,6 +2160,18 @@ gst_camerasrc_set_property (GObject * object, guint prop_id,
if (ret == 0)
src->param->setSensitivityGainRange(gain_range);
break;
+ case PROP_FPS_RANGE:
+ g_free(src->man_ctl.fps_range);
+ src->man_ctl.fps_range = g_strdup(g_value_get_string(value));
+ // NULL is the default; the HAL has no way to drop a range once set.
+ if (src->man_ctl.fps_range == NULL)
+ break;
+ ret = gst_camerasrc_parse_range(src->man_ctl.fps_range,
+ fps_range.min,
+ fps_range.max);
+ if (ret == 0)
+ src->param->setFpsRange(fps_range);
+ break;
case PROP_AWB_COLOR_TRANSFORM:
ret = gst_camerasrc_parse_string_to_matrix(g_value_get_string (value),
(float**)(transform.color_transform), 3, 3);
@@ -2380,6 +2402,9 @@ gst_camerasrc_get_property (GObject * object, guint prop_id,
case PROP_GAIN_RANGE:
g_value_set_string (value, src->man_ctl.gain_range);
break;
+ case PROP_FPS_RANGE:
+ g_value_set_string (value, src->man_ctl.fps_range);
+ break;
case PROP_AWB_COLOR_TRANSFORM:
g_value_set_string(value, src->man_ctl.color_transform);
break;
diff --git a/src/gstcamerasrc.h b/src/gstcamerasrc.h
index 508a405..ec8a64f 100644
--- a/src/gstcamerasrc.h
+++ b/src/gstcamerasrc.h
@@ -143,6 +143,7 @@ using namespace std;
#define DEFAULT_PROP_AF_REGION NULL
#define DEFAULT_PROP_EXPOSURE_TIME_RANGE NULL
#define DEFAULT_PROP_GAIN_RANGE NULL
+#define DEFAULT_PROP_FPS_RANGE NULL
#define DEFAULT_PROP_CCT_RANGE NULL
#define DEFAULT_PROP_COLOR_TRANSFORM NULL
#define DEFAULT_PROP_CUSTOM_AIC_PARAMETER NULL
@@ -407,6 +408,7 @@ struct _Gst3AManualControl
int converge_speed_mode;
gchar *exp_time_range;
gchar *gain_range;
+ gchar *fps_range;
/* Backlight Settings*/
int blc_area_mode;
guint wdr_level;
@@ -0,0 +1,140 @@
From ee34eedf620ff0b697d66eb70e06b91062ab7e15 Mon Sep 17 00:00:00 2001
From: Spencer Bull <spencer@omarchy.org>
Date: Tue, 29 Sep 2026 00:24:27 -0500
Subject: [PATCH] Scale AIQ analog gain codes to the ov08x40 driver's units
The OV08X40 tuning (OV08X40_KAFE799_PTL.aiqb) makes AIQ produce the raw
0x3508 register value, where 1x = 256. The in-tree ov08x40 driver takes
V4L2_CID_ANALOGUE_GAIN in units of half that (1x = 128, range 128..1984,
written to the register as val << 1). The HAL passed the code through, so
every analog gain below 7.75x ran at twice what AE and the ISP noise model
believed, and every request above collapsed to the 15.5x clamp. Measured
on a Dell XPS 16 DA16260: a 7.75x request (code 1984) reads back as
analogue_gain = 1984, i.e. 15.5x on the sensor.
Add an "analogGainCodeShift" sensor option that right-shifts the code
before V4L2_CID_ANALOGUE_GAIN, and set it to 1 for the ov08x40 on both
IPU7.5 and IPU7: the Lunar Lake tuning (OV08X40_BBG802N3_LNL.aiqb)
carries the same gain table. Sensors without the option are unchanged.
---
config/linux/ipu75xa/sensors/ov08x40-uf.json | 3 +++
config/linux/ipu7x/sensors/ov08x40-uf.json | 3 +++
src/core/SensorHwCtrl.cpp | 9 ++++++---
src/platformdata/CameraSensorsParser.cpp | 3 +++
src/platformdata/PlatformData.cpp | 4 ++++
src/platformdata/PlatformData.h | 11 +++++++++++
6 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/config/linux/ipu75xa/sensors/ov08x40-uf.json b/config/linux/ipu75xa/sensors/ov08x40-uf.json
index 483bf1d..dbe2363 100644
--- a/config/linux/ipu75xa/sensors/ov08x40-uf.json
+++ b/config/linux/ipu75xa/sensors/ov08x40-uf.json
@@ -194,6 +194,9 @@
"initialSkipFrame": 0,
"exposureLag": 2,
"gainLag": 2,
+ // AIQ codes are the 0x3508 register value (1x = 256); the in-tree
+ // ov08x40 driver takes V4L2_CID_ANALOGUE_GAIN as register / 2 (1x = 128).
+ "analogGainCodeShift": 1,
"digitalGainLag": 0,
"ltmGainLag": 1,
// There are 2 yuv color range mode, like full, reduced.
diff --git a/config/linux/ipu7x/sensors/ov08x40-uf.json b/config/linux/ipu7x/sensors/ov08x40-uf.json
index 472b0da..00f78d7 100644
--- a/config/linux/ipu7x/sensors/ov08x40-uf.json
+++ b/config/linux/ipu7x/sensors/ov08x40-uf.json
@@ -194,6 +194,9 @@
"initialSkipFrame": 0,
"exposureLag": 2,
"gainLag": 2,
+ // AIQ codes are the 0x3508 register value (1x = 256); the in-tree
+ // ov08x40 driver takes V4L2_CID_ANALOGUE_GAIN as register / 2 (1x = 128).
+ "analogGainCodeShift": 1,
"digitalGainLag": 0,
"ltmGainLag": 1,
// There are 2 yuv color range mode, like full, reduced.
diff --git a/src/core/SensorHwCtrl.cpp b/src/core/SensorHwCtrl.cpp
index 799e02e..a7832a2 100644
--- a/src/core/SensorHwCtrl.cpp
+++ b/src/core/SensorHwCtrl.cpp
@@ -136,9 +136,12 @@ int SensorHwCtrl::setAnalogGains(const vector<int>& analogGains) {
CheckAndLogError(mPixelArraySubdev == nullptr, NO_INIT, "pixel array sub device is not set");
CheckAndLogError(analogGains.empty(), BAD_VALUE, "No analog gain data!");
- LOG2("%s analogGain=%d", __func__, analogGains[0]);
- int status = mPixelArraySubdev->SetControl(V4L2_CID_ANALOGUE_GAIN, analogGains[0]);
- CheckAndLogError((status != 0), status, "failed to set analog gain %d.", analogGains[0]);
+ // AIQ produces the sensor's register code; some drivers expose the gain in
+ // coarser units (e.g. ov08x40 takes code / 2), so scale it down to match.
+ const int analogGain = analogGains[0] >> PlatformData::getAnalogGainCodeShift(mCameraId);
+ LOG2("%s analogGain=%d", __func__, analogGain);
+ int status = mPixelArraySubdev->SetControl(V4L2_CID_ANALOGUE_GAIN, analogGain);
+ CheckAndLogError((status != 0), status, "failed to set analog gain %d.", analogGain);
return OK;
}
diff --git a/src/platformdata/CameraSensorsParser.cpp b/src/platformdata/CameraSensorsParser.cpp
index c5a4633..04ac4ef 100644
--- a/src/platformdata/CameraSensorsParser.cpp
+++ b/src/platformdata/CameraSensorsParser.cpp
@@ -804,6 +804,9 @@ void CameraSensorsParser::parseSensorSection(const Json::Value& node) {
if (node.isMember("gainLag")) {
mCurCam->mAnalogGainLag = node["gainLag"].asInt();
}
+ if (node.isMember("analogGainCodeShift")) {
+ mCurCam->mAnalogGainCodeShift = node["analogGainCodeShift"].asInt();
+ }
if (node.isMember("digitalGainLag")) {
mCurCam->mDigitalGainLag = node["digitalGainLag"].asInt();
}
diff --git a/src/platformdata/PlatformData.cpp b/src/platformdata/PlatformData.cpp
index 7e25e61..d4b7cd3 100644
--- a/src/platformdata/PlatformData.cpp
+++ b/src/platformdata/PlatformData.cpp
@@ -379,6 +379,10 @@ int PlatformData::getDigitalGainLag(int cameraId) {
return getInstance()->mStaticCfg.mCameras[cameraId].mDigitalGainLag;
}
+int PlatformData::getAnalogGainCodeShift(int cameraId) {
+ return getInstance()->mStaticCfg.mCameras[cameraId].mAnalogGainCodeShift;
+}
+
int PlatformData::getExposureLag(int cameraId) {
return getInstance()->mStaticCfg.mCameras[cameraId].mExposureLag;
}
diff --git a/src/platformdata/PlatformData.h b/src/platformdata/PlatformData.h
index 8f1f84d..bfc8e6e 100644
--- a/src/platformdata/PlatformData.h
+++ b/src/platformdata/PlatformData.h
@@ -211,6 +211,7 @@ class PlatformData {
mDigitalGainLag(-1),
mExposureLag(MAX_BUFFER_COUNT),
mAnalogGainLag(0),
+ mAnalogGainCodeShift(0),
mMaxSensorDigitalGain(0),
mSensorDgType(SensorDgType::SENSOR_DG_TYPE_NONE),
mISysFourcc(V4L2_PIX_FMT_SGRBG8),
@@ -296,6 +297,7 @@ class PlatformData {
int mDigitalGainLag;
int mExposureLag;
int mAnalogGainLag;
+ int mAnalogGainCodeShift;
int mMaxSensorDigitalGain;
SensorDgType mSensorDgType;
std::string mCustomAicLibraryName;
@@ -843,6 +845,15 @@ class PlatformData {
* \return the value of digital gain lag
*/
static int getDigitalGainLag(int cameraId);
+
+ /**
+ * Get the right shift from AIQ analog gain codes to the sensor driver's
+ * V4L2_CID_ANALOGUE_GAIN units
+ *
+ * \param cameraId: [0, MAX_CAMERA_NUMBER - 1]
+ * \return the shift, 0 when the driver takes AIQ codes as they are
+ */
+ static int getAnalogGainCodeShift(int cameraId);
/**
* Get sensor's exposure lag
*
@@ -0,0 +1,91 @@
From 5ba99a9f2cda77035dd99cacf8d98d3a0293768f Mon Sep 17 00:00:00 2001
From: Spencer Bull <spencer@omarchy.org>
Date: Tue, 29 Sep 2026 08:27:30 -0500
Subject: [PATCH] PipeLine: fall back to the default ISP tuning when the mode
is missing
The static graph tags its nodes with an ISP tuning mode, and PipeLine
asks CCA to load the matching ISP container from the tuning file. When
the file has no container for that mode, updateTuning() fails and the
HAL carries on with the graph's mode anyway. The generic AIC then looks
up tunings for a mode it does not have, finds none, and every ISP kernel
it tunes (noise reduction, TNR, sharpening, ...) runs on library
defaults.
The OV08X40 PTL tuning (OV08X40_KAFE799_PTL.aiqb) ships DFLT, LMOD 1 and
LMOD 2, while its graph (OV08X40_KAFE799.IPU75XA.bin) tags the 1080p
video pipe with mode 4: gaic_find_all_tunings(60001, 4) returns nothing
and temporal noise reduction never engages. Measured on a Dell XPS 16
DA16260 at ISO 402: frame-to-frame noise on a flat wall drops from 9.2
to 1.9 once the default tuning is applied.
On failure, load the default (DFLT) ISP tuning and configure the CB
stages with mode 0, which is how the generic AIC indexes it. Tunings
that carry the requested mode are unchanged.
---
src/core/processingUnit/PipeLine.cpp | 23 +++++++++++++++++++++--
src/core/processingUnit/PipeLine.h | 3 +++
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/src/core/processingUnit/PipeLine.cpp b/src/core/processingUnit/PipeLine.cpp
index afd82f7..eebff11 100644
--- a/src/core/processingUnit/PipeLine.cpp
+++ b/src/core/processingUnit/PipeLine.cpp
@@ -53,6 +53,7 @@ void PipeLine::deinit() {
}
void PipeLine::updateIspTuningMode(TuningMode tuningMode) {
+ mUseDefaultIspTuning = false;
if (!PlatformData::supportUpdateTuning(mCameraId)) {
return;
}
@@ -74,8 +75,22 @@ void PipeLine::updateIspTuningMode(TuningMode tuningMode) {
CheckAndLogError(!intelCca, VOID_VALUE, "Failed to get IntelCca");
ia_err iaErr = intelCca->updateTuning(cca::CCA_LARD_ISP, lardParam, tmpNvm, mStreamId);
- CheckAndLogError(iaErr != ia_err_none, VOID_VALUE, "Failed to update tuning %u",
- ispTuningMode);
+ if (iaErr != ia_err_none) {
+ // The graph asks for an ISP tuning mode the tuning file does not carry (the
+ // OV08X40 PTL tuning ships DFLT, LMOD 1 and LMOD 2 while its graph asks for 4).
+ // Without a fallback the generic AIC finds no tunings for the mode, and every
+ // ISP kernel it tunes (noise reduction, TNR, sharpening, ...) runs on library
+ // defaults. Load the default ISP tuning and look it up as mode 0 instead.
+ LOGW("No ISP tuning for mode %u, falling back to the default tuning", ispTuningMode);
+ lardParam.isp_mode_index = IA_MKN_CHTOUL('D', 'F', 'L', 'T');
+ iaErr = intelCca->updateTuning(cca::CCA_LARD_ISP, lardParam, tmpNvm, mStreamId);
+ if (iaErr != ia_err_none) {
+ // The default ISP tuning loaded at init is still in place.
+ LOGW("Failed to reload the default ISP tuning (%d)", iaErr);
+ }
+ mUseDefaultIspTuning = true;
+ return;
+ }
LOG1("Update isp tuning mode %u, streamId %d", ispTuningMode, mStreamId);
}
@@ -292,6 +307,10 @@ status_t PipeLine::configurePipeStages() {
CheckAndLogError(!pNode, UNKNOWN_ERROR, "No PSys node for psys contextId %d",
unit.psysContextId);
+ if (mUseDefaultIspTuning) {
+ unit.node->nodeKernels.operationMode = 0;
+ }
+
std::unordered_map<uint8_t, TerminalConfig> terminalConfig;
ret = unit.ipuStage->configure(unit.node->nodeKernels,
const_cast<const GraphLink**>(links), numLinks,
diff --git a/src/core/processingUnit/PipeLine.h b/src/core/processingUnit/PipeLine.h
index 0f881f0..a72756d 100644
--- a/src/core/processingUnit/PipeLine.h
+++ b/src/core/processingUnit/PipeLine.h
@@ -134,6 +134,9 @@ class PipeLine {
std::map<uuid, TerminalInfo> mTerminalsDesc;
TuningMode mTuningMode;
+ // The tuning has no ISP container for the graph's tuning mode, so the default
+ // (DFLT) ISP tuning is loaded and looked up as mode 0.
+ bool mUseDefaultIspTuning = false;
PSysDevice* mPSysDevice;
PSysGraph mPSysGraph;
@@ -0,0 +1,28 @@
From: Kolbas <pkolbas@pm.me>
Subject: [PATCH] ipu7x: set Intel CVS pad formats for ov08x40
Lunar Lake analog of Omarchy's 0006 (ipu75xa) and of the ipu8 config part of
upstream intel/ipu7-camera-hal f167239b3ecf. On Linux 7.2 the "Intel CVS"
bridge entity sits between ov08x40 and the IPU7 CSI2 receiver; its pad formats
must be set or link validation fails at stream-on. Links stay sensor -> CSI2:
MediaControl (0005) rewrites them through the bridge at runtime.
---
diff --git i/config/linux/ipu7x/sensors/ov08x40-uf.json w/config/linux/ipu7x/sensors/ov08x40-uf.json
index 7123d45..472b0da 100644
--- i/config/linux/ipu7x/sensors/ov08x40-uf.json
+++ w/config/linux/ipu7x/sensors/ov08x40-uf.json
@@ -31,6 +31,14 @@
"name": "ov08x40 $I2CBUS", "pad": 0, "width": 3856, "height": 2176,
"format": "V4L2_MBUS_FMT_SGRBG10_1X10"
},
+ {
+ "name": "Intel CVS", "pad": 0, "width": 3856, "height": 2176,
+ "format": "V4L2_MBUS_FMT_SGRBG10_1X10"
+ },
+ {
+ "name": "Intel CVS", "pad": 1, "width": 3856, "height": 2176,
+ "format": "V4L2_MBUS_FMT_SGRBG10_1X10"
+ },
{
"name": "Intel IPU7 CSI2 $CSI_PORT", "pad": 0, "width": 3856, "height": 2176,
"format": "V4L2_MBUS_FMT_SGRBG10_1X10"
@@ -0,0 +1 @@
KERNEL=="ipu7-psys0", GROUP="root", MODE="0600", SYMLINK+="ipu-psys0"
@@ -1 +0,0 @@
KERNEL=="ipu7-psys0", MODE="0666", SYMLINK+="ipu-psys0"
+96 -49
View File
@@ -2,7 +2,7 @@
pkgname=intel-ipu7-camera
pkgver=1.0.6
pkgrel=2
pkgrel=3
pkgdesc="Intel IPU7 MIPI camera stack for Hurrican/Performance (OV08X40 + hardware ISP)"
arch=('x86_64')
url="https://github.com/TsaiGaggery/hurrican_omarchy_enabling"
@@ -27,6 +27,7 @@ makedepends=(
'libtool'
'pkgconf'
)
checkdepends=('linux-omarchy-headers')
install=intel-ipu7-camera.install
options=('!strip')
@@ -37,22 +38,23 @@ _ipu7_camera_hal_commit="b1f6ebef12111fb5da0133b144d69dd9b001836c"
_icamerasrc_commit="4fb31db76b618aae72184c59314b839dedb42689"
# Linux 7.2 moved the Intel CVS (Computer Vision Sensing) controller into the
# IPU media graph: ipu-bridge now places it between the sensor and the IPU
# CSI2 receiver as an "Intel CVS" V4L2 bridge sub-device, provided by the
# in-tree drivers/media/i2c/cvs driver. The legacy vision-drivers misc driver
# has no such sub-device, so on 7.2 the sensor never joins the graph and the
# HAL finds no camera. We ship the in-tree driver as a DKMS module
# for 7.2+ kernels (Arch-derived kernel configs cannot enable it: the option
# is hidden behind MEDIA_HIDE_ANCILLARY_SUBDRV) and keep vision-drivers for
# older kernels. Both dkms.conf files carry matching BUILD_EXCLUSIVE_KERNEL.
# IPU media graph, so the sensor only appears once drivers/media/i2c/cvs is
# loaded. linux-omarchy builds that driver in-tree, signed and carrying the
# XPS 14 speaker-ID wake-IRQ fix, and camera-init loads it. Kernels before 7.2
# use the legacy misc driver from vision-drivers instead.
#
# 0007 is the upstream fix for the audio regression on the Dell XPS 14
# DA14260: the driver requested its "wake" GPIO, which shares a pin with the
# CS35L57 amplifiers' speaker-ID GpioIo, so every cs35l56 probe failed with
# -EBUSY and the sound card never appeared. Drop it once the stable tag
# fetched below contains the fix.
_intel_cvs_kernel_tag="v7.2.5"
_intel_cvs_url="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/media/i2c/cvs"
# 0008-0011 are image quality fixes for the XPS 14 / 16 camera:
# - 0011: the graph asks for ISP tuning mode 4, which the OV08X40 tuning does
# not carry, so no ISP tuning at all (noise reduction, TNR, sharpening, ...)
# reached the hardware. Fall back to the tuning's default ISP container.
# - 0010: the tuning makes AIQ emit the raw analog gain register code, but
# the in-tree ov08x40 driver takes half of it, so the sensor ran at twice
# the gain AE and the ISP noise model assumed.
# - 0008: the HAL ignored the requested YUV range and always produced
# full-range frames that consumers decode as limited range.
# - 0009: icamerasrc pinned auto exposure to exactly 1/30 s; its new
# fps-range property lets AE lengthen frames in dim light instead of
# adding gain (see v4l2-relayd-ipu7.conf).
source=(
"ipu7-drivers::git+https://github.com/intel/ipu7-drivers.git#commit=${_ipu7_drivers_commit}"
@@ -60,18 +62,19 @@ source=(
"ipu7-camera-bins::git+https://github.com/intel/ipu7-camera-bins.git#commit=${_ipu7_camera_bins_commit}"
"ipu7-camera-hal::git+https://github.com/intel/ipu7-camera-hal.git#commit=${_ipu7_camera_hal_commit}"
"icamerasrc::git+https://github.com/intel/icamerasrc.git#commit=${_icamerasrc_commit}"
"intel-cvs-core.c::${_intel_cvs_url}/core.c?h=${_intel_cvs_kernel_tag}"
"intel-cvs-v4l2.c::${_intel_cvs_url}/v4l2.c?h=${_intel_cvs_kernel_tag}"
"intel-cvs-icvs.h::${_intel_cvs_url}/icvs.h?h=${_intel_cvs_kernel_tag}"
"0004-ipu7-psys-register-device-bus.patch"
"0005-ipu7-psys-harden-userptr-pinning.patch"
"check-userptr-range.c"
"0003-icvs-set-rgbcamera_pwrup_host-0-for-Panther-Lake.patch"
"0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch"
"0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch"
"0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch"
"0012-camhal-ipu7x-ov08x40-Intel-CVS-formats.patch"
"0008-camhal-honor-the-reduced-YUV-color-range.patch"
"0009-icamerasrc-add-an-fps-range-property-for-auto-exposure.patch"
"0010-camhal-scale-analog-gain-codes-to-the-ov08x40-driver-units.patch"
"0011-camhal-fall-back-to-the-default-ISP-tuning-when-the-mode-is-missing.patch"
"dkms-ipu7-drivers.conf"
"dkms-vision-drivers.conf"
"dkms-intel-cvs.conf"
"intel-cvs-Makefile"
"camera-deps.conf"
"v4l2loopback-modprobe.conf"
"camera-init.service"
@@ -79,27 +82,28 @@ source=(
"hide-ipu7-v4l2.conf"
"disable-libcamera.conf"
"71-ipu7-hide-isys.rules"
"90-ipu7-psys.rules"
"70-ipu7-psys.rules"
"camera-tmpfiles.conf"
"ov08x40.yaml"
"camera-sleep-hook"
"v4l2-relayd-ipu7.conf"
"v4l2-relayd-ipu7-override.conf"
"camera-videosrc-init"
)
sha256sums=(
'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'b4a90ad1815c02e4cadc1b5ad6c576ae0b150cb365c68d2c934cf964a995d91f'
'e988014f54b1b5183e9ef43b602d73bbc2c991f19a8d503560a8cdba112b76c6'
'6dcd5201fb78766a440038c8eba2cc8c3892064ba829065e2269c30dc1905983'
'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'SKIP'
'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP'
'SKIP' 'SKIP' 'SKIP' 'SKIP'
)
prepare() {
cd "${srcdir}/ipu7-drivers"
patch -Np1 -i "${srcdir}/0004-ipu7-psys-register-device-bus.patch"
# --fuzz=0: a security patch that lands somewhere other than its anchor still
# compiles, and the build is the only place that can refuse it.
patch -Np1 --fuzz=0 -i "${srcdir}/0004-ipu7-psys-register-device-bus.patch"
patch -Np1 --fuzz=0 -i "${srcdir}/0005-ipu7-psys-harden-userptr-pinning.patch"
cd "${srcdir}/vision-drivers"
git config user.email "build@localhost"
@@ -112,15 +116,13 @@ prepare() {
cd "${srcdir}/ipu7-camera-hal"
patch -Np1 -i "${srcdir}/0005-camhal-MediaControl-route-through-Intel-CVS-bridge.patch"
patch -Np1 -i "${srcdir}/0006-camhal-ipu75xa-ov08x40-Intel-CVS-formats.patch"
patch -Np1 -i "${srcdir}/0012-camhal-ipu7x-ov08x40-Intel-CVS-formats.patch"
patch -Np1 -i "${srcdir}/0008-camhal-honor-the-reduced-YUV-color-range.patch"
patch -Np1 -i "${srcdir}/0010-camhal-scale-analog-gain-codes-to-the-ov08x40-driver-units.patch"
patch -Np1 -i "${srcdir}/0011-camhal-fall-back-to-the-default-ISP-tuning-when-the-mode-is-missing.patch"
# Intel CVS: take the wake IRQ without claiming the GPIO (shared with the
# speaker-ID line on the XPS 14). makepkg symlinks plain downloads into
# srcdir and patch refuses to touch a symlink, so patch a real copy. The
# patch is against a/drivers/media/i2c/cvs/core.c, hence -p5.
cp --remove-destination "$(readlink -f "${srcdir}/intel-cvs-core.c")" \
"${srcdir}/intel-cvs-core.c"
patch -p5 -F0 --no-backup-if-mismatch "${srcdir}/intel-cvs-core.c" \
< "${srcdir}/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch"
cd "${srcdir}/icamerasrc"
patch -Np1 -i "${srcdir}/0009-icamerasrc-add-an-fps-range-property-for-auto-exposure.patch"
# Stage proprietary libs/headers for build-time use
local staging="${srcdir}/staging"
@@ -157,7 +159,7 @@ build() {
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_CAMHAL_ADAPTOR=ON \
-DBUILD_CAMHAL_PLUGIN=ON \
-DIPU_VERSIONS="ipu75xa" \
-DIPU_VERSIONS="ipu7x;ipu75xa" \
-DUSE_STATIC_GRAPH=ON \
-DUSE_STATIC_GRAPH_AUTOGEN=ON \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
@@ -179,6 +181,54 @@ build() {
make -j"$(nproc)"
}
check() {
local driver_tree="${srcdir}/ipu7-drivers"
local psys_source="${srcdir}/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c"
local check_tree="${srcdir}/ipu7-drivers-check"
local kernel_build kernel_files grep_result result=0 attach_failure
[[ -f $psys_source && -r $psys_source ]] || return 1
grep -q 'kvmalloc_array(npages, sizeof(\*pages)' "${psys_source}" || return 1
grep -q 'attach->len > MAX_RW_COUNT' "${psys_source}" || return 1
if grep 'FOLL_FORCE' "${psys_source}" >/dev/null; then
return 1
else
grep_result=$?
(( grep_result == 1 )) || return 1
fi
attach_failure=$(sed -n '/^attach_fail:$/,+1p' "$psys_source") || return 1
[[ $attach_failure == $'attach_fail:\n\tkbuf->db_attach = NULL;' ]] || return 1
cc -std=c11 -O2 -Wall -Wextra -Werror \
"${srcdir}/check-userptr-range.c" -o "${srcdir}/check-userptr-range" || return 1
"${srcdir}/check-userptr-range" || return 1
kernel_files=$(pacman -Qql linux-omarchy-headers) || return 1
kernel_build="$(sed -n \
'\#/build/Makefile$# { s#/Makefile$##; s#^#/#; p; q; }' <<<"$kernel_files")" || return 1
if [[ ! -f "${kernel_build}/Makefile" ]]; then
printf 'Unable to locate the linux-omarchy-headers build tree\n' >&2
return 1
fi
(
cd "${srcdir}"
"${kernel_build}/scripts/checkpatch.pl" --no-tree --strict --no-signoff \
0005-ipu7-psys-harden-userptr-pinning.patch
) || return 1
rm -rf "${check_tree}" || return 1
cp -a "${driver_tree}" "${check_tree}" || return 1
if ! make -C "${check_tree}" BUILD_INTEL_IPU_ACPI=1 \
KERNEL_SRC="${kernel_build}"; then
rm -rf "${check_tree}"
return 1
fi
[[ -s ${check_tree}/drivers/media/pci/intel/ipu7/psys/intel-ipu7-psys.ko ]] || result=1
rm -rf "${check_tree}" || return 1
return "$result"
}
package() {
# DKMS: IPU7 drivers (ISYS + PSYS + ACPI)
local ipu7_dkms_dir="${pkgdir}/usr/src/ipu7-drivers-${pkgver}"
@@ -195,15 +245,6 @@ package() {
install -Dm644 "${srcdir}/dkms-vision-drivers.conf" "${cvs_dkms_dir}/dkms.conf"
sed -i "s/@PKGVER@/${pkgver}/" "${cvs_dkms_dir}/dkms.conf"
# DKMS: in-tree Linux 7.2 Intel CVS V4L2 bridge driver, kernels 7.2+
local icvs_dkms_dir="${pkgdir}/usr/src/intel-cvs-${pkgver}"
install -Dm644 "${srcdir}/intel-cvs-core.c" "${icvs_dkms_dir}/core.c"
install -Dm644 "${srcdir}/intel-cvs-v4l2.c" "${icvs_dkms_dir}/v4l2.c"
install -Dm644 "${srcdir}/intel-cvs-icvs.h" "${icvs_dkms_dir}/icvs.h"
install -Dm644 "${srcdir}/intel-cvs-Makefile" "${icvs_dkms_dir}/Makefile"
install -Dm644 "${srcdir}/dkms-intel-cvs.conf" "${icvs_dkms_dir}/dkms.conf"
sed -i "s/@PKGVER@/${pkgver}/" "${icvs_dkms_dir}/dkms.conf"
# Firmware
install -dm755 "${pkgdir}/usr/lib/firmware/intel/ipu"
install -Dm644 "${srcdir}/ipu7-camera-bins/lib/firmware/intel/ipu/"*.bin \
@@ -267,8 +308,10 @@ package() {
# udev rules
install -Dm644 "${srcdir}/71-ipu7-hide-isys.rules" \
"${pkgdir}/usr/lib/udev/rules.d/71-ipu7-hide-isys.rules"
install -Dm644 "${srcdir}/90-ipu7-psys.rules" \
"${pkgdir}/usr/lib/udev/rules.d/90-ipu7-psys.rules"
# Root-only: the relay is the PSYS node's only user, and the driver's buffer
# lifecycle has use-after-free bugs that anyone who can open it can reach.
install -Dm644 "${srcdir}/70-ipu7-psys.rules" \
"${pkgdir}/usr/lib/udev/rules.d/70-ipu7-psys.rules"
# v4l2-relayd config for IPU7 camera
install -Dm644 "${srcdir}/v4l2-relayd-ipu7.conf" \
@@ -283,4 +326,8 @@ package() {
# Sleep hook — staged here, installed to system-sleep by the .install scriptlet
install -Dm755 "${srcdir}/camera-sleep-hook" \
"${pkgdir}/usr/share/intel-ipu7-camera/camera-sleep-hook"
# Picks the relay pipeline for this board; run before each relay start
install -Dm755 "${srcdir}/camera-videosrc-init" \
"${pkgdir}/usr/share/intel-ipu7-camera/camera-videosrc-init"
}
@@ -1 +1 @@
d /run/camera 0777 root root -
d /run/camera 0770 root video -
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Drop the relay's 180-degree rotation on boards whose sensor is mounted upright.
#
# The packaged pipeline rotates because the sensor is mounted inverted on the
# Panther Lake boards this package was first written for. On Lunar Lake it is
# not: the sensor reports camera_sensor_rotation = 0 and camera_orientation =
# Front, so the rotation turns an already-correct frame upside down. Keyed on
# the bridge ACPI id; anything not positively identified as Lunar Lake keeps
# the configured pipeline untouched:
#
# INTC10DE Lunar Lake no rotation
# INTC10E1 Panther Lake rotate-180, as packaged
# INTC10CF Meteor Lake rotate-180, as packaged
# INTC10E0 Arrow Lake rotate-180, as packaged
#
# Runs as v4l2-relayd@ipu7's ExecStartPre, after one that removes the previous
# override, so VIDEOSRC arrives parsed by systemd from the configured files.
# ExecStart re-reads the drop-in's optional EnvironmentFile, so an edited
# /etc/v4l2-relayd.d/ipu7.conf takes effect on the next start.
set -eu
out=/run/v4l2-relayd-ipu7-videosrc.env
rm -f "${out}"
shopt -s nullglob
lunar_lake=(/sys/bus/acpi/devices/INTC10DE:*)
shopt -u nullglob
((${#lunar_lake[@]} > 0)) || exit 0
src=${VIDEOSRC:-}
[[ -n ${src} ]] || exit 0
src=${src/ ! videoflip method=rotate-180/}
# Quote for systemd's EnvironmentFile parser, which unescapes these in "...".
src=${src//\\/\\\\}
src=${src//\"/\\\"}
src=${src//\$/\\\$}
src=${src//\`/\\\`}
printf 'VIDEOSRC="%s"\n' "${src}" >"${out}"
@@ -0,0 +1,50 @@
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define MAX_RW_COUNT (INT_MAX & ~(PAGE_SIZE - 1))
static int page_array_size(uintptr_t start, uint64_t len,
size_t *npages, size_t *bytes)
{
uintptr_t last;
if (!len || len > MAX_RW_COUNT)
return -EINVAL;
if (__builtin_add_overflow(start, len - 1, &last))
return -EOVERFLOW;
*npages = (((last & ~(PAGE_SIZE - 1)) -
(start & ~(PAGE_SIZE - 1))) >> PAGE_SHIFT) + 1;
if (!*npages || *npages > INT_MAX)
return -E2BIG;
if (__builtin_mul_overflow(*npages, sizeof(void *), bytes))
return -EOVERFLOW;
return 0;
}
int main(void)
{
const uintptr_t start = UINT64_C(0x100000000);
const uint64_t exploit_pages = UINT64_C(0x20000001);
const uint64_t ordinary_size = UINT64_C(64) * 1024 * 1024;
size_t npages;
size_t bytes;
assert(page_array_size(start, ordinary_size, &npages, &bytes) == 0);
assert(npages == ordinary_size / PAGE_SIZE);
assert(bytes == (ordinary_size / PAGE_SIZE) * sizeof(void *));
assert(page_array_size(start, exploit_pages * PAGE_SIZE,
&npages, &bytes) == -EINVAL);
assert(page_array_size(UINTPTR_MAX - 100, 200,
&npages, &bytes) == -EOVERFLOW);
assert(page_array_size(start, 0, &npages, &bytes) == -EINVAL);
return 0;
}
@@ -1,26 +0,0 @@
# Intel CVS (Computer Vision Sensing) CSI-2 bridge driver.
#
# Copy of the in-tree Linux 7.2 driver (drivers/media/i2c/cvs) plus the
# upstream wake-IRQ fix carried by the PKGBUILD, built out of tree so it
# exists on every 7.2+ kernel regardless of the kernel's Kconfig.
# Arch-derived configs cannot enable VIDEO_INTEL_CVS at all: it lives under
# the "Miscellaneous helper chips" menu, which is hidden by
# MEDIA_HIDE_ANCILLARY_SUBDRV unless CONFIG_EXPERT is set.
#
# 7.2's ipu-bridge inserts the CVS device between the sensor and the IPU
# CSI2 receiver, so the IPU only sees the sensor once a driver registers
# this V4L2 bridge sub-device. Kernels before 7.2 keep using the legacy
# misc driver from vision-drivers (see dkms-vision-drivers.conf).
PACKAGE_NAME="intel-cvs"
PACKAGE_VERSION="@PKGVER@"
AUTOINSTALL="yes"
# 7.2 and later only (regex, so the pacman dkms hook also honours it)
BUILD_EXCLUSIVE_KERNEL="^(7\.([2-9]|[1-9][0-9]+)\.|([8-9]|[1-9][0-9]+)\.)"
BUILT_MODULE_NAME[0]="intel_cvs"
BUILT_MODULE_LOCATION[0]=""
DEST_MODULE_LOCATION[0]="/updates"
MAKE[0]="make KERNEL_SRC=${kernel_source_dir}"
CLEAN="make KERNEL_SRC=${kernel_source_dir} clean"
@@ -1,8 +1,7 @@
# Legacy Intel CVS misc driver (no V4L2 bridge). Only for kernels before
# 7.2: from 7.2 on, ipu-bridge routes the sensor through a CVS V4L2 bridge
# sub-device, which this driver does not provide, and it would displace the
# in-tree intel_cvs module of the same name. 7.2+ kernels use intel-cvs
# (see dkms-intel-cvs.conf) instead.
# in-tree intel_cvs module of the same name, which 7.2+ kernels use instead.
PACKAGE_NAME="vision-drivers"
PACKAGE_VERSION="@PKGVER@"
AUTOINSTALL="yes"
@@ -1,15 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
# Out-of-tree build of the in-tree Linux 7.2 Intel CVS driver
# (drivers/media/i2c/cvs). Sources are kernel files plus the upstream
# wake-IRQ fix carried by the PKGBUILD.
KERNELRELEASE ?= $(shell uname -r)
KERNEL_SRC ?= /lib/modules/$(KERNELRELEASE)/build
obj-m := intel_cvs.o
intel_cvs-y := core.o v4l2.o
all:
$(MAKE) -C $(KERNEL_SRC) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_SRC) M=$(CURDIR) clean
@@ -19,12 +19,31 @@ _install_sleep_hook() {
chmod 755 /usr/lib/systemd/system-sleep/camera-sleep-hook
}
_apply_runtime_permissions() {
if ! /usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/camera.conf; then
echo ":: WARNING: failed to restrict /run/camera; reboot before using the camera." >&2
fi
if [ -S /run/udev/control ]; then
if ! /usr/bin/udevadm control --reload-rules ||
! /usr/bin/udevadm trigger --action=change --sysname-match=ipu7-psys0; then
echo ":: WARNING: failed to restrict the IPU7 device; reboot before using the camera." >&2
fi
fi
}
_warn_if_driver_is_loaded() {
if [ -d /sys/module/intel_ipu7_psys ]; then
echo ":: SECURITY: reboot required to activate the updated IPU7 kernel driver." >&2
fi
}
post_install() {
_install_sleep_hook
_apply_runtime_permissions
systemctl daemon-reload
systemctl disable camera-init.service v4l2-relayd@ipu7.service 2>/dev/null || true
systemctl enable intel-ipu7-camera.service
udevadm control --reload-rules 2>/dev/null || true
if [ -n "$SUDO_USER" ]; then
usermod -aG video "$SUDO_USER" 2>/dev/null || true
@@ -37,12 +56,13 @@ post_install() {
post_upgrade() {
_install_sleep_hook
_apply_runtime_permissions
_warn_if_driver_is_loaded
systemctl daemon-reload
systemctl disable camera-init.service v4l2-relayd@ipu7.service 2>/dev/null || true
systemctl enable intel-ipu7-camera.service
# Disable old camera-feed-watch (replaced by v4l2-relayd)
systemctl --global disable camera-feed-watch.service 2>/dev/null || true
udevadm control --reload-rules 2>/dev/null || true
if [ -n "$SUDO_USER" ]; then
_add_pipewire_camera "/home/$SUDO_USER/.config/chromium-flags.conf"
@@ -3,6 +3,14 @@ After=camera-init.service
[Service]
Environment=GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0
# Board-specific VIDEOSRC, regenerated on every start from the configured one.
# Optional on purpose: without it /etc/v4l2-relayd.d/ipu7.conf applies unchanged.
EnvironmentFile=-/run/v4l2-relayd-ipu7-videosrc.env
# Remove the last override first: each ExecStartPre re-reads the environment
# files, so the generator then sees the configured VIDEOSRC, not a stale one.
ExecStartPre=-/usr/bin/rm -f /run/v4l2-relayd-ipu7-videosrc.env
ExecStartPre=-/usr/share/intel-ipu7-camera/camera-videosrc-init
ExecStopPost=-/usr/bin/rm -f /run/v4l2-relayd-ipu7-videosrc.env
# Relax sandboxing for icamerasrc hardware access
PrivateNetwork=no
InaccessibleDirectories=
@@ -1,4 +1,7 @@
VIDEOSRC="icamerasrc device-name=ov08x40-uf sharpness=80 ev=-1 saturation=10 ! videoflip method=rotate-180"
# fps-range and gain-range: in dim light AE may lengthen frames down to 15 fps
# and never raises gain past 27 dB, instead of holding 30 fps at ~48x gain.
# color-range=reduced: consumers decode NV12 webcam frames as limited range.
VIDEOSRC="icamerasrc device-name=ov08x40-uf fps-range=15~30 gain-range=0~27 color-range=reduced ! videoflip method=rotate-180"
FORMAT=NV12
WIDTH=1920
HEIGHT=1080
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
set -euo pipefail
REPO_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
PKGBUILD=${1:-"$REPO_ROOT/pkgbuilds/intel-ipu7-camera/PKGBUILD"}
(( $# <= 1 )) && [[ -f $PKGBUILD ]] || { echo 'Usage: ipu7-headers.sh [PKGBUILD]' >&2; exit 1; }
fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
has_package() {
local sought=$1 package
shift
for package in "$@"; do
[[ $package == "$sought" ]] && return 0
done
return 1
}
# Sourcing reads the real metadata and defines the real check(), without
# running prepare(), build(), package(), or any hardware operation.
source "$PKGBUILD"
for package in linux-headers linux-omarchy-headers; do
if has_package "$package" "${depends[@]}"; then
fail "$package must not be a runtime dependency"
fi
done
declare -p checkdepends >/dev/null 2>&1 || fail 'checkdepends is missing'
has_package linux-omarchy-headers "${checkdepends[@]}" || fail 'Omarchy headers are missing from checkdepends'
if has_package linux-headers "${checkdepends[@]}"; then
fail 'Arch headers must not be a check dependency'
fi
TEST_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/ipu7-headers.XXXXXXXX")
cleanup() {
if [[ -n ${TEST_ROOT:-} && -d $TEST_ROOT && ${TEST_ROOT##*/} == ipu7-headers.* ]]; then
rm -rf -- "$TEST_ROOT"
fi
}
trap cleanup EXIT
srcdir="$TEST_ROOT/src"
TEST_KERNEL_BUILD="$TEST_ROOT/kernel/build"
TEST_CALLS="$TEST_ROOT/calls"
export TEST_KERNEL_BUILD TEST_CALLS srcdir
mkdir -p "$TEST_ROOT/bin" "$srcdir/ipu7-drivers/drivers/media/pci/intel/ipu7/psys" \
"$TEST_KERNEL_BUILD/scripts"
printf '%s\n' 'fixture kernel Makefile' >"$TEST_KERNEL_BUILD/Makefile"
printf '%s\n' 'fixture patch' >"$srcdir/0005-ipu7-psys-harden-userptr-pinning.patch"
printf '%s\n' 'fixture source' >"$srcdir/check-userptr-range.c"
cat >"$srcdir/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c" <<'EOF'
kvmalloc_array(npages, sizeof(*pages)
attach->len > MAX_RW_COUNT
attach_fail:
kbuf->db_attach = NULL;
EOF
# Each command accepts only the one call made by check(). All output stays in
# the disposable fixture, including the stand-in compiled program and module.
cat >"$TEST_ROOT/bin/stub" <<'EOF'
#!/bin/bash
set -euo pipefail
case ${0##*/} in
stub) [[ ${1:-} == '--probe' ]] || exit 1 ;;
pacman)
printf 'pacman %s\n' "$*" >>"$TEST_CALLS"
if (( $# != 2 )) || [[ $1 != '-Qql' || $2 != 'linux-omarchy-headers' ]]; then
printf 'Unexpected pacman query: %s\n' "$*" >&2
exit 1
fi
[[ $TEST_SCENARIO != 'missing-package' ]] || exit 1
if [[ $TEST_SCENARIO == 'missing-makefile' ]]; then
printf '%s\n' "$TEST_KERNEL_BUILD/missing/build/Makefile"
else
printf '%s\n' "$TEST_KERNEL_BUILD/Makefile"
fi
;;
cc)
printf 'cc %s\n' "$*" >>"$TEST_CALLS"
(( $# == 8 )) && [[ $6 == "$srcdir/check-userptr-range.c" && $7 == '-o' && $8 == "$srcdir/check-userptr-range" ]] || exit 1
cat >"$8" <<'PROGRAM'
#!/bin/bash
printf '%s\n' 'userptr fixture ran' >>"$TEST_CALLS"
PROGRAM
chmod +x "$8"
;;
make)
printf 'make %s\n' "$*" >>"$TEST_CALLS"
(( $# == 4 )) && [[ $1 == '-C' && $2 == "$srcdir/ipu7-drivers-check" && $3 == 'BUILD_INTEL_IPU_ACPI=1' && $4 == "KERNEL_SRC=/$TEST_KERNEL_BUILD" ]] || exit 1
mkdir -p "$2/drivers/media/pci/intel/ipu7/psys"
printf '%s\n' 'fixture module' >"$2/drivers/media/pci/intel/ipu7/psys/intel-ipu7-psys.ko"
;;
checkpatch.pl)
printf 'checkpatch %s\n' "$*" >>"$TEST_CALLS"
(( $# == 4 )) && [[ $1 == '--no-tree' && $2 == '--strict' && $3 == '--no-signoff' && $4 == '0005-ipu7-psys-harden-userptr-pinning.patch' ]] || exit 1
[[ -f $4 ]] || exit 1
;;
*) exit 1 ;;
esac
EOF
chmod +x "$TEST_ROOT/bin/stub"
"$TEST_ROOT/bin/stub" --probe 2>/dev/null || fail 'Set TMPDIR to an executable temporary directory for the test fixtures'
for command in pacman cc make; do ln -s stub "$TEST_ROOT/bin/$command"; done
ln -s "$TEST_ROOT/bin/stub" "$TEST_KERNEL_BUILD/scripts/checkpatch.pl"
export PATH="$TEST_ROOT/bin:$PATH"
TEST_SCENARIO=success
export TEST_SCENARIO
: >"$TEST_CALLS"
check || fail 'check() rejected valid Omarchy headers fixture'
grep -Fxq 'pacman -Qql linux-omarchy-headers' "$TEST_CALLS" || fail 'Omarchy headers were not queried'
grep -Fxq 'userptr fixture ran' "$TEST_CALLS" || fail 'compiled fixture was not run'
grep -Fxq 'checkpatch --no-tree --strict --no-signoff 0005-ipu7-psys-harden-userptr-pinning.patch' "$TEST_CALLS" || fail 'checkpatch was not reached'
grep -Fxq "make -C $srcdir/ipu7-drivers-check BUILD_INTEL_IPU_ACPI=1 KERNEL_SRC=/$TEST_KERNEL_BUILD" "$TEST_CALLS" || fail 'module build did not use Omarchy headers'
for TEST_SCENARIO in missing-package missing-makefile; do
: >"$TEST_CALLS"
if check; then fail "check() accepted $TEST_SCENARIO"; fi
grep -Fxq 'pacman -Qql linux-omarchy-headers' "$TEST_CALLS" || fail "headers query skipped for $TEST_SCENARIO"
if grep -Eq '^(checkpatch|make) ' "$TEST_CALLS"; then fail "build continued after $TEST_SCENARIO"; fi
done
echo 'PASS: IPU7 header metadata, Omarchy build path, and missing-header failures'
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
set -euo pipefail
REPO_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
TEST_ROOT=$(mktemp -d)
trap 'rm -rf -- "$TEST_ROOT"' EXIT
CALLS="$TEST_ROOT/calls"
# Exercise the real scriptlet with only system operations and state probes
# substituted. No host files, services, udev state or modules are changed.
script=$(<"$REPO_ROOT/pkgbuilds/intel-ipu7-camera/intel-ipu7-camera.install")
script=${script//\/usr\/bin\/systemd-tmpfiles/fixture_tmpfiles}
script=${script//\/usr\/bin\/udevadm/fixture_udevadm}
source /dev/stdin <<<"$script"
fixture_tmpfiles() {
printf 'tmpfiles %s\n' "$*" >>"$CALLS"
[[ $scenario != "tmpfiles-fail" ]]
}
fixture_udevadm() {
printf 'udev %s\n' "$*" >>"$CALLS"
case "$scenario:$1" in
reload-fail:control|trigger-fail:trigger) return 1 ;;
esac
}
function [() {
case "$*" in
'-S /run/udev/control ]') [[ $scenario != "no-udev" ]] ;;
'-d /sys/module/intel_ipu7_psys ]') [[ $loaded == "yes" ]] ;;
*) builtin [ "$@" ;;
esac
}
_install_sleep_hook() { echo sleep-hook >>"$CALLS"; }
_add_pipewire_camera() { printf 'browser %s\n' "$*" >>"$CALLS"; }
systemctl() { printf 'systemctl %s\n' "$*" >>"$CALLS"; }
usermod() { printf 'usermod %s\n' "$*" >>"$CALLS"; }
SUDO_USER=fixture-user
fail() { printf 'FAIL: %s (%s, loaded=%s, %s)\n' "$*" "$hook" "$loaded" "$scenario" >&2; exit 1; }
for hook in post_install post_upgrade; do
for loaded in yes no; do
for scenario in reload-fail trigger-fail tmpfiles-fail no-udev success; do
: >"$CALLS"
# Pacman scriptlets do not use errexit; a refresh failure must not skip
# the remaining lifecycle work, including the loaded-driver warning.
output=$(set +e; "$hook" 2>&1) || fail "scriptlet failed"
[[ $output != *"restricted device permissions are active"* ]] || fail "unverified permission assurance"
if [[ $hook == "post_upgrade" && $loaded == "yes" ]]; then
[[ $output == *"reboot required to activate the updated IPU7 kernel driver"* ]] || fail "missing reboot warning"
else
[[ $output != *"reboot required to activate"* ]] || fail "unexpected loaded-driver warning"
fi
case "$scenario" in
tmpfiles-fail) [[ $output == *"failed to restrict /run/camera"* ]] || fail "missing directory warning" ;;
reload-fail|trigger-fail) [[ $output == *"failed to restrict the IPU7 device"* ]] || fail "missing device warning" ;;
*) [[ $output != *"WARNING:"* ]] || fail "unexpected refresh warning" ;;
esac
grep -qx 'sleep-hook' "$CALLS" || fail "sleep hook skipped"
grep -qx 'tmpfiles --create /usr/lib/tmpfiles.d/camera.conf' "$CALLS" || fail "permission refresh skipped"
grep -qx 'systemctl daemon-reload' "$CALLS" || fail "daemon reload skipped"
grep -qx 'systemctl enable intel-ipu7-camera.service' "$CALLS" || fail "service enable skipped"
[[ $(grep -c '^browser ' "$CALLS") == 3 ]] || fail "browser lifecycle skipped"
if [[ $scenario == "no-udev" ]]; then
! grep -q '^udev ' "$CALLS" || fail "contacted absent udev"
else
grep -qx 'udev control --reload-rules' "$CALLS" || fail "udev reload skipped"
if [[ $scenario != "reload-fail" ]]; then
grep -qx 'udev trigger --action=change --sysname-match=ipu7-psys0' "$CALLS" || fail "udev trigger skipped"
fi
fi
done
done
done
echo 'PASS: 20 IPU7 install/upgrade warning and lifecycle cases'