diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e7331db..80a59cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/pkgbuilds/intel-ipu7-camera/.gitattributes b/pkgbuilds/intel-ipu7-camera/.gitattributes new file mode 100644 index 0000000..5c0496d --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/.gitattributes @@ -0,0 +1,2 @@ +# Diff context has a marker space before upstream tabs and blank lines. +*.patch whitespace=-blank-at-eol,-space-before-tab diff --git a/pkgbuilds/intel-ipu7-camera/0005-ipu7-psys-harden-userptr-pinning.patch b/pkgbuilds/intel-ipu7-camera/0005-ipu7-psys-harden-userptr-pinning.patch new file mode 100644 index 0000000..22fa4f8 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0005-ipu7-psys-harden-userptr-pinning.patch @@ -0,0 +1,153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Afonso Oliveira +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 + #include + #include ++#include + #include + #include + #include +@@ -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); diff --git a/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch b/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch deleted file mode 100644 index 3fd68bd..0000000 --- a/pkgbuilds/intel-ipu7-camera/0007-media-i2c-cvs-get-the-wake-irq-without-claiming-the-gpio.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Junjie Cao -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 ---- - 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"); diff --git a/pkgbuilds/intel-ipu7-camera/0008-camhal-honor-the-reduced-YUV-color-range.patch b/pkgbuilds/intel-ipu7-camera/0008-camhal-honor-the-reduced-YUV-color-range.patch new file mode 100644 index 0000000..510cb1d --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0008-camhal-honor-the-reduced-YUV-color-range.patch @@ -0,0 +1,35 @@ +From d358e3b1bcf9f26638ae326e979661d0f2c740e2 Mon Sep 17 00:00:00 2001 +From: Spencer Bull +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::epsilon()) { diff --git a/pkgbuilds/intel-ipu7-camera/0009-icamerasrc-add-an-fps-range-property-for-auto-exposure.patch b/pkgbuilds/intel-ipu7-camera/0009-icamerasrc-add-an-fps-range-property-for-auto-exposure.patch new file mode 100644 index 0000000..fd74228 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0009-icamerasrc-add-an-fps-range-property-for-auto-exposure.patch @@ -0,0 +1,110 @@ +From 2d2f5f56e4f4033df3bd1e51fdc519c59ddb6f09 Mon Sep 17 00:00:00 2001 +From: Spencer Bull +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; diff --git a/pkgbuilds/intel-ipu7-camera/0010-camhal-scale-analog-gain-codes-to-the-ov08x40-driver-units.patch b/pkgbuilds/intel-ipu7-camera/0010-camhal-scale-analog-gain-codes-to-the-ov08x40-driver-units.patch new file mode 100644 index 0000000..89097bc --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0010-camhal-scale-analog-gain-codes-to-the-ov08x40-driver-units.patch @@ -0,0 +1,140 @@ +From ee34eedf620ff0b697d66eb70e06b91062ab7e15 Mon Sep 17 00:00:00 2001 +From: Spencer Bull +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& 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 + * diff --git a/pkgbuilds/intel-ipu7-camera/0011-camhal-fall-back-to-the-default-ISP-tuning-when-the-mode-is-missing.patch b/pkgbuilds/intel-ipu7-camera/0011-camhal-fall-back-to-the-default-ISP-tuning-when-the-mode-is-missing.patch new file mode 100644 index 0000000..8b497b5 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0011-camhal-fall-back-to-the-default-ISP-tuning-when-the-mode-is-missing.patch @@ -0,0 +1,91 @@ +From 5ba99a9f2cda77035dd99cacf8d98d3a0293768f Mon Sep 17 00:00:00 2001 +From: Spencer Bull +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 terminalConfig; + ret = unit.ipuStage->configure(unit.node->nodeKernels, + const_cast(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 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; diff --git a/pkgbuilds/intel-ipu7-camera/0012-camhal-ipu7x-ov08x40-Intel-CVS-formats.patch b/pkgbuilds/intel-ipu7-camera/0012-camhal-ipu7x-ov08x40-Intel-CVS-formats.patch new file mode 100644 index 0000000..cd6c3f5 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/0012-camhal-ipu7x-ov08x40-Intel-CVS-formats.patch @@ -0,0 +1,28 @@ +From: Kolbas +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" diff --git a/pkgbuilds/intel-ipu7-camera/70-ipu7-psys.rules b/pkgbuilds/intel-ipu7-camera/70-ipu7-psys.rules new file mode 100644 index 0000000..664e7a1 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/70-ipu7-psys.rules @@ -0,0 +1 @@ +KERNEL=="ipu7-psys0", GROUP="root", MODE="0600", SYMLINK+="ipu-psys0" diff --git a/pkgbuilds/intel-ipu7-camera/90-ipu7-psys.rules b/pkgbuilds/intel-ipu7-camera/90-ipu7-psys.rules deleted file mode 100644 index 4fde7b7..0000000 --- a/pkgbuilds/intel-ipu7-camera/90-ipu7-psys.rules +++ /dev/null @@ -1 +0,0 @@ -KERNEL=="ipu7-psys0", MODE="0666", SYMLINK+="ipu-psys0" diff --git a/pkgbuilds/intel-ipu7-camera/PKGBUILD b/pkgbuilds/intel-ipu7-camera/PKGBUILD index 6683be9..2eea443 100644 --- a/pkgbuilds/intel-ipu7-camera/PKGBUILD +++ b/pkgbuilds/intel-ipu7-camera/PKGBUILD @@ -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" } diff --git a/pkgbuilds/intel-ipu7-camera/camera-tmpfiles.conf b/pkgbuilds/intel-ipu7-camera/camera-tmpfiles.conf index 4b88626..069c6ac 100644 --- a/pkgbuilds/intel-ipu7-camera/camera-tmpfiles.conf +++ b/pkgbuilds/intel-ipu7-camera/camera-tmpfiles.conf @@ -1 +1 @@ -d /run/camera 0777 root root - +d /run/camera 0770 root video - diff --git a/pkgbuilds/intel-ipu7-camera/camera-videosrc-init b/pkgbuilds/intel-ipu7-camera/camera-videosrc-init new file mode 100755 index 0000000..ee04357 --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/camera-videosrc-init @@ -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}" diff --git a/pkgbuilds/intel-ipu7-camera/check-userptr-range.c b/pkgbuilds/intel-ipu7-camera/check-userptr-range.c new file mode 100644 index 0000000..0919b0e --- /dev/null +++ b/pkgbuilds/intel-ipu7-camera/check-userptr-range.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +#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; +} diff --git a/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf b/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf deleted file mode 100644 index ba882ac..0000000 --- a/pkgbuilds/intel-ipu7-camera/dkms-intel-cvs.conf +++ /dev/null @@ -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" diff --git a/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf b/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf index ab47545..bc4d4bf 100644 --- a/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf +++ b/pkgbuilds/intel-ipu7-camera/dkms-vision-drivers.conf @@ -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" diff --git a/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile b/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile deleted file mode 100644 index 58b251c..0000000 --- a/pkgbuilds/intel-ipu7-camera/intel-cvs-Makefile +++ /dev/null @@ -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 diff --git a/pkgbuilds/intel-ipu7-camera/intel-ipu7-camera.install b/pkgbuilds/intel-ipu7-camera/intel-ipu7-camera.install index 5f093a6..84a7f0b 100644 --- a/pkgbuilds/intel-ipu7-camera/intel-ipu7-camera.install +++ b/pkgbuilds/intel-ipu7-camera/intel-ipu7-camera.install @@ -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" diff --git a/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf b/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf index 5ec4d0d..c817cf0 100644 --- a/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7-override.conf +++ b/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7-override.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= diff --git a/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7.conf b/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7.conf index eb1b6eb..6d6bc25 100644 --- a/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7.conf +++ b/pkgbuilds/intel-ipu7-camera/v4l2-relayd-ipu7.conf @@ -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 diff --git a/tests/ipu7-headers.sh b/tests/ipu7-headers.sh new file mode 100644 index 0000000..33f9821 --- /dev/null +++ b/tests/ipu7-headers.sh @@ -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' diff --git a/tests/ipu7-install.sh b/tests/ipu7-install.sh new file mode 100644 index 0000000..37416a2 --- /dev/null +++ b/tests/ipu7-install.sh @@ -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'