From f03a4508b0a63789ef197d2bc5026bb179d43fc9 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:44:27 -0500 Subject: [PATCH 01/12] media: apple: isp: contain timed-out IPC sessions Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/.kunitconfig | 25 ++ drivers/media/platform/apple/isp/isp-cam.c | 4 +- drivers/media/platform/apple/isp/isp-cmd.c | 3 + drivers/media/platform/apple/isp/isp-drv.h | 2 + drivers/media/platform/apple/isp/isp-fw.c | 13 +- drivers/media/platform/apple/isp/isp-ipc.c | 16 +- drivers/media/platform/apple/isp/isp-v4l2.c | 2 + tools/asahi/isp-tests.py | 93 ++++++ tools/asahi/isp/session.c | 293 ++++++++++++++++++ 9 files changed, 444 insertions(+), 7 deletions(-) create mode 100644 drivers/media/platform/apple/isp/.kunitconfig create mode 100644 tools/asahi/isp-tests.py create mode 100644 tools/asahi/isp/session.c diff --git a/drivers/media/platform/apple/isp/.kunitconfig b/drivers/media/platform/apple/isp/.kunitconfig new file mode 100644 index 0000000000000..55e7f3a7025be --- /dev/null +++ b/drivers/media/platform/apple/isp/.kunitconfig @@ -0,0 +1,25 @@ +# Full driver suites on arm64 (requires an arm64 QEMU): +# ./tools/testing/kunit/kunit.py run --arch=arm64 \ +# --kunitconfig=drivers/media/platform/apple/isp +# The independent IPC layout suite can also run alone on UML. +CONFIG_KUNIT=y +CONFIG_COMPILE_TEST=y +CONFIG_OF=y +CONFIG_PM=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_MEDIA_SUPPORT_FILTER=y +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_DEV=y +CONFIG_MEDIA_PLATFORM_DRIVERS=y +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_VIDEO_APPLE_ISP=y +CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST=y +CONFIG_VIDEO_APPLE_ISP_IPC_LAYOUT_KUNIT_TEST=y +CONFIG_DRM=y +CONFIG_IOMMU_SUPPORT=y +CONFIG_FW_LOADER=y +CONFIG_DEBUG_KERNEL=y +CONFIG_PROVE_LOCKING=y +CONFIG_DEBUG_SPINLOCK=y diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 004a7fa13d019..2bc9a44df7288 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -1057,8 +1057,8 @@ void apple_isp_stop_capture(struct apple_isp *isp) { if (isp->ipc_failed || isp->fw_state == ISP_FW_DEAD) return; - isp_cmd_ch_stop(isp, 0); // TODO channel mask - isp_cmd_ch_buffer_return(isp, isp->current_ch); + if (!isp_cmd_ch_stop(isp, 0)) /* TODO channel mask */ + isp_cmd_ch_buffer_return(isp, isp->current_ch); /* ... until its buffers are back. */ apple_isp_wdt_stop(isp); diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c index 75b8704ccedcc..24f77995fc3b7 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -28,6 +28,9 @@ int isp_cmd_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int struct isp_message *req; int err; + if (READ_ONCE(isp->ipc_error)) + return READ_ONCE(isp->ipc_error); + #if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) KUNIT_STATIC_STUB_REDIRECT(isp_cmd_send, isp, args, insize, outsize, timeout); #endif diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 30425f6cf483d..7e5fb2e108370 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -347,6 +347,8 @@ struct apple_isp { } fw; /* changes under video_lock once the video device is registered */ enum isp_fw_state fw_state; + /* First IPC failure; cleared only after fresh channel initialization. */ + int ipc_error; struct isp_surf *ipc_surf; struct isp_surf *extra_surf; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 22dbf99a4c3ec..28f726e9bca0e 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -837,6 +837,9 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) static int isp_stop_command_processor(struct apple_isp *isp) { + if (READ_ONCE(isp->ipc_error)) + return READ_ONCE(isp->ipc_error); + #if 0 int res = isp_cmd_stop(isp, 0); if (res) { @@ -982,6 +985,7 @@ static int isp_firmware_boot(struct apple_isp *isp) /* Stage 1 stops the coprocessor and powers it down itself. */ isp_err(isp, "failed firmware boot stage 1: %d\n", err); isp_collect_gc_surface(isp); + cmpxchg(&isp->ipc_error, 0, err); return err; } @@ -998,6 +1002,9 @@ static int isp_firmware_boot(struct apple_isp *isp) goto stop; } + /* The old coprocessor stopped and all channel slots were initialized. */ + WRITE_ONCE(isp->ipc_error, 0); + err = isp_enable_irq(isp); if (err < 0) { isp_err(isp, "failed to enable interrupts: %d\n", err); @@ -1015,8 +1022,10 @@ static int isp_firmware_boot(struct apple_isp *isp) return 0; disable_irqs: + cmpxchg(&isp->ipc_error, 0, err); isp_disable_irq(isp); stop: + cmpxchg(&isp->ipc_error, 0, err); isp_firmware_stop(isp); return err; } @@ -1053,7 +1062,7 @@ int apple_isp_firmware_boot(struct apple_isp *isp) break; case ISP_FW_RUNNING: /* Resident firmware keeps running between streams. */ - return 0; + return READ_ONCE(isp->ipc_error); case ISP_FW_DEAD: dev_err_ratelimited(isp->dev, "firmware was stopped and cannot be restarted before the next boot\n"); @@ -1088,7 +1097,7 @@ int apple_isp_firmware_boot(struct apple_isp *isp) /* The end of a stream or of the camera detection at probe */ void apple_isp_firmware_shutdown(struct apple_isp *isp) { - if (!isp->hw->resident_fw) { + if (!isp->hw->resident_fw || READ_ONCE(isp->ipc_error)) { apple_isp_firmware_halt(isp); return; } diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 7b4dddf892a04..b84215d9e27da 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -241,7 +241,10 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, unsigned long timeout) { long t; - int err; + int err = READ_ONCE(isp->ipc_error); + + if (err) + return err; #if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) KUNIT_STATIC_STUB_REDIRECT(ipc_chan_send, isp, chan, timeout); @@ -262,7 +265,9 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, if (!timeout) return 0; - t = wait_event_timeout(isp->wait, chan_tx_done(isp, chan), timeout); + t = wait_event_timeout(isp->wait, + READ_ONCE(isp->ipc_error) || chan_tx_done(isp, chan), + timeout); if (t == 0) { /* Resolved before the next request, see isp_ipc_resync(). */ if (isp_is_p010(isp)) @@ -271,10 +276,15 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", chan->name, chan->req.arg0, chan->req.arg1, chan->req.arg2); - return -ETIME; + cmpxchg(&isp->ipc_error, 0, -ETIME); + wake_up_all(&isp->wait); + return READ_ONCE(isp->ipc_error); } chan->deadline_active = false; + if (READ_ONCE(isp->ipc_error)) + return READ_ONCE(isp->ipc_error); + isp_dbg(isp, "%s: request success (%ld)\n", chan->name, t); return 0; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 24506ed526a34..73b7e147a17ad 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -243,6 +243,8 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) err = isp_ipc_resync(isp); if (err) return err; + if (READ_ONCE(isp->ipc_error)) + return READ_ONCE(isp->ipc_error); /* Clear what earlier commands and batches left in the reserved fields. */ memset(bl, 0, ISP_CMD_AREA_SIZE(isp_num_capmeta(isp))); diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py new file mode 100644 index 0000000000000..d5af2de183812 --- /dev/null +++ b/tools/asahi/isp-tests.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Compile Apple ISP control flow with observing host transport boundaries.""" +import argparse +import hashlib +import json +from pathlib import Path +import re +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[2] +DRIVER = ROOT / 'drivers/media/platform/apple/isp' + + +def function(source, name): + match = re.search(r'^[\w *]+\b' + name + r'\(', source, re.M) + if match is None: + raise RuntimeError('missing function: ' + name) + start = source.index('{', match.end()) + depth, end = 1, start + 1 + while depth: + depth += (source[end] == '{') - (source[end] == '}') + end += 1 + return source[match.start():end] + '\n' + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--out', type=Path) + parser.add_argument('--emit', type=Path, help='write C for build-time compilation') + parser.add_argument('--cc', default='clang') + parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset']) + args = parser.parse_args() + inputs = {n: (DRIVER / n).read_text() for n in + ['isp-ipc.c', 'isp-cmd.c', 'isp-cam.c', 'isp-fw.c']} + groups = { + 'isp-ipc.c': ['ipc_chan_send'], + 'isp-cmd.c': ['cisp_send'], + 'isp-cam.c': ['apple_isp_stop_capture'], + 'isp-fw.c': ['isp_firmware_stop', 'isp_firmware_shutdown', + 'apple_isp_firmware_halt', 'isp_firmware_boot', + 'apple_isp_firmware_boot', 'apple_isp_firmware_shutdown'], + } + bodies = {n: function(inputs[f], n) for f, names in groups.items() for n in names} + if args.control == 'send': + bodies['ipc_chan_send'] = bodies['ipc_chan_send'].replace( + 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') + elif args.control == 'overwrite': + bodies['cisp_send'] = bodies['cisp_send'].replace( + 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') + elif args.control == 'stop': + bodies['apple_isp_stop_capture'] = bodies['apple_isp_stop_capture'].replace( + 'if (!isp_cmd_ch_stop(isp, 0)) /* TODO channel mask */', + 'isp_cmd_ch_stop(isp, 0);') + elif args.control == 'reset': + body = bodies['isp_firmware_boot'] + body = body.replace('WRITE_ONCE(isp->ipc_error, 0);', '') + bodies['isp_firmware_boot'] = body.replace( + 'int err;', 'int err;\n\tWRITE_ONCE(isp->ipc_error, 0);', 1) + code = (ROOT / 'tools/asahi/isp/session.c').read_text() + code = code.replace('/* DRIVER FUNCTIONS */', '\n'.join(bodies.values())) + + if args.emit: + args.emit.write_text(code) + return 0 + + def run(out): + out.mkdir(parents=True, exist_ok=True) + source, binary = out / 'session.c', out / 'session' + source.write_text(code) + compile_cmd = [args.cc, '-std=gnu11', '-Wall', '-Wextra', '-Werror', + '-Wno-unused-parameter', '-O1', '-g', '-pthread', + '-fsanitize=address,undefined', '-fno-sanitize-recover=all', + str(source), '-o', str(binary)] + subprocess.run(compile_cmd, check=True) + result = subprocess.run([str(binary)], capture_output=True, text=True) + receipt = {'control': args.control, 'compile': compile_cmd, + 'exit': result.returncode, 'stdout': result.stdout, + 'stderr': result.stderr, 'sources': { + f: hashlib.sha256(s.encode()).hexdigest() for f, s in inputs.items()}} + (out / 'results.json').write_text(json.dumps(receipt, indent=2) + '\n') + print(result.stdout or result.stderr, end='') + return result.returncode + + if args.out: + return run(args.out) + with tempfile.TemporaryDirectory(prefix='isp-tests-') as out: + return run(Path(out)) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c new file mode 100644 index 0000000000000..2ae0b6ffe0199 --- /dev/null +++ b/tools/asahi/isp/session.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Host fixture: the functions under test are extracted without rewriting. */ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef uint64_t u64; +typedef uint32_t u32; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x, v) ((x) = (v)) +#define cmpxchg(p, old, v) __sync_val_compare_and_swap(p, old, v) +#define dma_wmb() ((void)0) +#define dev_err(...) ((void)0) +#define dev_warn(...) ((void)0) +#define dev_err_ratelimited(...) ((void)0) +#define isp_err(...) ((void)0) +#define isp_dbg(...) ((void)0) +#define CISP_OPCODE_GET(x) ((x) >> 32) +#define ISP_MBOX2_IRQ_DOORBELL 0 +#define ISP_FW_OFF 0 +#define ISP_FW_RUNNING 1 +#define ISP_FW_DEAD 2 +static void check(bool ok, int line) +{ + if (!ok) { + fprintf(stderr, "FAIL line %d\n", line); + exit(1); + } +} + +#define CHECK(x) check((x), __LINE__) + +struct isp_message { + u64 arg0, arg1, arg2; +}; + +struct isp_channel { + struct isp_message req; + unsigned int doorbell; +}; + +struct hardware { + bool resident_fw; +}; + +struct apple_isp { + int ipc_error, wait, fw_state, irq; + struct isp_channel *chan_io; + const struct hardware *hw; + void *dev, *wq, *cmd_virt; + u64 cmd_iova; + unsigned int current_ch; +}; + +static unsigned int writes, bells, cursors, stops, returns, watchdog_stops; +static unsigned int stop_order, free_order, order, halts, freed_released; +static int stage_error, fail_stage, stop_error; +static bool ack; +static int entry_error, stage3_error; +static void chan_write_msg(struct apple_isp *isp, struct isp_channel *ch, + struct isp_message *msg) +{ + writes++; +} + +static void isp_mbox2_write32(struct apple_isp *isp, int reg, unsigned int v) +{ + bells++; +} + +static bool chan_tx_done(struct apple_isp *isp, struct isp_channel *ch) +{ + if (ack) + cursors++; + return ack; +} + +#define wait_event_timeout(w, condition, timeout) \ + ((void)(w), (void)(timeout), (condition) ? 1L : 0L) +static pthread_mutex_t wait_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; +static bool waiting, notified; + +static void wake_up_all(int *wait) +{ + pthread_mutex_lock(&wait_lock); + notified = true; + pthread_cond_broadcast(&wait_cond); + pthread_mutex_unlock(&wait_lock); +} + +static void *waiter(void *arg) +{ + struct timespec deadline; + + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec++; + pthread_mutex_lock(&wait_lock); + waiting = true; + pthread_cond_broadcast(&wait_cond); + while (!notified) { + int err = pthread_cond_timedwait(&wait_cond, &wait_lock, &deadline); + + CHECK(err != ETIMEDOUT); + CHECK(!err); + } + pthread_mutex_unlock(&wait_lock); + return NULL; +} + +static int isp_cmd_ch_stop(struct apple_isp *isp, int ch) +{ + stops++; + return stop_error; +} + +static int isp_cmd_ch_buffer_return(struct apple_isp *isp, int ch) +{ + returns++; + return 0; +} + +static void apple_isp_wdt_stop(struct apple_isp *isp) +{ + watchdog_stops++; +} + +static void isp_firmware_shutdown_stage1(struct apple_isp *isp) +{ + stop_order = ++order; +} + +static void isp_firmware_shutdown_stage3(struct apple_isp *isp) +{ + free_order = ++order; +} + +static void isp_firmware_shutdown_stage2(struct apple_isp *isp) +{ + ++order; +} + +static void isp_collect_gc_surface(struct apple_isp *isp) +{ + ++order; +} + +static int stage(struct apple_isp *isp, int n) +{ + if (n == 1) + entry_error = isp->ipc_error; + if (n == 3) + stage3_error = isp->ipc_error; + return fail_stage == n ? stage_error : 0; +} + +static int isp_firmware_boot_stage1(struct apple_isp *isp) +{ + return stage(isp, 1); +} + +static int isp_firmware_boot_stage2(struct apple_isp *isp) +{ + return stage(isp, 2); +} + +static int isp_firmware_boot_stage3(struct apple_isp *isp) +{ + return stage(isp, 3); +} + +static int isp_enable_irq(struct apple_isp *isp) +{ + return stage(isp, 4); +} + +static int isp_start_command_processor(struct apple_isp *isp) +{ + if (isp->ipc_error) + return isp->ipc_error; + return stage(isp, 5); +} + +static bool shutdown_called; +static int isp_stop_command_processor(struct apple_isp *isp) +{ + shutdown_called = true; + return 0; +} + +static void isp_disable_irq(struct apple_isp *isp) +{ +} + +static void flush_workqueue(void *wq) +{ +} + +static int pm_runtime_resume_and_get(void *dev) +{ + return 0; +} + +static void pm_runtime_put_sync(void *dev) +{ + halts++; +} + +static void apple_isp_firmware_halt(struct apple_isp *isp); + +static void synchronize_irq(int irq) +{ +} + +static void isp_free_released_surfaces(struct apple_isp *isp) +{ + freed_released++; +} + +/* DRIVER FUNCTIONS */ +int main(void) +{ + struct isp_channel ch = { 0 }; + const struct hardware hw = { 0 }, resident = { .resident_fw = true }; + u64 area[4] = { 1, 2, 3, 4 }, args[4] = { 5, 6, 7, 8 }; + struct apple_isp isp = { .hw = &hw, .chan_io = &ch, .cmd_virt = area }; + + pthread_t thread; + + CHECK(!pthread_create(&thread, NULL, waiter, NULL)); + pthread_mutex_lock(&wait_lock); + while (!waiting) + pthread_cond_wait(&wait_cond, &wait_lock); + pthread_mutex_unlock(&wait_lock); + CHECK(ipc_chan_send(&isp, &ch, 1) == -ETIME); + CHECK(!pthread_join(thread, NULL)); + CHECK(isp.ipc_error == -ETIME && writes == 1 && bells == 1 && + cursors == 0); + ack = true; + for (unsigned int i = 0; i < 3; i++) { + CHECK(ipc_chan_send(&isp, &ch, 1) == -ETIME); + CHECK(ipc_chan_send(&isp, &ch, 0) == -ETIME); + CHECK(cisp_send(&isp, args, sizeof(args), 0, 1) == -ETIME); + } + CHECK(writes == 1 && bells == 1 && cursors == 0); + CHECK(area[0] == 1 && area[1] == 2 && area[2] == 3 && area[3] == 4); + isp.ipc_error = -EIO; + CHECK(ipc_chan_send(&isp, &ch, 1) == -EIO); + CHECK(writes == 1 && cursors == 0); + for (unsigned int fail = 0; fail < 2; fail++) { + stop_error = fail ? -ETIME : 0; + stops = 0; + returns = 0; + watchdog_stops = 0; + apple_isp_stop_capture(&isp); + CHECK(stops == 1 && returns == !fail && watchdog_stops == 1); + } + for (fail_stage = 1; fail_stage <= 5; fail_stage++) { + isp.ipc_error = -ETIME; + stage_error = -ENOMEM; + order = 0; + stop_order = 0; + free_order = 0; + CHECK(isp_firmware_boot(&isp) == -ENOMEM); + CHECK(entry_error == -ETIME); + if (fail_stage >= 3) + CHECK(stage3_error == -ETIME); + if (fail_stage >= 2) + CHECK(stop_order && free_order > stop_order); + CHECK(isp.ipc_error == (fail_stage <= 3 ? -ETIME : -ENOMEM)); + } + fail_stage = 0; + isp.ipc_error = -ETIME; + CHECK(isp_firmware_boot(&isp) == 0 && isp.ipc_error == 0); + CHECK(ipc_chan_send(&isp, &ch, 1) == 0 && cursors == 1); + CHECK(ipc_chan_send(&isp, &ch, 0) == 0 && cursors == 1); + isp.hw = &resident; + isp.fw_state = ISP_FW_RUNNING; + isp.ipc_error = -ETIME; + CHECK(apple_isp_firmware_boot(&isp) == -ETIME && + isp.ipc_error == -ETIME); + apple_isp_firmware_shutdown(&isp); + CHECK(halts == 1 && freed_released == 0 && isp.fw_state == ISP_FW_DEAD); + CHECK(stop_order < free_order); + CHECK(apple_isp_firmware_boot(&isp) == -EIO); + puts("PASS: timeout, late ACK, shared writes, stop, boot and resident lifetime"); + return 0; +} From 569c98c82642a7aa3c93ecab5faa64e02bd5c5b8 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:44:27 -0500 Subject: [PATCH 02/12] media: apple: isp: retain host buffer ownership on send Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-v4l2.c | 76 +---- tools/asahi/isp-tests.py | 25 +- tools/asahi/isp/buffers.c | 357 ++++++++++++++++++++ 3 files changed, 397 insertions(+), 61 deletions(-) create mode 100644 tools/asahi/isp/buffers.c diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 73b7e147a17ad..7ff6a7857a498 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -230,7 +230,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) struct isp_format *fmt = isp_get_current_format(isp); struct isp_channel *chan = isp->chan_bh; struct isp_message *req = &chan->req; - struct isp_buffer *buf, *tmp; + struct isp_buffer *buf; unsigned int num_rendered = 0, num_buffers = 0; unsigned long flags; size_t offset; @@ -325,10 +325,6 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) send: spin_unlock_irqrestore(&isp->buf_lock, flags); - /* - * The firmware can write to the command area, so the error path below - * goes by the count kept here rather than the one in the batch. - */ bl->num_buffers = num_buffers; req->arg0 = isp->cmd_iova; @@ -337,49 +333,15 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND; err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); - if (err) { - /* - * The firmware may have taken the batch. Keep its leases owned - * until the timeout is resolved, see apple_isp_stop_camera(). - */ - if (isp_is_p010(isp)) - return err; - /* If we fail, consider the buffer not submitted. */ - dev_err(isp->dev, - "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", - chan->name, req->arg0, req->arg1, req->arg2); - - /* - * Try to find the buffer in the list, and if it's - * still there, move it back to the pending list. - */ - spin_lock_irqsave(&isp->buf_lock, flags); - - bufd = &bl->buffers[0]; - for (unsigned int i = 0; i < num_buffers; i++, bufd++) { - list_for_each_entry_safe_reverse( - buf, tmp, &isp->bufs_submitted, link) { - if (bufd->iovas[0] == buf->surfs[0].iova) { - list_move_tail(&buf->link, - &isp->bufs_pending); - } - } - for (int j = 0; j < isp_num_meta(isp); j++) { - struct isp_surf *meta = isp->meta_surfs[j]; - if (bufd->iovas[0] == meta->iova) { - meta->submitted = false; - } - } - for (int j = 0; j < isp_num_capmeta(isp); j++) { - struct isp_surf *meta = isp->capmeta_surfs[j]; - - if (bufd->iovas[0] == meta->iova) - meta->submitted = false; - } - } - - spin_unlock_irqrestore(&isp->buf_lock, flags); - } + /* + * The host lists and submitted flags are the ownership record. Once + * published, the batch may be consumed even if its ACK never arrives. + * Keep all outstanding buffers until return or coprocessor shutdown; + * never recover their identity from the firmware-writable batch. + */ + if (err) + dev_err(isp->dev, "%s: failed to send buffers: %d\n", + chan->name, err); return err; } @@ -533,18 +495,16 @@ static int apple_isp_start_streaming(struct apple_isp *isp) } /* - * Resident firmware was not restarted, but the channel's pools are - * configured again for every stream. Metadata buffers still marked + * The channel's pools are configured again for every stream after + * either a fresh boot or a successful resident stop. Metadata buffers still marked * as submitted to the previous pools would never reach the new ones. */ - if (isp->hw->resident_fw) { - spin_lock_irqsave(&isp->buf_lock, flags); - for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) - isp->meta_surfs[i]->submitted = false; - for (int i = 0; i < isp_num_capmeta(isp); i++) - isp->capmeta_surfs[i]->submitted = false; - spin_unlock_irqrestore(&isp->buf_lock, flags); - } + spin_lock_irqsave(&isp->buf_lock, flags); + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) + isp->meta_surfs[i]->submitted = false; + for (int i = 0; i < isp_num_capmeta(isp); i++) + isp->capmeta_surfs[i]->submitted = false; + spin_unlock_irqrestore(&isp->buf_lock, flags); err = isp_submit_buffers(isp, true); if (err) { diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index d5af2de183812..c27aeb4d1c270 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -30,7 +30,8 @@ def main(): parser.add_argument('--out', type=Path) parser.add_argument('--emit', type=Path, help='write C for build-time compilation') parser.add_argument('--cc', default='clang') - parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset']) + parser.add_argument('--suite', choices=['session', 'buffers'], default='session') + parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback']) args = parser.parse_args() inputs = {n: (DRIVER / n).read_text() for n in ['isp-ipc.c', 'isp-cmd.c', 'isp-cam.c', 'isp-fw.c']} @@ -58,7 +59,24 @@ def main(): body = body.replace('WRITE_ONCE(isp->ipc_error, 0);', '') bodies['isp_firmware_boot'] = body.replace( 'int err;', 'int err;\n\tWRITE_ONCE(isp->ipc_error, 0);', 1) - code = (ROOT / 'tools/asahi/isp/session.c').read_text() + if args.suite == 'buffers': + inputs = {'isp-v4l2.c': (DRIVER / 'isp-v4l2.c').read_text()} + bodies = {n: function(inputs['isp-v4l2.c'], n) for n in + ['isp_submit_buffers', 'apple_isp_start_streaming']} + bodies['apple_isp_stop_capture'] = function( + (DRIVER / 'isp-cam.c').read_text(), 'apple_isp_stop_capture') + if args.control == 'rollback': + old = subprocess.check_output( + ['git', 'show', '7876c8e8349c4f3d0a2f4ce982e3e13ce5bb5d70:' + 'drivers/media/platform/apple/isp/isp-v4l2.c'], cwd=ROOT, text=True) + bodies['isp_submit_buffers'] = function(old, 'isp_submit_buffers') + code = (ROOT / ('tools/asahi/isp/' + args.suite + '.c')).read_text() + if args.suite == 'buffers': + structures = re.findall(r'^struct isp_buflist(?:_buffer)? \{.*?^} __packed;', + inputs['isp-v4l2.c'], re.M | re.S) + if len(structures) != 2: + raise RuntimeError('missing buffer list structures') + code = code.replace('/* DRIVER STRUCTURES */', '\n'.join(structures)) code = code.replace('/* DRIVER FUNCTIONS */', '\n'.join(bodies.values())) if args.emit: @@ -70,7 +88,8 @@ def run(out): source, binary = out / 'session.c', out / 'session' source.write_text(code) compile_cmd = [args.cc, '-std=gnu11', '-Wall', '-Wextra', '-Werror', - '-Wno-unused-parameter', '-O1', '-g', '-pthread', + '-Wno-unused-parameter', '-Wno-unused-but-set-variable', + '-Wno-sign-compare', '-O1', '-g', '-pthread', '-fsanitize=address,undefined', '-fno-sanitize-recover=all', str(source), '-o', str(binary)] subprocess.run(compile_cmd, check=True) diff --git a/tools/asahi/isp/buffers.c b/tools/asahi/isp/buffers.c new file mode 100644 index 0000000000000..50e5098cfdede --- /dev/null +++ b/tools/asahi/isp/buffers.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Host-only boundaries for the real batch producer and return handler. */ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef uint64_t u64; +typedef uint32_t u32; +typedef u64 dma_addr_t; +#define __packed +#define ISP_MAX_PLANES 4 +#define ISP_MAX_BUFFERS 16 +#define ISP_CAPMETA_BUFFERS 8 +#define ISP_CMD_AREA_SIZE(n) (16 + 64 * (32 + (n))) +#define ISP_IPC_BUFEXC_STAT_SIZE 0x280 +#define ISP_IPC_BUFEXC_FLAG_COMMAND 0x30000000 +#define ISP_IPC_BUFEXC_FLAG_RENDER 0x10000000 +#define ISP_IPC_BUFEXC_FLAG_ACK 0x80000000 +#define ISP_IPC_FLAG_ACK 1 +#define ISP_BUFFER_TIMEOUT 1500 +#define CISP_POOL_TYPE_META 0 +#define CISP_POOL_TYPE_RENDERED 1 +#define CISP_POOL_TYPE_RENDERED_SCL1 9 +#define ISP_BUFLIST_POOL_CAPTURE_META 2 +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define max_t(t, a, b) ((t)max_u64(a, b)) +#define READ_ONCE(x) (x) +#define isp_num_capmeta(i) \ + ((i)->hw->capture_meta_size ? ISP_CAPMETA_BUFFERS : 0) +#define isp_get_current_format(i) (&(i)->fmt) +#define isp_fw_iova(i, x) ((x) & (i)->fw_iova_mask) +static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned int acquisitions; +#define spin_lock_irqsave(l, f) \ + ((void)(l), (f) = 0, acquisitions++, pthread_mutex_lock(&buffer_mutex)) +#define spin_unlock_irqrestore(l, f) \ + ((void)(l), (void)(f), pthread_mutex_unlock(&buffer_mutex)) +#define spin_lock(l) ((void)(l), acquisitions++, pthread_mutex_lock(&buffer_mutex)) +#define spin_unlock(l) ((void)(l), pthread_mutex_unlock(&buffer_mutex)) +#define ISP_STATE_STREAMING 0 +#define ISP_STATE_SLEEPING 2 + +static void set_bit(unsigned int bit, unsigned long *state) +{ + *state |= 1UL << bit; +} + +#define dev_err(...) ((void)0) +#define dev_err_ratelimited(...) ((void)0) +#define dev_warn_ratelimited(...) ((void)0) +#define GFP_KERNEL 0 +#define array_size(a, b) ((a) * (b)) +#define kfree(p) free(p) + +static void check(bool ok, int line) +{ + if (!ok) { + fprintf(stderr, "FAIL line %d\n", line); + exit(1); + } +} + +#define CHECK(x) check((x), __LINE__) + +struct list_head { + struct list_head *next, *prev; +}; + +static void INIT_LIST_HEAD(struct list_head *h) +{ + h->next = h; + h->prev = h; +} + +static void list_add_tail(struct list_head *e, struct list_head *h) +{ + e->prev = h->prev; + e->next = h; + h->prev->next = e; + h->prev = e; +} + +static void list_del(struct list_head *e) +{ + e->prev->next = e->next; + e->next->prev = e->prev; +} + +static void list_move_tail(struct list_head *e, struct list_head *h) +{ + list_del(e); + list_add_tail(e, h); +} + +static bool list_empty(struct list_head *h) +{ + return h->next == h; +} + +#define container_of(p, type, m) \ + ({ \ + type *__ptr; \ + __ptr = (void *)((char *)(p) - \ + offsetof(__typeof__(*__ptr), m)); \ + __ptr; \ + }) +#define list_first_entry_or_null(h, t, m) \ + ({ \ + struct list_head *__head = (h); \ + list_empty(__head) ? NULL : container_of(__head->next, t, m); \ + }) +#define list_for_each_entry(p, h, m) \ + for (struct list_head *node = (h)->next, *head = node->prev; \ + node != head && \ + ((p) = container_of(node, struct isp_buffer, m), true); \ + node = node->next) +#define list_for_each_entry_safe_reverse(p, t, h, m) \ + for (struct list_head *node = (h)->prev, *head = node->next, *prev; \ + node != head && (prev = node->prev, \ + (p) = container_of(node, struct isp_buffer, m), \ + (t) = NULL, true); \ + node = prev) + +static u64 max_u64(u64 a, u64 b) +{ + return a > b ? a : b; +} + +struct isp_surf { + u64 iova, size; + bool submitted; + void *virt; +}; + +struct isp_buffer { + struct { + struct { + u64 timestamp; + unsigned int done; + } vb2_buf; + unsigned int sequence, field; + } vb; + struct list_head link; + struct isp_surf surfs[4]; +}; + +struct isp_message { + u64 arg0, arg1, arg2; +}; + +struct isp_channel { + struct isp_message req, rsp; +}; + +struct isp_format { + unsigned int num_planes; + size_t plane_size[4]; +}; + +struct hardware { + bool scl1, resident_fw; + unsigned int capture_meta_size; +}; + +struct apple_isp { + struct isp_format fmt; + struct isp_channel *chan_bh; + const struct hardware *hw; + struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; + struct isp_surf *capmeta_surfs[ISP_CAPMETA_BUFFERS]; + struct isp_surf *bt_surf; + struct list_head bufs_pending, bufs_submitted; + void *cmd_virt; + u64 cmd_iova, fw_iova_mask; + int ipc_error, buf_lock; + unsigned int current_ch; + unsigned long state; + unsigned int sequence; +}; + +/* DRIVER STRUCTURES */ +static unsigned int sends, fail_at, sent_meta, sent_capmeta; +static bool quiescent; +static void (*transition_error)(struct apple_isp *isp); +static int send_error; +static bool mutate; + +static int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + struct isp_buflist *bl = isp->cmd_virt; + + sends++; + for (unsigned int i = 0; i < bl->num_buffers; i++) { + sent_meta += bl->buffers[i].pool_type == CISP_POOL_TYPE_META; + sent_capmeta += bl->buffers[i].pool_type == ISP_BUFLIST_POOL_CAPTURE_META; + } + if (mutate) { + bl->num_buffers = UINT64_MAX; + memset(bl->buffers, 0xff, 64 * 40); + } + int err = fail_at ? (sends == fail_at ? -ETIME : 0) : send_error; + + if (err) + isp->ipc_error = err; + return err; +} + +static int apple_isp_start_camera(struct apple_isp *isp) +{ + CHECK(quiescent || !isp->ipc_error); + isp->ipc_error = 0; + quiescent = false; + return 0; +} + +static void apple_isp_stop_camera(struct apple_isp *isp) +{ + quiescent = true; +} + +static int apple_isp_start_capture(struct apple_isp *isp) +{ + if (transition_error) + transition_error(isp); + return 0; +} + +static int isp_cmd_ch_stop(struct apple_isp *isp, int channel) +{ + return isp->ipc_error; +} + +static int isp_cmd_ch_buffer_return(struct apple_isp *isp, int channel) +{ + return 0; +} + +static void apple_isp_wdt_stop(struct apple_isp *isp) +{ +} + +static void apple_isp_stop_capture(struct apple_isp *isp); + +/* DRIVER FUNCTIONS */ +int main(void) +{ + struct hardware hw = { 0 }; + struct isp_channel chan = { 0 }; + struct isp_surf metadata[ISP_MAX_BUFFERS], capmeta[ISP_CAPMETA_BUFFERS]; + struct isp_buffer rendered[ISP_MAX_BUFFERS + 1] = { 0 }; + unsigned char area[ISP_CMD_AREA_SIZE(ISP_CAPMETA_BUFFERS)]; + struct apple_isp isp = { + .hw = &hw, + .chan_bh = &chan, + .cmd_virt = area, + .fmt = { .num_planes = 2, .plane_size = { 4096, 2048 } }, + }; + + for (unsigned int caps = 0; caps < 2; caps++) { + for (unsigned int fail = 0; fail < 2; fail++) { + for (unsigned int change = 0; change < 2; change++) { + INIT_LIST_HEAD(&isp.bufs_pending); + INIT_LIST_HEAD(&isp.bufs_submitted); + isp.ipc_error = 0; + hw.capture_meta_size = caps; + for (unsigned int i = 0; + i < ARRAY_SIZE(metadata); i++) { + metadata[i] = (struct isp_surf){ + .iova = 0x100000000ULL + + i * 4096 + }; + isp.meta_surfs[i] = &metadata[i]; + } + for (unsigned int i = 0; + i < ARRAY_SIZE(capmeta); i++) { + capmeta[i] = (struct isp_surf){ + .iova = 0x200000000ULL + + i * 4096 + }; + isp.capmeta_surfs[i] = &capmeta[i]; + } + for (unsigned int i = 0; + i < ARRAY_SIZE(rendered); i++) { + rendered[i].surfs[0].iova = + 0x300000000ULL + i * 8192; + list_add_tail(&rendered[i].link, + &isp.bufs_pending); + } + send_error = fail ? -ETIME : 0; + mutate = change; + unsigned int locks_before = acquisitions; + + CHECK(isp_submit_buffers(&isp, false) == + send_error); + CHECK(acquisitions == locks_before + 1); + CHECK(isp.bufs_pending.next == + &rendered[ISP_MAX_BUFFERS].link); + CHECK(isp.bufs_pending.prev == + &rendered[ISP_MAX_BUFFERS].link); + CHECK(isp.bufs_submitted.next == + &rendered[0].link); + CHECK(isp.bufs_submitted.prev == + &rendered[ISP_MAX_BUFFERS - 1].link); + for (unsigned int i = 0; + i < ARRAY_SIZE(metadata); i++) + CHECK(metadata[i].submitted); + for (unsigned int i = 0; + i < ARRAY_SIZE(capmeta); i++) + CHECK(capmeta[i].submitted == !!caps); + if (fail) { + unsigned int before = sends; + unsigned char saved[sizeof(area)]; + + memcpy(saved, area, sizeof(area)); + CHECK(isp_submit_buffers(&isp, false) == + -ETIME); + CHECK(sends == before && + !memcmp(saved, area, + sizeof(area))); + } + } + } + } + /* Reuse the same metadata objects after each failed, retired session. */ + mutate = false; + for (unsigned int caps = 0; caps < 2; caps++) { + hw.capture_meta_size = caps; + for (unsigned int phase = 1; phase <= (caps ? 2 : 1); phase++) { + INIT_LIST_HEAD(&isp.bufs_pending); + INIT_LIST_HEAD(&isp.bufs_submitted); + isp.ipc_error = 0; + isp.state = 0; + fail_at = phase; + sends = 0; + CHECK(apple_isp_start_streaming(&isp) == -ETIME); + CHECK(quiescent && metadata[0].submitted); + if (phase == 2) + CHECK(capmeta[0].submitted); + fail_at = 0; + send_error = 0; + sent_meta = 0; + sent_capmeta = 0; + CHECK(!apple_isp_start_streaming(&isp)); + CHECK(sent_meta == ISP_MAX_BUFFERS); + CHECK(sent_capmeta == (caps ? ISP_CAPMETA_BUFFERS : 0)); + apple_isp_stop_camera(&isp); + } + } + puts("PASS: immutable ownership with mutable batches, timeout and full pools"); + return 0; +} From e3bda26c886854df7399bfdc0c664806dfd34d3b Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:44:27 -0500 Subject: [PATCH 03/12] media: apple: isp: bound receive turns across IPC channels Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-drv.h | 2 + drivers/media/platform/apple/isp/isp-fw.c | 35 +++++-- drivers/media/platform/apple/isp/isp-ipc.c | 14 ++- tools/asahi/isp-tests.py | 14 ++- tools/asahi/isp/receive.c | 112 +++++++++++++++++++++ tools/asahi/isp/session.c | 83 ++++++++++++++- 6 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 tools/asahi/isp/receive.c diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 7e5fb2e108370..bc2ee0c9556da 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -349,6 +349,8 @@ struct apple_isp { enum isp_fw_state fw_state; /* First IPC failure; cleared only after fresh channel initialization. */ int ipc_error; + /* End the current receive invocation before resident reclamation. */ + bool ipc_quiescing; struct isp_surf *ipc_surf; struct isp_surf *extra_surf; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 28f726e9bca0e..b197db5dd5b25 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -228,16 +228,22 @@ static irqreturn_t apple_isp_isr(int irq, void *dev) static irqreturn_t apple_isp_isr_thread(int irq, void *dev) { struct apple_isp *isp = dev; + bool more; - wake_up_all(&isp->wait); + do { + wake_up_all(&isp->wait); - ipc_chan_handle(isp, isp->chan_sm); - wake_up_all(&isp->wait); /* Some commands depend on sm */ + more = ipc_chan_handle(isp, isp->chan_sm) == -EAGAIN; + wake_up_all(&isp->wait); /* Some commands depend on sm */ - ipc_chan_handle(isp, isp->chan_tm); + more |= ipc_chan_handle(isp, isp->chan_tm) == -EAGAIN; + more |= ipc_chan_handle(isp, isp->chan_bt) == -EAGAIN; + wake_up_all(&isp->wait); - ipc_chan_handle(isp, isp->chan_bt); - wake_up_all(&isp->wait); + /* Fair turns across channels, without depending on another IRQ. */ + cond_resched(); + } while (more && !READ_ONCE(isp->ipc_error) && + !READ_ONCE(isp->ipc_quiescing)); return IRQ_HANDLED; } @@ -1040,6 +1046,8 @@ static void isp_firmware_shutdown(struct apple_isp *isp) */ if (isp_stop_command_processor(isp)) dev_warn(isp->dev, "firmware did not suspend, stopping it\n"); + /* Also stop a receive thread whose producer never leaves a gap. */ + cmpxchg(&isp->ipc_error, 0, -ESHUTDOWN); isp_disable_irq(isp); isp_firmware_stop(isp); } @@ -1108,9 +1116,20 @@ void apple_isp_firmware_shutdown(struct apple_isp *isp) * The stream has stopped, so free them now: wait for the interrupt * thread to finish the free requests the firmware has already sent. */ - if (isp->fw_state == ISP_FW_RUNNING) - synchronize_irq(isp->irq); + if (isp->fw_state != ISP_FW_RUNNING) + return; + + /* Finish a fair receive round even if the producer keeps refilling. */ + WRITE_ONCE(isp->ipc_quiescing, true); + disable_irq(isp->irq); + /* No receive handler can race this lifetime decision while disabled. */ + if (READ_ONCE(isp->ipc_error)) { + apple_isp_firmware_halt(isp); + return; + } isp_free_released_surfaces(isp); + WRITE_ONCE(isp->ipc_quiescing, false); + enable_irq(isp->irq); } /* Stop the firmware; resident firmware cannot be started again. */ diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index b84215d9e27da..634cb74fe4bf3 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -157,12 +157,15 @@ static inline bool chan_rx_done(struct apple_isp *isp, struct isp_channel *chan) int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) { - unsigned int budget = isp_is_p010(isp) ? chan->num : UINT_MAX; - int err = 0; + int err = -EAGAIN; mutex_lock(&chan->lock); - /* A busy producer must not starve the other completion/control rings. */ - while (budget--) { + /* One ring per turn, even if the producer refills acknowledged slots. */ + for (u32 i = 0; i < chan->num; i++) { + if (READ_ONCE(isp->ipc_error)) { + err = READ_ONCE(isp->ipc_error); + break; + } chan_read_msg(isp, chan, &chan->req); if (chan_rx_done(isp, chan)) { err = 0; @@ -170,8 +173,11 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan) } err = chan_handle_once(isp, chan); if (err < 0) { + cmpxchg(&isp->ipc_error, 0, err); + wake_up_all(&isp->wait); break; } + err = -EAGAIN; } mutex_unlock(&chan->lock); diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index c27aeb4d1c270..f9d806b5d5436 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -30,8 +30,8 @@ def main(): parser.add_argument('--out', type=Path) parser.add_argument('--emit', type=Path, help='write C for build-time compilation') parser.add_argument('--cc', default='clang') - parser.add_argument('--suite', choices=['session', 'buffers'], default='session') - parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback']) + parser.add_argument('--suite', choices=['session', 'buffers', 'receive'], default='session') + parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback', 'drain']) args = parser.parse_args() inputs = {n: (DRIVER / n).read_text() for n in ['isp-ipc.c', 'isp-cmd.c', 'isp-cam.c', 'isp-fw.c']} @@ -43,6 +43,9 @@ def main(): 'apple_isp_firmware_halt', 'isp_firmware_boot', 'apple_isp_firmware_boot', 'apple_isp_firmware_shutdown'], } + if args.suite == 'session': + groups['isp-ipc.c'] += ['chan_rx_done', 'ipc_chan_handle'] + groups['isp-fw.c'] += ['apple_isp_isr_thread'] bodies = {n: function(inputs[f], n) for f, names in groups.items() for n in names} if args.control == 'send': bodies['ipc_chan_send'] = bodies['ipc_chan_send'].replace( @@ -70,6 +73,13 @@ def main(): ['git', 'show', '7876c8e8349c4f3d0a2f4ce982e3e13ce5bb5d70:' 'drivers/media/platform/apple/isp/isp-v4l2.c'], cwd=ROOT, text=True) bodies['isp_submit_buffers'] = function(old, 'isp_submit_buffers') + if args.suite == 'receive': + bodies = {n: function(inputs[f], n) for f, n in [ + ('isp-ipc.c', 'chan_rx_done'), ('isp-ipc.c', 'ipc_chan_handle'), + ('isp-fw.c', 'apple_isp_isr_thread')]} + if args.control == 'drain': + bodies['ipc_chan_handle'] = bodies['ipc_chan_handle'].replace( + 'for (u32 i = 0; i < chan->num; i++)', 'while (1)') code = (ROOT / ('tools/asahi/isp/' + args.suite + '.c')).read_text() if args.suite == 'buffers': structures = re.findall(r'^struct isp_buflist(?:_buffer)? \{.*?^} __packed;', diff --git a/tools/asahi/isp/receive.c b/tools/asahi/isp/receive.c new file mode 100644 index 0000000000000..f924858e09ba7 --- /dev/null +++ b/tools/asahi/isp/receive.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Fake producer, with the actual drain and IRQ-thread scheduling functions. */ +#include +#include +#include +#include +#include + +typedef uint32_t u32; +#define irqreturn_t int +#define IRQ_HANDLED 1 +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x, v) ((x) = (v)) +#define cmpxchg(p, old, v) __sync_val_compare_and_swap(p, old, v) +#define mutex_lock(l) ((void)(l)) +#define mutex_unlock(l) ((void)(l)) +#define wake_up_all(w) ((void)(w)) +#define ISP_IPC_FLAG_ACK 1 +#define ISP_IPC_FLAG_TERMINAL_ACK 3 + +struct isp_message { + unsigned long arg0; +}; + +struct isp_channel { + unsigned int num, cursor, remaining, handled; + int lock, error; + struct isp_message req; +}; + +struct apple_isp { + struct isp_channel *chan_sm, *chan_tm, *chan_bt; + int ipc_error, wait; + bool ipc_quiescing; +}; + +static unsigned int schedules; +static bool refill; +static void check(bool ok, int line) +{ + if (!ok) { + fprintf(stderr, "FAIL line %d\n", line); + exit(1); + } +} + +#define CHECK(x) check((x), __LINE__) + +static void cond_resched(void) +{ + CHECK(++schedules < 10000); +} + +static void chan_read_msg(struct apple_isp *isp, struct isp_channel *chan, + struct isp_message *req) +{ + req->arg0 = chan->remaining ? 0 : ISP_IPC_FLAG_ACK; +} + +static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) +{ + if (chan->error) + return chan->error; + chan->handled++; + if (!refill) + chan->remaining--; + chan->cursor = (chan->cursor + 1) % chan->num; + return 0; +} + +/* DRIVER FUNCTIONS */ +int main(void) +{ + struct isp_channel channels[3] = { + { .num = 2, .remaining = 11 }, + { .num = 3, .remaining = 8 }, + { .num = 1, .remaining = 7 }, + }; + struct apple_isp isp = { + .chan_sm = &channels[0], + .chan_tm = &channels[1], + .chan_bt = &channels[2], + }; + + CHECK(ipc_chan_handle(&isp, &channels[0]) == -EAGAIN); + CHECK(channels[0].handled == 2 && channels[0].remaining == 9); + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED); + CHECK(channels[0].handled == 11 && channels[1].handled == 8 && + channels[2].handled == 7); + CHECK(!channels[0].remaining && !channels[1].remaining && + !channels[2].remaining); + CHECK(schedules >= 7 && schedules <= 8); + /* A continuous producer must let a quiescing invocation finish. */ + refill = true; + channels[0].remaining = 2; + isp.ipc_quiescing = true; + unsigned int before = schedules; + + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED); + CHECK(schedules == before + 1 && !isp.ipc_error); + refill = false; + isp.ipc_quiescing = false; + channels[0].remaining = 2; + channels[0].error = -EIO; + CHECK(ipc_chan_handle(&isp, &channels[0]) == -EIO && + isp.ipc_error == -EIO); + channels[0].error = 0; + CHECK(ipc_chan_handle(&isp, &channels[0]) == -EIO && + channels[0].remaining == 2); + puts("PASS: bounded turns, burst continuation, channel fairness and error containment"); + return 0; +} diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c index 2ae0b6ffe0199..246748a976e28 100644 --- a/tools/asahi/isp/session.c +++ b/tools/asahi/isp/session.c @@ -22,6 +22,12 @@ typedef uint32_t u32; #define isp_dbg(...) ((void)0) #define CISP_OPCODE_GET(x) ((x) >> 32) #define ISP_MBOX2_IRQ_DOORBELL 0 +#define irqreturn_t int +#define IRQ_HANDLED 1 +#define ISP_IPC_FLAG_ACK 1 +#define ISP_IPC_FLAG_TERMINAL_ACK 3 +#define mutex_lock(l) ((void)(l)) +#define mutex_unlock(l) ((void)(l)) #define ISP_FW_OFF 0 #define ISP_FW_RUNNING 1 #define ISP_FW_DEAD 2 @@ -41,7 +47,8 @@ struct isp_message { struct isp_channel { struct isp_message req; - unsigned int doorbell; + unsigned int doorbell, num, cursor, remaining; + int lock; }; struct hardware { @@ -50,7 +57,8 @@ struct hardware { struct apple_isp { int ipc_error, wait, fw_state, irq; - struct isp_channel *chan_io; + bool ipc_quiescing; + struct isp_channel *chan_io, *chan_sm, *chan_tm, *chan_bt; const struct hardware *hw; void *dev, *wq, *cmd_virt; u64 cmd_iova; @@ -186,7 +194,32 @@ static int isp_start_command_processor(struct apple_isp *isp) return stage(isp, 5); } -static bool shutdown_called; +static irqreturn_t apple_isp_isr_thread(int irq, void *dev); +static unsigned int receive_rounds; +static bool refill; + +static void cond_resched(void) +{ + CHECK(++receive_rounds < 10000); +} + +static void chan_read_msg(struct apple_isp *isp, struct isp_channel *chan, + struct isp_message *req) +{ + req->arg0 = chan->remaining ? 0 : ISP_IPC_FLAG_ACK; +} + +static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan) +{ + if (!refill) + chan->remaining--; + return 0; +} + +static bool shutdown_called, flood; +static struct apple_isp *synchronizing; +static int sync_error; +static bool irq_disabled; static int isp_stop_command_processor(struct apple_isp *isp) { shutdown_called = true; @@ -195,6 +228,8 @@ static int isp_stop_command_processor(struct apple_isp *isp) static void isp_disable_irq(struct apple_isp *isp) { + if (flood) + apple_isp_isr_thread(isp->irq, isp); } static void flush_workqueue(void *wq) @@ -213,12 +248,24 @@ static void pm_runtime_put_sync(void *dev) static void apple_isp_firmware_halt(struct apple_isp *isp); -static void synchronize_irq(int irq) +static void disable_irq(int irq) +{ + CHECK(synchronizing->ipc_quiescing); + irq_disabled = true; + apple_isp_isr_thread(irq, synchronizing); + if (sync_error) + synchronizing->ipc_error = sync_error; +} + +static void enable_irq(int irq) { + CHECK(irq_disabled && !synchronizing->ipc_quiescing); + irq_disabled = false; } static void isp_free_released_surfaces(struct apple_isp *isp) { + CHECK(irq_disabled); freed_released++; } @@ -226,9 +273,12 @@ static void isp_free_released_surfaces(struct apple_isp *isp) int main(void) { struct isp_channel ch = { 0 }; + struct isp_channel incoming = { .num = 2, .remaining = 2 }; const struct hardware hw = { 0 }, resident = { .resident_fw = true }; u64 area[4] = { 1, 2, 3, 4 }, args[4] = { 5, 6, 7, 8 }; - struct apple_isp isp = { .hw = &hw, .chan_io = &ch, .cmd_virt = area }; + struct apple_isp isp = { .hw = &hw, .chan_io = &ch, .cmd_virt = area, + .chan_sm = &incoming, .chan_tm = &incoming, + .chan_bt = &incoming }; pthread_t thread; @@ -284,10 +334,33 @@ int main(void) isp.ipc_error = -ETIME; CHECK(apple_isp_firmware_boot(&isp) == -ETIME && isp.ipc_error == -ETIME); + synchronizing = &isp; apple_isp_firmware_shutdown(&isp); CHECK(halts == 1 && freed_released == 0 && isp.fw_state == ISP_FW_DEAD); CHECK(stop_order < free_order); CHECK(apple_isp_firmware_boot(&isp) == -EIO); + /* Healthy resident close is temporary; a late receive failure halts. */ + isp.fw_state = ISP_FW_RUNNING; + isp.ipc_error = 0; + isp.chan_sm = &incoming; + isp.chan_tm = &incoming; + isp.chan_bt = &incoming; + refill = true; + receive_rounds = 0; + synchronizing = &isp; + apple_isp_firmware_shutdown(&isp); + CHECK(!irq_disabled && !isp.ipc_quiescing && freed_released == 1); + CHECK(receive_rounds == 1); + CHECK(isp.fw_state == ISP_FW_RUNNING && !isp.ipc_error); + sync_error = -EIO; + apple_isp_firmware_shutdown(&isp); + CHECK(isp.fw_state == ISP_FW_DEAD && freed_released == 1); + CHECK(halts == 2 && isp.ipc_error == -EIO); + isp.ipc_error = 0; + isp.ipc_quiescing = false; + flood = true; + isp_firmware_shutdown(&isp); + CHECK(isp.ipc_error == -ESHUTDOWN); puts("PASS: timeout, late ACK, shared writes, stop, boot and resident lifetime"); return 0; } From c700eca8229f4f0621721f013ee60b24847c4a34 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:44:27 -0500 Subject: [PATCH 04/12] media: apple: isp: validate outstanding buffer return Signed-off-by: Satya Benson --- .../media/platform/apple/isp/isp-v4l2-test.c | 43 ++- drivers/media/platform/apple/isp/isp-v4l2.c | 177 +++++++----- tools/asahi/isp-tests.py | 47 +++- tools/asahi/isp/buffers.c | 85 +++++- tools/asahi/isp/returns.c | 262 ++++++++++++++++++ 5 files changed, 528 insertions(+), 86 deletions(-) create mode 100644 tools/asahi/isp/returns.c diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 88f428b3b0c27..fe13b70399a1d 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only /* * KUnit tests for the videobuf2 queue of the Apple ISP driver, built into - * isp-v4l2.c. They check the queue parameters vb2 accepts and the buffer - * limit queue_setup() enforces, without any hardware. + * isp-v4l2.c. They check queue parameters, buffer limits and suspended + * return ownership without any hardware. */ #include @@ -393,6 +393,43 @@ static void isp_test_meta_exposure(struct kunit *test) KUNIT_EXPECT_EQ(test, isp->frame_gain, 600); } +/* Returned suspended buffers remain queued, but no longer outstanding. */ +static void isp_test_suspended_return(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct apple_isp_hw hw = {}; + struct isp_surf surface = {}; + struct isp_channel chan = {}; + struct isp_buffer *buf; + struct isp_buflist *bl; + + isp->hw = &hw; + isp->fw_iova_mask = U64_MAX; + spin_lock_init(&isp->buf_lock); + INIT_LIST_HEAD(&isp->bufs_pending); + INIT_LIST_HEAD(&isp->bufs_submitted); + buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + buf->surfs[0].iova = 0x300000; + list_add_tail(&buf->link, &isp->bufs_submitted); + bl = kunit_kzalloc(test, sizeof(*bl) + sizeof(bl->buffers[0]), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bl); + bl->num_buffers = 1; + bl->buffers[0].iovas[0] = buf->surfs[0].iova; + bl->buffers[0].pool_type = CISP_POOL_TYPE_RENDERED; + surface.iova = 0x1000; + surface.virt = bl; + surface.size = sizeof(*bl) + sizeof(bl->buffers[0]); + isp->bt_surf = &surface; + chan.req.arg0 = surface.iova; + chan.req.arg1 = surface.size; + set_bit(ISP_STATE_SLEEPING, &isp->state); + KUNIT_EXPECT_EQ(test, ipc_bt_handle(isp, &chan), 0); + KUNIT_EXPECT_TRUE(test, list_empty(&isp->bufs_submitted)); + KUNIT_EXPECT_PTR_EQ(test, isp->bufs_pending.next, &buf->link); + KUNIT_EXPECT_EQ(test, isp->sequence, 0); +} + static struct kunit_case isp_v4l2_test_cases[] = { KUNIT_CASE(isp_test_meta_exposure), KUNIT_CASE(isp_test_imx958_last_lease_requeue), @@ -401,6 +438,8 @@ static struct kunit_case isp_v4l2_test_cases[] = { KUNIT_CASE(isp_test_p010_format), KUNIT_CASE(isp_test_p010_admission), KUNIT_CASE(isp_test_p010_completion_identity), + + KUNIT_CASE(isp_test_suspended_return), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 7ff6a7857a498..607a76a882b3f 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -125,16 +125,57 @@ void ipc_bt_complete(struct apple_isp *isp) spin_unlock_irqrestore(&isp->buf_lock, flags); } +/* Only identities currently owned by the firmware may be returned. */ +static struct isp_surf *isp_returned_surface(struct apple_isp *isp, + const struct isp_buflist_buffer *desc, + struct isp_buffer **rendered) +{ + u32 iova = desc->iovas[0]; + struct isp_buffer *buf; + + *rendered = NULL; + if (desc->pool_type == CISP_POOL_TYPE_META) { + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + struct isp_surf *meta = isp->meta_surfs[i]; + + if (meta && meta->submitted && (u32)meta->iova == iova) + return meta; + } + } else if (desc->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && + isp_num_capmeta(isp)) { + for (int i = 0; i < isp_num_capmeta(isp); i++) { + struct isp_surf *meta = isp->capmeta_surfs[i]; + + if (meta && meta->submitted && (u32)meta->iova == iova) + return meta; + } + } else { + /* Preserve the existing returned-tag and low-address ABI. */ + list_for_each_entry(buf, &isp->bufs_submitted, link) { + if (isp_is_p010(isp) ? + isp_p010_buffer_matches(isp_get_current_format(isp), buf, desc) : + (u32)buf->surfs[0].iova == iova) { + *rendered = buf; + return &buf->surfs[0]; + } + } + } + + return NULL; +} + int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; - struct isp_buffer *tmp, *buf; + struct isp_surf *surfaces[ISP_MAX_BUFFERS * 2 + ISP_CAPMETA_BUFFERS]; + struct isp_buffer *rendered[ARRAY_SIZE(surfaces)]; + struct isp_buflist_buffer *buffers; struct isp_buflist *bl; u64 count; int err = 0; - /* printk("H2T: 0x%llx 0x%llx 0x%llx\n", (long long)req->arg0, - (long long)req->arg1, (long long)req->arg2); */ + if (READ_ONCE(isp->ipc_error)) + return READ_ONCE(isp->ipc_error); if (!isp->bt_surf || req->arg1 < sizeof(*bl) || req->arg1 > isp->bt_surf->size) { @@ -148,76 +189,88 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) if (!bl) return -EIO; - count = bl->num_buffers; - if (count > (req->arg1 - sizeof(*bl)) / - sizeof(struct isp_buflist_buffer)) { - dev_err_ratelimited(isp->dev, "%s: Bad length 0x%llx\n", - chan->name, req->arg1); + count = READ_ONCE(bl->num_buffers); + if (count > (req->arg1 - sizeof(*bl)) / sizeof(*buffers) || + count > ARRAY_SIZE(surfaces)) { + dev_err_ratelimited(isp->dev, "%s: Bad count 0x%llx\n", + chan->name, count); return -EIO; } + /* Validate and consume the same snapshot of firmware-writable records. */ + buffers = kmemdup(bl->buffers, array_size(count, sizeof(*buffers)), GFP_KERNEL); + if (!buffers) + return -ENOMEM; + spin_lock(&isp->buf_lock); + if (READ_ONCE(isp->ipc_error)) { + err = READ_ONCE(isp->ipc_error); + goto unlock; + } for (u64 i = 0; i < count; i++) { - struct isp_buflist_buffer *bufd = &bl->buffers[i]; - - /* printk("Return: 0x%llx (%d)\n", bufd->iovas[0], - bufd->pool_type); */ - - if (bufd->pool_type == 0) { - for (int j = 0; j < isp_num_meta(isp); j++) { - struct isp_surf *meta = isp->meta_surfs[j]; - if ((u32)bufd->iovas[0] == (u32)meta->iova) { - WARN_ON(!meta->submitted); - isp_meta_read_exposure(isp, meta); - if (isp_is_p010(isp)) - __set_bit(j, &isp->meta_returned); - else - meta->submitted = false; - } + surfaces[i] = isp_returned_surface(isp, &buffers[i], &rendered[i]); + if (!surfaces[i]) { + err = -EIO; + goto unlock; + } + for (u64 j = 0; j < i; j++) { + if (surfaces[i] == surfaces[j]) { + err = -EIO; + goto unlock; } - } else if (bufd->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && - isp_num_capmeta(isp)) { - for (int j = 0; j < isp_num_capmeta(isp); j++) { - struct isp_surf *meta = isp->capmeta_surfs[j]; + } + } + + /* No completion or ownership change until the whole list is valid. */ + for (u64 i = 0; i < count; i++) { + struct isp_buffer *buf = rendered[i]; + + if (buf) { + enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; - if (meta && (u32)bufd->iovas[0] == (u32)meta->iova) - meta->submitted = false; + if (test_bit(ISP_STATE_SLEEPING, &isp->state)) { + list_move_tail(&buf->link, &isp->bufs_pending); + continue; + } + + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = isp->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) + state = VB2_BUF_STATE_DONE; + if (isp_is_p010(isp)) { + buf->return_state = state; + list_move_tail(&buf->link, &isp->bufs_returned); + } else { + list_del(&buf->link); + vb2_buffer_done(&buf->vb.vb2_buf, state); } } else { - list_for_each_entry_safe_reverse( - buf, tmp, &isp->bufs_submitted, link) { - if (isp_is_p010(isp) ? - isp_p010_buffer_matches(isp_get_current_format(isp), - buf, bufd) : - (u32)buf->surfs[0].iova == (u32)bufd->iovas[0]) { - enum vb2_buffer_state state = - VB2_BUF_STATE_ERROR; - - buf->vb.vb2_buf.timestamp = - ktime_get_ns(); - buf->vb.sequence = isp->sequence++; - buf->vb.field = V4L2_FIELD_NONE; - if (req->arg2 == - ISP_IPC_BUFEXC_FLAG_RENDER) - state = VB2_BUF_STATE_DONE; - if (isp_is_p010(isp)) { - buf->return_state = state; - list_move_tail(&buf->link, &isp->bufs_returned); - } else { - vb2_buffer_done(&buf->vb.vb2_buf, state); - list_del(&buf->link); - } + if (buffers[i].pool_type == CISP_POOL_TYPE_META) { + isp_meta_read_exposure(isp, surfaces[i]); + if (isp_is_p010(isp)) { + for (int j = 0; j < isp_num_meta(isp); j++) + if (surfaces[i] == isp->meta_surfs[j]) + __set_bit(j, &isp->meta_returned); + continue; } } + surfaces[i]->submitted = false; } } +unlock: spin_unlock(&isp->buf_lock); + kfree(buffers); + if (err) { + dev_err_ratelimited(isp->dev, "%s: invalid buffer return\n", chan->name); + return err; + } rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK; rsp->arg1 = 0x0; rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK; - return err; + return 0; } /* @@ -584,20 +637,18 @@ int apple_isp_video_suspend(struct apple_isp *isp) mutex_lock(&isp->video_lock); - /* - * Stop the stream but keep its buffers queued to the driver. First - * move the buffers given to the firmware to the pending list, so that - * the buffer return for the stop does not complete them; they are - * submitted again when the stream restarts on resume. - * isp_vb2_buf_queue() does not submit while the stream is stopped. - */ + /* Keep outstanding identities until their return or the stop boundary. */ if (test_bit(ISP_STATE_STREAMING, &isp->state)) { spin_lock_irqsave(&isp->buf_lock, flags); - list_splice_init(&isp->bufs_submitted, &isp->bufs_pending); + set_bit(ISP_STATE_SLEEPING, &isp->state); spin_unlock_irqrestore(&isp->buf_lock, flags); apple_isp_stop_streaming(isp); - set_bit(ISP_STATE_SLEEPING, &isp->state); + + /* Unreturned buffers can be resubmitted only after camera stop. */ + spin_lock_irqsave(&isp->buf_lock, flags); + list_splice_init(&isp->bufs_submitted, &isp->bufs_pending); + spin_unlock_irqrestore(&isp->buf_lock, flags); } mutex_unlock(&isp->video_lock); diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index f9d806b5d5436..a351fd614db67 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -29,10 +29,16 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--out', type=Path) parser.add_argument('--emit', type=Path, help='write C for build-time compilation') + parser.add_argument('--base-source', type=Path, help='pinned public baseline for rollback control') parser.add_argument('--cc', default='clang') - parser.add_argument('--suite', choices=['session', 'buffers', 'receive'], default='session') - parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback', 'drain']) + parser.add_argument('--suite', choices=['session', 'buffers', 'receive', 'returns'], default='session') + parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback', 'drain', 'address', 'pool', 'duplicate', 'snapshot']) args = parser.parse_args() + allowed = {'session': {None, 'send', 'overwrite', 'stop', 'reset'}, + 'buffers': {None, 'rollback'}, 'receive': {None, 'drain'}, + 'returns': {None, 'address', 'pool', 'duplicate', 'snapshot'}} + if args.control not in allowed[args.suite]: + parser.error('control does not belong to this suite') inputs = {n: (DRIVER / n).read_text() for n in ['isp-ipc.c', 'isp-cmd.c', 'isp-cam.c', 'isp-fw.c']} groups = { @@ -62,17 +68,43 @@ def main(): body = body.replace('WRITE_ONCE(isp->ipc_error, 0);', '') bodies['isp_firmware_boot'] = body.replace( 'int err;', 'int err;\n\tWRITE_ONCE(isp->ipc_error, 0);', 1) - if args.suite == 'buffers': + if args.suite in ['buffers', 'returns']: inputs = {'isp-v4l2.c': (DRIVER / 'isp-v4l2.c').read_text()} bodies = {n: function(inputs['isp-v4l2.c'], n) for n in ['isp_submit_buffers', 'apple_isp_start_streaming']} bodies['apple_isp_stop_capture'] = function( (DRIVER / 'isp-cam.c').read_text(), 'apple_isp_stop_capture') if args.control == 'rollback': - old = subprocess.check_output( + old = args.base_source.read_text() if args.base_source else subprocess.check_output( ['git', 'show', '7876c8e8349c4f3d0a2f4ce982e3e13ce5bb5d70:' 'drivers/media/platform/apple/isp/isp-v4l2.c'], cwd=ROOT, text=True) bodies['isp_submit_buffers'] = function(old, 'isp_submit_buffers') + if args.suite == 'returns': + bodies = {n: function(inputs['isp-v4l2.c'], n) for n in + ['isp_returned_surface', 'ipc_bt_handle', 'isp_submit_buffers', + 'apple_isp_start_streaming', 'apple_isp_stop_streaming', + 'isp_vb2_release_buffers', 'apple_isp_video_suspend', + 'apple_isp_video_resume'] + } + bodies['apple_isp_stop_capture'] = function( + (DRIVER / 'isp-cam.c').read_text(), 'apple_isp_stop_capture') + bodies = {**{'apple_isp_stop_capture': bodies.pop('apple_isp_stop_capture')}, **bodies} + + if args.control == 'address': + bodies['isp_returned_surface'] = bodies['isp_returned_surface'].replace( + '(u32)buf->surfs[0].iova == iova', 'true') + elif args.control == 'pool': + body = bodies['isp_returned_surface'] + bodies['isp_returned_surface'] = body.replace( + 'desc->pool_type == CISP_POOL_TYPE_META', 'false') + elif args.control == 'duplicate': + bodies['ipc_bt_handle'] = bodies['ipc_bt_handle'].replace( + 'surfaces[i] == surfaces[j]', 'false') + elif args.control == 'snapshot': + body = bodies['ipc_bt_handle'] + body = body.replace('isp_returned_surface(isp, &buffers[i]', + 'isp_returned_surface(isp, &bl->buffers[i]') + bodies['ipc_bt_handle'] = body if args.suite == 'receive': bodies = {n: function(inputs[f], n) for f, n in [ ('isp-ipc.c', 'chan_rx_done'), ('isp-ipc.c', 'ipc_chan_handle'), @@ -81,7 +113,10 @@ def main(): bodies['ipc_chan_handle'] = bodies['ipc_chan_handle'].replace( 'for (u32 i = 0; i < chan->num; i++)', 'while (1)') code = (ROOT / ('tools/asahi/isp/' + args.suite + '.c')).read_text() - if args.suite == 'buffers': + if args.suite == 'returns': + prefix = (ROOT / 'tools/asahi/isp/buffers.c').read_text() + code = code.replace('/* BUFFER FIXTURE */', prefix.split('/* DRIVER FUNCTIONS */')[0]) + if args.suite in ['buffers', 'returns']: structures = re.findall(r'^struct isp_buflist(?:_buffer)? \{.*?^} __packed;', inputs['isp-v4l2.c'], re.M | re.S) if len(structures) != 2: @@ -99,7 +134,7 @@ def run(out): source.write_text(code) compile_cmd = [args.cc, '-std=gnu11', '-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter', '-Wno-unused-but-set-variable', - '-Wno-sign-compare', '-O1', '-g', '-pthread', + '-Wno-sign-compare', '-Wno-unused-function', '-O1', '-g', '-pthread', '-fsanitize=address,undefined', '-fno-sanitize-recover=all', str(source), '-o', str(binary)] subprocess.run(compile_cmd, check=True) diff --git a/tools/asahi/isp/buffers.c b/tools/asahi/isp/buffers.c index 50e5098cfdede..816c5d749d5b2 100644 --- a/tools/asahi/isp/buffers.c +++ b/tools/asahi/isp/buffers.c @@ -42,6 +42,19 @@ static unsigned int acquisitions; ((void)(l), (void)(f), pthread_mutex_unlock(&buffer_mutex)) #define spin_lock(l) ((void)(l), acquisitions++, pthread_mutex_lock(&buffer_mutex)) #define spin_unlock(l) ((void)(l), pthread_mutex_unlock(&buffer_mutex)) +static bool test_bit(unsigned int bit, const unsigned long *state) +{ + return *state & (1UL << bit); +} + +static inline bool test_and_clear_bit(unsigned int bit, unsigned long *state) +{ + bool previous = test_bit(bit, state); + + *state &= ~(1UL << bit); + return previous; +} + #define ISP_STATE_STREAMING 0 #define ISP_STATE_SLEEPING 2 @@ -97,14 +110,25 @@ static void list_move_tail(struct list_head *e, struct list_head *h) list_add_tail(e, h); } +static void list_splice_init(struct list_head *from, struct list_head *to) +{ + if (from->next == from) + return; + from->prev->next = to->next; + to->next->prev = from->prev; + to->next = from->next; + from->next->prev = to; + INIT_LIST_HEAD(from); +} + static bool list_empty(struct list_head *h) { return h->next == h; } -#define container_of(p, type, m) \ +#define container_of(p, type, m) \ ({ \ - type *__ptr; \ + type *__ptr; \ __ptr = (void *)((char *)(p) - \ offsetof(__typeof__(*__ptr), m)); \ __ptr; \ @@ -137,12 +161,20 @@ struct isp_surf { void *virt; }; +struct apple_isp; +struct vb2_queue { + struct apple_isp *priv; + bool error; +}; + +struct vb2_buffer { + u64 timestamp; + unsigned int done; +}; + struct isp_buffer { struct { - struct { - u64 timestamp; - unsigned int done; - } vb2_buf; + struct vb2_buffer vb2_buf; unsigned int sequence, field; } vb; struct list_head link; @@ -177,14 +209,25 @@ struct apple_isp { struct list_head bufs_pending, bufs_submitted; void *cmd_virt; u64 cmd_iova, fw_iova_mask; - int ipc_error, buf_lock; + int ipc_error, buf_lock, video_lock; unsigned int current_ch; + struct vb2_queue vbq; unsigned long state; unsigned int sequence; }; /* DRIVER STRUCTURES */ static unsigned int sends, fail_at, sent_meta, sent_capmeta; +static int (*return_hook)(struct apple_isp *isp); +static pthread_mutex_t *video_lock(void) +{ + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + + return &lock; +} + +#define mutex_lock(l) ((void)(l), pthread_mutex_lock(video_lock())) +#define mutex_unlock(l) ((void)(l), pthread_mutex_unlock(video_lock())) static bool quiescent; static void (*transition_error)(struct apple_isp *isp); static int send_error; @@ -238,7 +281,7 @@ static int isp_cmd_ch_stop(struct apple_isp *isp, int channel) static int isp_cmd_ch_buffer_return(struct apple_isp *isp, int channel) { - return 0; + return return_hook ? return_hook(isp) : 0; } static void apple_isp_wdt_stop(struct apple_isp *isp) @@ -262,7 +305,10 @@ int main(void) .fmt = { .num_planes = 2, .plane_size = { 4096, 2048 } }, }; - for (unsigned int caps = 0; caps < 2; caps++) { + for (unsigned int mode = 0; mode < 4; mode++) { + unsigned int caps = mode & 1; + bool pre_start = mode & 2; + for (unsigned int fail = 0; fail < 2; fail++) { for (unsigned int change = 0; change < 2; change++) { INIT_LIST_HEAD(&isp.bufs_pending); @@ -296,29 +342,38 @@ int main(void) mutate = change; unsigned int locks_before = acquisitions; - CHECK(isp_submit_buffers(&isp, false) == + CHECK(isp_submit_buffers(&isp, pre_start) == send_error); CHECK(acquisitions == locks_before + 1); CHECK(isp.bufs_pending.next == - &rendered[ISP_MAX_BUFFERS].link); + &rendered[caps && pre_start ? + 0 : + ISP_MAX_BUFFERS] + .link); CHECK(isp.bufs_pending.prev == &rendered[ISP_MAX_BUFFERS].link); CHECK(isp.bufs_submitted.next == - &rendered[0].link); + (caps && pre_start ? &isp.bufs_submitted : + &rendered[0].link)); CHECK(isp.bufs_submitted.prev == - &rendered[ISP_MAX_BUFFERS - 1].link); + (caps && pre_start ? + &isp.bufs_submitted : + &rendered[ISP_MAX_BUFFERS - 1] + .link)); for (unsigned int i = 0; i < ARRAY_SIZE(metadata); i++) CHECK(metadata[i].submitted); for (unsigned int i = 0; i < ARRAY_SIZE(capmeta); i++) - CHECK(capmeta[i].submitted == !!caps); + CHECK(capmeta[i].submitted == + !!(caps && !pre_start)); if (fail) { unsigned int before = sends; unsigned char saved[sizeof(area)]; memcpy(saved, area, sizeof(area)); - CHECK(isp_submit_buffers(&isp, false) == + CHECK(isp_submit_buffers(&isp, + pre_start) == -ETIME); CHECK(sends == before && !memcmp(saved, area, diff --git a/tools/asahi/isp/returns.c b/tools/asahi/isp/returns.c new file mode 100644 index 0000000000000..84fa75cca96ea --- /dev/null +++ b/tools/asahi/isp/returns.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* BUFFER FIXTURE */ +enum vb2_buffer_state { VB2_BUF_STATE_ERROR, VB2_BUF_STATE_DONE }; +#define V4L2_FIELD_NONE 0 + +static bool allocation_failure, change_after_copy; +static unsigned int completions; +static enum vb2_buffer_state expected_state = VB2_BUF_STATE_DONE; +static struct isp_buflist *shared; + +static void *kmemdup(const void *p, size_t size, int flags) +{ + void *copy; + + if (allocation_failure) + return NULL; + copy = malloc(size ? size : 1); + CHECK(copy); + memcpy(copy, p, size); + if (change_after_copy) + memset(shared, 0xff, 16 + size); + return copy; +} + +static void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf, + u64 addr, u64 size) +{ + if (addr < surf->iova || addr - surf->iova > surf->size || + size > surf->size - (addr - surf->iova)) + return NULL; + return (char *)surf->virt + (addr - surf->iova); +} + +static u64 ktime_get_ns(void) +{ + return 1234; +} + +static void vb2_buffer_done(struct vb2_buffer *buf, enum vb2_buffer_state state) +{ + CHECK(state == expected_state); + CHECK(!buf->done); + buf->done++; + completions++; +} + +static void vb2_queue_error(struct vb2_queue *q) +{ + q->error = true; +} + +/* DRIVER FUNCTIONS */ +static struct isp_channel *return_channel; + +static int suspend_return(struct apple_isp *isp) +{ + int err = ipc_bt_handle(isp, return_channel); + + if (err) + isp->ipc_error = err; + return err; +} + +int main(void) +{ + unsigned char memory[8192] = { 0 }; + struct isp_surf surface = { .iova = 0x1000, + .size = sizeof(memory), + .virt = memory }; + struct isp_surf metadata[ISP_MAX_BUFFERS], capmeta[ISP_CAPMETA_BUFFERS]; + struct isp_buffer rendered[ISP_MAX_BUFFERS] = { 0 }; + struct hardware hw = { .capture_meta_size = 1 }; + struct isp_channel chan = { 0 }; + struct apple_isp isp = { .hw = &hw, + .bt_surf = &surface, + .fw_iova_mask = UINT64_MAX }; + struct isp_buflist *bl = (void *)memory; + + shared = bl; + for (unsigned int test = 0; test < 27; test++) { + bool valid = test == 0 || test == 11 || test == 12 || + test == 13 || test == 16 || test == 1 || test == 2 || + test == 5 || test == 18 || test == 19 || test == 20 || + test == 21 || test == 22 || test == 24; + unsigned int before = completions; + u64 count = 1; + + INIT_LIST_HEAD(&isp.bufs_pending); + INIT_LIST_HEAD(&isp.bufs_submitted); + memset(memory, 0, sizeof(memory)); + isp.ipc_error = 0; + isp.sequence = 0; + hw.scl1 = false; + hw.capture_meta_size = 1; + isp.fw_iova_mask = UINT64_MAX; + allocation_failure = test == 10; + change_after_copy = test == 13; + for (unsigned int i = 0; i < ARRAY_SIZE(rendered); i++) { + metadata[i] = + (struct isp_surf){ .iova = 0x100100000ULL + + i * 4096, + .submitted = true }; + isp.meta_surfs[i] = &metadata[i]; + rendered[i].surfs[0].iova = 0x300300000ULL + i * 8192; + rendered[i].vb.vb2_buf.done = 0; + list_add_tail(&rendered[i].link, &isp.bufs_submitted); + } + for (unsigned int i = 0; i < ARRAY_SIZE(capmeta); i++) { + capmeta[i] = (struct isp_surf){ .iova = 0x200200000ULL + + i * 4096, + .submitted = true }; + isp.capmeta_surfs[i] = &capmeta[i]; + } + bl->buffers[0].pool_type = CISP_POOL_TYPE_RENDERED; + bl->buffers[0].iovas[0] = rendered[0].surfs[0].iova; + chan.req = (struct isp_message){ + .arg0 = surface.iova, + .arg1 = 80, + .arg2 = ISP_IPC_BUFEXC_FLAG_RENDER + }; + if (test == 1) + bl->buffers[0].iovas[0] += 1ULL << 32; + if (test == 2) + bl->buffers[0].pool_type = 99; + if (test == 3) { + count = 2; + chan.req.arg1 += 64; + bl->buffers[1] = bl->buffers[0]; + } + if (test == 4) { + list_del(&rendered[0].link); + list_add_tail(&rendered[0].link, &isp.bufs_pending); + } + if (test == 5) + bl->buffers[0].pool_type = CISP_POOL_TYPE_RENDERED_SCL1; + if (test == 6) + chan.req.arg1 = 15; + if (test == 7) + count = UINT64_MAX; + if (test == 8) + chan.req.arg0 += sizeof(memory) - 1; + if (test == 9) + isp.ipc_error = -ETIME; + if (test == 11) { + isp.fw_iova_mask = (1ULL << 36) - 1; + bl->buffers[0].iovas[0] |= 1ULL << 40; + } + if (test == 12) { + count = 0; + chan.req.arg1 = 16; + } + if (test == 14 || test == 15) { + bl->buffers[0].pool_type = + test == 14 ? CISP_POOL_TYPE_META : + ISP_BUFLIST_POOL_CAPTURE_META; + bl->buffers[0].iovas[0] = + test == 14 ? metadata[0].iova : capmeta[0].iova; + metadata[0].submitted = false; + hw.capture_meta_size = 0; + } + if (test == 16 || test == 17) { + count = test == 17 ? 41 : 40; + chan.req.arg1 = 16 + count * 64; + for (unsigned int i = 0; i < 40; i++) { + bl->buffers[i].pool_type = i < 16 ? 0 : + i < 24 ? 2 : + 1; + bl->buffers[i].iovas[0] = + i < 16 ? metadata[i].iova : + i < 24 ? capmeta[i - 16].iova : + rendered[i - 24].surfs[0].iova; + } + } + if (test == 18) + chan.req.arg2 = 0; + if (test >= 19 && test <= 22) { + hw.resident_fw = test >= 21; + isp.state = 1UL << ISP_STATE_STREAMING; + bl->buffers[1].pool_type = CISP_POOL_TYPE_META; + bl->buffers[1].iovas[0] = metadata[0].iova; + if (!(test & 1)) { + count = 2; + chan.req.arg1 += 64; + } + bl->num_buffers = count; + return_hook = suspend_return; + return_channel = &chan; + unsigned int completed_before = completions; + + CHECK(!apple_isp_video_suspend(&isp)); + CHECK(!isp.ipc_error && completions == completed_before); + CHECK(list_empty(&isp.bufs_submitted)); + CHECK(!list_empty(&isp.bufs_pending)); + return_hook = NULL; + isp.cmd_virt = memory; + isp.chan_bh = &chan; + CHECK(!apple_isp_video_resume(&isp)); + CHECK(test_bit(ISP_STATE_STREAMING, &isp.state)); + CHECK(!list_empty(&isp.bufs_submitted)); + CHECK(!isp.vbq.error && completions == completed_before); + isp.state = 0; + continue; + } + if (test == 24) { + hw.capture_meta_size = 0; + bl->buffers[0].pool_type = ISP_BUFLIST_POOL_CAPTURE_META; + } + if (test == 23) + bl->buffers[0].iovas[0] += 123; + /* Reject the entire list even when its prefix is outstanding. */ + if (test >= 25) { + count = test == 25 ? 2 : 4; + chan.req.arg1 = 16 + count * 64; + bl->buffers[count - 1] = bl->buffers[0]; + bl->buffers[count - 1].iovas[0] += 123; + if (test == 26) { + bl->buffers[1].pool_type = CISP_POOL_TYPE_META; + bl->buffers[1].iovas[0] = metadata[0].iova; + bl->buffers[2].pool_type = ISP_BUFLIST_POOL_CAPTURE_META; + bl->buffers[2].iovas[0] = capmeta[0].iova; + } + } + expected_state = test == 18 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE; + bl->num_buffers = count; + CHECK((ipc_bt_handle(&isp, &chan) == 0) == valid); + if (valid && count) { + CHECK(completions == before + (test == 16 ? 16 : 1)); + CHECK(rendered[0].vb.vb2_buf.done == 1); + if (test == 16) { + for (unsigned int i = 0; + i < ARRAY_SIZE(metadata); i++) + CHECK(!metadata[i].submitted); + for (unsigned int i = 0; + i < ARRAY_SIZE(capmeta); i++) + CHECK(!capmeta[i].submitted); + } + /* A stale return with no outstanding identity must not complete twice. */ + CHECK(ipc_bt_handle(&isp, &chan) != 0); + } else { + CHECK(completions == before && + !rendered[0].vb.vb2_buf.done); + CHECK(metadata[1].submitted && capmeta[1].submitted); + if (test >= 25) { + struct list_head *node = isp.bufs_submitted.next; + + CHECK(list_empty(&isp.bufs_pending)); + CHECK(!isp.sequence); + for (unsigned int i = 0; i < ARRAY_SIZE(rendered); i++) { + CHECK(node == &rendered[i].link); + CHECK(!rendered[i].vb.vb2_buf.done); + CHECK(metadata[i].submitted); + node = node->next; + } + CHECK(node == &isp.bufs_submitted); + for (unsigned int i = 0; i < ARRAY_SIZE(capmeta); i++) + CHECK(capmeta[i].submitted); + } + } + } + puts("PASS: 27 return/lifecycle cases, full pools and mutable records"); + return 0; +} From 5ccfbce7f6d269628ab962ab0b4df7919c356055 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:44:27 -0500 Subject: [PATCH 05/12] media: apple: isp: wake streaming queues after IPC failures Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-fw.c | 2 + .../media/platform/apple/isp/isp-v4l2-test.c | 56 ++++++++- drivers/media/platform/apple/isp/isp-v4l2.c | 24 +++- drivers/media/platform/apple/isp/isp-v4l2.h | 1 + tools/asahi/isp-tests.py | 27 ++++- tools/asahi/isp/buffers.c | 5 +- tools/asahi/isp/queue.c | 107 ++++++++++++++++++ tools/asahi/isp/receive.c | 28 ++++- tools/asahi/isp/session.c | 16 ++- 9 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 tools/asahi/isp/queue.c diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index b197db5dd5b25..96dd009a2d559 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -245,6 +245,8 @@ static irqreturn_t apple_isp_isr_thread(int irq, void *dev) } while (more && !READ_ONCE(isp->ipc_error) && !READ_ONCE(isp->ipc_quiescing)); + apple_isp_video_error(isp); + return IRQ_HANDLED; } diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index fe13b70399a1d..b363cc876a0c3 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -2,13 +2,15 @@ /* * KUnit tests for the videobuf2 queue of the Apple ISP driver, built into * isp-v4l2.c. They check queue parameters, buffer limits and suspended - * return ownership without any hardware. + * return ownership and error wakeups without any hardware. */ #include #include #include #include +#include +#include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -430,6 +432,57 @@ static void isp_test_suspended_return(struct kunit *test) KUNIT_EXPECT_EQ(test, isp->sequence, 0); } +struct isp_test_wait { + struct vb2_queue *queue; + struct completion entered; + struct completion exited; + long remaining; +}; + +static int isp_test_queue_waiter(void *arg) +{ + struct isp_test_wait *w = arg; + DEFINE_WAIT(wait); + + prepare_to_wait(&w->queue->done_wq, &wait, TASK_UNINTERRUPTIBLE); + complete(&w->entered); + w->remaining = schedule_timeout(5 * HZ); + finish_wait(&w->queue->done_wq, &wait); + complete(&w->exited); + while (!kthread_should_stop()) + schedule_timeout_interruptible(HZ); + return 0; +} + +/* Use the real vb2 error wakeup, waitqueue and startup notification lock. */ +static void isp_test_error_wakeup(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_test_wait w = { .queue = &isp->vbq }; + struct task_struct *task; + + spin_lock_init(&isp->buf_lock); + init_waitqueue_head(&isp->vbq.done_wq); + isp->ipc_error = -EIO; + apple_isp_video_error(isp); + KUNIT_EXPECT_FALSE(test, isp->vbq.error); + set_bit(ISP_STATE_STREAMING, &isp->state); + isp->ipc_error = 0; + apple_isp_video_error(isp); + KUNIT_EXPECT_FALSE(test, isp->vbq.error); + init_completion(&w.entered); + init_completion(&w.exited); + task = kthread_run(isp_test_queue_waiter, &w, "isp-test-wait"); + KUNIT_ASSERT_FALSE(test, IS_ERR(task)); + wait_for_completion(&w.entered); + isp->ipc_error = -EIO; + apple_isp_video_error(isp); + wait_for_completion(&w.exited); + kthread_stop(task); + KUNIT_EXPECT_TRUE(test, isp->vbq.error); + KUNIT_EXPECT_GT(test, w.remaining, 0L); +} + static struct kunit_case isp_v4l2_test_cases[] = { KUNIT_CASE(isp_test_meta_exposure), KUNIT_CASE(isp_test_imx958_last_lease_requeue), @@ -440,6 +493,7 @@ static struct kunit_case isp_v4l2_test_cases[] = { KUNIT_CASE(isp_test_p010_completion_identity), KUNIT_CASE(isp_test_suspended_return), + KUNIT_CASE(isp_test_error_wakeup), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 607a76a882b3f..1f7e505dabc42 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -503,6 +503,17 @@ static void isp_vb2_release_buffers(struct apple_isp *isp, spin_unlock_irqrestore(&isp->buf_lock, flags); } +/* Serialize error notification with the startup state transition. */ +void apple_isp_video_error(struct apple_isp *isp) +{ + unsigned long flags; + + spin_lock_irqsave(&isp->buf_lock, flags); + if (READ_ONCE(isp->ipc_error) && test_bit(ISP_STATE_STREAMING, &isp->state)) + vb2_queue_error(&isp->vbq); + spin_unlock_irqrestore(&isp->buf_lock, flags); +} + static void isp_vb2_buf_queue(struct vb2_buffer *vb) { struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue); @@ -520,7 +531,7 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) /* Restart an empty IMX958 queue when its last returned lease is queued. */ if (test_bit(ISP_STATE_STREAMING, &isp->state) && (isp_supports_p010(isp) || !empty)) { - if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { + if (isp_submit_buffers(isp, false)) { /* * After a timeout the leases stay owned until the * stream stops. Only firmware that did not answer at @@ -581,7 +592,16 @@ static int apple_isp_start_streaming(struct apple_isp *isp) } } - set_bit(ISP_STATE_STREAMING, &isp->state); + /* Pair with the receive thread's error notification under buf_lock. */ + spin_lock_irqsave(&isp->buf_lock, flags); + err = READ_ONCE(isp->ipc_error); + if (!err) + set_bit(ISP_STATE_STREAMING, &isp->state); + spin_unlock_irqrestore(&isp->buf_lock, flags); + if (err) { + apple_isp_stop_capture(isp); + goto stop_camera; + } return 0; diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index 55135594576b2..b7c4ea0a68c8f 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -12,6 +12,7 @@ void apple_isp_free_video(struct apple_isp *isp); void ipc_bt_complete(struct apple_isp *isp); int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan); +void apple_isp_video_error(struct apple_isp *isp); int apple_isp_video_suspend(struct apple_isp *isp); int apple_isp_video_resume(struct apple_isp *isp); diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index a351fd614db67..326af5db95cba 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -31,12 +31,13 @@ def main(): parser.add_argument('--emit', type=Path, help='write C for build-time compilation') parser.add_argument('--base-source', type=Path, help='pinned public baseline for rollback control') parser.add_argument('--cc', default='clang') - parser.add_argument('--suite', choices=['session', 'buffers', 'receive', 'returns'], default='session') - parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback', 'drain', 'address', 'pool', 'duplicate', 'snapshot']) + parser.add_argument('--suite', choices=['session', 'buffers', 'receive', 'returns', 'queue'], default='session') + parser.add_argument('--control', choices=['send', 'overwrite', 'stop', 'reset', 'rollback', 'drain', 'address', 'pool', 'duplicate', 'snapshot', 'queue-error']) args = parser.parse_args() allowed = {'session': {None, 'send', 'overwrite', 'stop', 'reset'}, 'buffers': {None, 'rollback'}, 'receive': {None, 'drain'}, - 'returns': {None, 'address', 'pool', 'duplicate', 'snapshot'}} + 'returns': {None, 'address', 'pool', 'duplicate', 'snapshot'}, + 'queue': {None, 'queue-error'}} if args.control not in allowed[args.suite]: parser.error('control does not belong to this suite') inputs = {n: (DRIVER / n).read_text() for n in @@ -53,6 +54,9 @@ def main(): groups['isp-ipc.c'] += ['chan_rx_done', 'ipc_chan_handle'] groups['isp-fw.c'] += ['apple_isp_isr_thread'] bodies = {n: function(inputs[f], n) for f, names in groups.items() for n in names} + if args.suite == 'session' and 'apple_isp_video_error(isp);' in bodies['apple_isp_isr_thread']: + bodies = {'apple_isp_video_error': function( + (DRIVER / 'isp-v4l2.c').read_text(), 'apple_isp_video_error'), **bodies} if args.control == 'send': bodies['ipc_chan_send'] = bodies['ipc_chan_send'].replace( 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') @@ -68,7 +72,7 @@ def main(): body = body.replace('WRITE_ONCE(isp->ipc_error, 0);', '') bodies['isp_firmware_boot'] = body.replace( 'int err;', 'int err;\n\tWRITE_ONCE(isp->ipc_error, 0);', 1) - if args.suite in ['buffers', 'returns']: + if args.suite in ['buffers', 'returns', 'queue']: inputs = {'isp-v4l2.c': (DRIVER / 'isp-v4l2.c').read_text()} bodies = {n: function(inputs['isp-v4l2.c'], n) for n in ['isp_submit_buffers', 'apple_isp_start_streaming']} @@ -105,18 +109,29 @@ def main(): body = body.replace('isp_returned_surface(isp, &buffers[i]', 'isp_returned_surface(isp, &bl->buffers[i]') bodies['ipc_bt_handle'] = body + if args.suite == 'queue': + bodies = {n: function(inputs['isp-v4l2.c'], n) for n in + ['isp_submit_buffers', 'isp_vb2_release_buffers', + 'isp_vb2_buf_queue', 'apple_isp_stop_streaming', 'isp_vb2_stop_streaming', + 'apple_isp_start_streaming', 'apple_isp_video_error']} + if args.control == 'queue-error': + bodies['isp_vb2_buf_queue'] = bodies['isp_vb2_buf_queue'].replace( + 'vb2_queue_error(&isp->vbq);', '(void)0;') if args.suite == 'receive': bodies = {n: function(inputs[f], n) for f, n in [ ('isp-ipc.c', 'chan_rx_done'), ('isp-ipc.c', 'ipc_chan_handle'), ('isp-fw.c', 'apple_isp_isr_thread')]} + if 'apple_isp_video_error(isp);' in bodies['apple_isp_isr_thread']: + bodies = {'apple_isp_video_error': function( + (DRIVER / 'isp-v4l2.c').read_text(), 'apple_isp_video_error'), **bodies} if args.control == 'drain': bodies['ipc_chan_handle'] = bodies['ipc_chan_handle'].replace( 'for (u32 i = 0; i < chan->num; i++)', 'while (1)') code = (ROOT / ('tools/asahi/isp/' + args.suite + '.c')).read_text() - if args.suite == 'returns': + if args.suite in ['returns', 'queue']: prefix = (ROOT / 'tools/asahi/isp/buffers.c').read_text() code = code.replace('/* BUFFER FIXTURE */', prefix.split('/* DRIVER FUNCTIONS */')[0]) - if args.suite in ['buffers', 'returns']: + if args.suite in ['buffers', 'returns', 'queue']: structures = re.findall(r'^struct isp_buflist(?:_buffer)? \{.*?^} __packed;', inputs['isp-v4l2.c'], re.M | re.S) if len(structures) != 2: diff --git a/tools/asahi/isp/buffers.c b/tools/asahi/isp/buffers.c index 816c5d749d5b2..af2913f6e3505 100644 --- a/tools/asahi/isp/buffers.c +++ b/tools/asahi/isp/buffers.c @@ -168,6 +168,7 @@ struct vb2_queue { }; struct vb2_buffer { + struct vb2_queue *vb2_queue; u64 timestamp; unsigned int done; }; @@ -213,6 +214,7 @@ struct apple_isp { unsigned int current_ch; struct vb2_queue vbq; unsigned long state; + void *exposure_priority; unsigned int sequence; }; @@ -245,7 +247,8 @@ static int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, } if (mutate) { bl->num_buffers = UINT64_MAX; - memset(bl->buffers, 0xff, 64 * 40); + memset(bl->buffers, 0xff, + ISP_CMD_AREA_SIZE(isp_num_capmeta(isp)) - 16); } int err = fail_at ? (sends == fail_at ? -ETIME : 0) : send_error; diff --git a/tools/asahi/isp/queue.c b/tools/asahi/isp/queue.c new file mode 100644 index 0000000000000..25f6506e1f87a --- /dev/null +++ b/tools/asahi/isp/queue.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* BUFFER FIXTURE */ +enum vb2_buffer_state { VB2_BUF_STATE_ERROR, VB2_BUF_STATE_DONE }; +#define ISP_STATE_STREAMING 0 + +static unsigned int completed, stopped; + +static struct apple_isp *vb2_get_drv_priv(struct vb2_queue *q) +{ + return q->priv; +} + +static void vb2_queue_error(struct vb2_queue *q) +{ + q->error = true; +} + +static void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) +{ + CHECK(quiescent && state == VB2_BUF_STATE_ERROR && !vb->done); + vb->done++; + completed++; +} + +static void apple_isp_stop_capture(struct apple_isp *isp) +{ + stopped++; +} + +static void v4l2_ctrl_grab(void *ctrl, bool grabbed) +{ + CHECK(!grabbed); +} + +/* DRIVER FUNCTIONS */ +static void receive_error(struct apple_isp *isp) +{ + isp->ipc_error = -EIO; + apple_isp_video_error(isp); +} + +int main(void) +{ + struct hardware hw = { 0 }; + struct isp_channel chan = { 0 }; + struct isp_surf metadata[ISP_MAX_BUFFERS]; + struct isp_buffer rendered[3] = { 0 }; + unsigned char area[ISP_CMD_AREA_SIZE(0)]; + struct apple_isp isp = { + .hw = &hw, + .chan_bh = &chan, + .cmd_virt = area, + .fmt = { .num_planes = 2, .plane_size = { 4096, 2048 } }, + .state = 1UL << ISP_STATE_STREAMING, + }; + + isp.vbq.priv = &isp; + INIT_LIST_HEAD(&isp.bufs_pending); + INIT_LIST_HEAD(&isp.bufs_submitted); + for (unsigned int i = 0; i < ARRAY_SIZE(metadata); i++) { + metadata[i] = + (struct isp_surf){ .iova = 0x100000000ULL + i * 4096 }; + isp.meta_surfs[i] = &metadata[i]; + } + for (unsigned int i = 0; i < ARRAY_SIZE(rendered); i++) { + rendered[i].surfs[0].iova = 0x300000000ULL + i * 8192; + rendered[i].vb.vb2_buf.vb2_queue = &isp.vbq; + } + list_add_tail(&rendered[0].link, &isp.bufs_submitted); + send_error = -ETIME; + mutate = true; + isp_vb2_buf_queue(&rendered[1].vb.vb2_buf); + CHECK(isp.vbq.error && !completed && sends == 1); + isp_vb2_buf_queue(&rendered[2].vb.vb2_buf); + CHECK(isp.vbq.error && !completed && sends == 1); + isp_vb2_stop_streaming(&isp.vbq); + CHECK(quiescent && stopped == 1 && completed == 3); + CHECK(list_empty(&isp.bufs_pending) && list_empty(&isp.bufs_submitted)); + isp_vb2_stop_streaming(&isp.vbq); + CHECK(completed == 3 && stopped == 1); + /* A receive error before or during startup must not poison vb2. */ + for (unsigned int when = 0; when < 3; when++) { + isp.state = 0; + isp.vbq.error = false; + isp.ipc_error = 0; + transition_error = when == 1 ? receive_error : NULL; + send_error = 0; + mutate = false; + if (!when) { + receive_error(&isp); + CHECK(!isp.vbq.error); + quiescent = true; + } + int err = apple_isp_start_streaming(&isp); + + CHECK(err == (when == 1 ? -EIO : 0)); + CHECK(!isp.vbq.error); + CHECK(test_bit(ISP_STATE_STREAMING, &isp.state) == (when != 1)); + if (when == 2) { + receive_error(&isp); + CHECK(isp.vbq.error); + } + apple_isp_stop_camera(&isp); + } + puts("PASS: queue error wakeup and shutdown before error completion on close"); + return 0; +} diff --git a/tools/asahi/isp/receive.c b/tools/asahi/isp/receive.c index f924858e09ba7..88d895246253b 100644 --- a/tools/asahi/isp/receive.c +++ b/tools/asahi/isp/receive.c @@ -12,6 +12,8 @@ typedef uint32_t u32; #define READ_ONCE(x) (x) #define WRITE_ONCE(x, v) ((x) = (v)) #define cmpxchg(p, old, v) __sync_val_compare_and_swap(p, old, v) +#define spin_lock_irqsave(l, f) ((void)(l), (f) = 0) +#define spin_unlock_irqrestore(l, f) ((void)(l), (void)(f)) #define mutex_lock(l) ((void)(l)) #define mutex_unlock(l) ((void)(l)) #define wake_up_all(w) ((void)(w)) @@ -30,10 +32,22 @@ struct isp_channel { struct apple_isp { struct isp_channel *chan_sm, *chan_tm, *chan_bt; - int ipc_error, wait; + int ipc_error, wait, vbq, buf_lock; bool ipc_quiescing; + unsigned long state; }; +#define ISP_STATE_STREAMING 0 +static bool test_bit(unsigned int bit, const unsigned long *state) +{ + return *state & (1UL << bit); +} + +static void vb2_queue_error(int *queue) +{ + (*queue)++; +} + static unsigned int schedules; static bool refill; static void check(bool ok, int line) @@ -90,6 +104,15 @@ int main(void) CHECK(!channels[0].remaining && !channels[1].remaining && !channels[2].remaining); CHECK(schedules >= 7 && schedules <= 8); + /* Healthy streaming IRQs must leave the queue usable. */ + isp.state = 1UL << ISP_STATE_STREAMING; + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED); + CHECK(!isp.ipc_error && !isp.vbq); + channels[0].remaining = 1; + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED); + CHECK(!channels[0].remaining && channels[0].handled == 12); + CHECK(!isp.ipc_error && !isp.vbq); + isp.state = 0; /* A continuous producer must let a quiescing invocation finish. */ refill = true; channels[0].remaining = 2; @@ -107,6 +130,9 @@ int main(void) channels[0].error = 0; CHECK(ipc_chan_handle(&isp, &channels[0]) == -EIO && channels[0].remaining == 2); + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED && isp.vbq == 0); + isp.state = 1UL << ISP_STATE_STREAMING; + CHECK(apple_isp_isr_thread(0, &isp) == IRQ_HANDLED && isp.vbq == 1); puts("PASS: bounded turns, burst continuation, channel fairness and error containment"); return 0; } diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c index 246748a976e28..7e10e649985d6 100644 --- a/tools/asahi/isp/session.c +++ b/tools/asahi/isp/session.c @@ -56,7 +56,8 @@ struct hardware { }; struct apple_isp { - int ipc_error, wait, fw_state, irq; + int ipc_error, wait, fw_state, irq, vbq, buf_lock; + unsigned long state; bool ipc_quiescing; struct isp_channel *chan_io, *chan_sm, *chan_tm, *chan_bt; const struct hardware *hw; @@ -65,6 +66,19 @@ struct apple_isp { unsigned int current_ch; }; +#define ISP_STATE_STREAMING 0 +#define spin_lock_irqsave(l, f) ((void)(l), (f) = 0) +#define spin_unlock_irqrestore(l, f) ((void)(l), (void)(f)) +static bool test_bit(unsigned int bit, const unsigned long *state) +{ + return *state & (1UL << bit); +} + +static void vb2_queue_error(int *queue) +{ + (*queue)++; +} + static unsigned int writes, bells, cursors, stops, returns, watchdog_stops; static unsigned int stop_order, free_order, order, halts, freed_released; static int stage_error, fail_stage, stop_error; From 8a81305b7f76735816afea1b125779c1d3813178 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:49:45 -0500 Subject: [PATCH 06/12] media: apple: isp: add an opt-in J613 firmware 26.6.2 preview profile Require matching firmware and compatibility identities on J613/T8122 before enabling preview. Recognize 26.6.2 separately so the existing wide setfile command is selected. Disable still MBNR explicitly and keep temporal history out of the preview path. Preserve H17 and older profiles, including their metadata handling. Test the production firmware parser and admission predicate with malformed identities and board, SoC and opt-in combinations. Capture quality, metadata requirements and suspend remain hardware qualification gates. Signed-off-by: Satya Benson --- MAINTAINERS | 4 ++ drivers/media/platform/apple/isp/isp-cam.c | 10 +++- drivers/media/platform/apple/isp/isp-drv.c | 32 +++++++++++ drivers/media/platform/apple/isp/isp-drv.h | 2 + tools/asahi/isp-preview-flow-tests.py | 57 +++++++++++++++++++ tools/asahi/isp-preview-tests.py | 65 ++++++++++++++++++++++ 6 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 tools/asahi/isp-preview-flow-tests.py create mode 100644 tools/asahi/isp-preview-tests.py diff --git a/MAINTAINERS b/MAINTAINERS index e0a0070243ae6..51c769c4ab512 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2789,6 +2789,10 @@ F: tools/asahi/apple-pwm-pm-tests.py F: drivers/reset/reset-apple-cio.c F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* +F: tools/asahi/isp-preview-flow-tests.py +F: tools/asahi/isp-preview-tests.py +F: tools/asahi/isp-tests.py +F: tools/asahi/isp/ F: tools/asahi/pmp-bootargs-tests.rs F: drivers/spi/spi-apple-test.c F: drivers/spi/spi-apple.c diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 2bc9a44df7288..f2d72341af439 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -904,7 +904,9 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) if (err) return err; - err = isp_cmd_ch_mbnr_enable(isp, ch, 0, ISP_MBNR_MODE_ENABLE, 1); + err = isp_cmd_ch_mbnr_enable(isp, ch, 0, + isp->preview_26 ? ISP_MBNR_MODE_DISABLE : + ISP_MBNR_MODE_ENABLE, 1); if (err) return err; @@ -938,7 +940,11 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } - err = isp_ch_configure_temporal_filter(isp, ch); + /* Preview has no consumer for temporal history on this firmware. */ + if (isp->preview_26) + err = isp_cmd_apple_ch_temporal_filter_disable(isp, ch); + else + err = isp_ch_configure_temporal_filter(isp, ch); if (err) return err; diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 93a21e901ff6a..79951e191d555 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -325,6 +325,11 @@ static int apple_isp_init_presets(struct apple_isp *isp) return 0; } +/* Preview only; processing and image quality need separate qualification. */ +static bool preview_26; +module_param(preview_26, bool, 0400); +MODULE_PARM_DESC(preview_26, "Allow J613 preview on ISP firmware 26.6.2"); + static const char * isp_fw2str(enum isp_firmware_version version) { switch (version) { @@ -336,6 +341,8 @@ static const char * isp_fw2str(enum isp_firmware_version version) return "13.5"; case ISP_FIRMWARE_V_14_7: return "14.7"; + case ISP_FIRMWARE_V_26_6_2: + return "26.6.2"; default: return "unknown"; } @@ -365,6 +372,8 @@ static enum isp_firmware_version isp_read_fw_version(struct device *dev, /* not provided, see isp_check_firmware_version() */ break; case 3: + if (ver[0] == 26 && ver[1] == 6 && ver[2] == 2) + return ISP_FIRMWARE_V_26_6_2; if (ver[0] == 12 && ver[1] == 3 && ver[2] <= 1) return ISP_FIRMWARE_V_12_3; else if (ver[0] == 12 && ver[1] == 4 && ver[2] == 0) @@ -415,6 +424,18 @@ isp_check_firmware_version(struct apple_isp *isp) return compat; } +static bool isp_preview26_admitted(struct device *dev, + enum isp_firmware_version compat) +{ + if (compat != ISP_FIRMWARE_V_26_6_2 || !preview_26 || + !of_machine_is_compatible("apple,j613")) + return false; + if (!of_device_is_compatible(dev->of_node, "apple,t8122-isp")) + return false; + return isp_read_fw_version(dev, "apple,firmware-version") == + ISP_FIRMWARE_V_26_6_2; +} + static int apple_isp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -443,6 +464,17 @@ static int apple_isp_probe(struct platform_device *pdev) * with unknown firmware. */ isp->fw_compat = isp_check_firmware_version(isp); + if (isp->hw->fw_abi != ISP_FW_ABI_H17 && + (isp->fw_compat == ISP_FIRMWARE_V_26_6_2 || + isp_read_fw_version(dev, "apple,firmware-version") == + ISP_FIRMWARE_V_26_6_2)) { + if (!isp_preview26_admitted(dev, isp->fw_compat)) { + err = dev_err_probe(dev, -ENODEV, + "26.6.2 preview requires J613/T8122 and preview_26=1\n"); + goto free_isp; + } + isp->preview_26 = true; + } err = of_property_read_u32(dev->of_node, "apple,platform-id", &isp->platform_id); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index bc2ee0c9556da..4cea1e7e4605a 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -79,6 +79,7 @@ enum isp_firmware_version { ISP_FIRMWARE_V_12_4, ISP_FIRMWARE_V_13_5, ISP_FIRMWARE_V_14_7, + ISP_FIRMWARE_V_26_6_2, }; struct isp_surf { @@ -287,6 +288,7 @@ struct apple_isp { struct device *dev; const struct apple_isp_hw *hw; enum isp_firmware_version fw_compat; + bool preview_26; u32 platform_id; u32 temporal_filter; u32 cam_connections_scheme; diff --git a/tools/asahi/isp-preview-flow-tests.py b/tools/asahi/isp-preview-flow-tests.py new file mode 100644 index 0000000000000..4a8c2ebde2ba0 --- /dev/null +++ b/tools/asahi/isp-preview-flow-tests.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Observe the production capture configuration through fake command boundaries.""" +import importlib.util +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[2] +spec=importlib.util.spec_from_file_location('isp',root/'tools/asahi/isp-tests.py') +mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod) +body=mod.function((root/'drivers/media/platform/apple/isp/isp-cam.c').read_text(),'isp_ch_configure_capture') +names=sorted(set(re.findall(r'\b(isp_(?:cmd_\w+|ch_configure_frame_rate|ch_configure_pools|ch_load_setfile))\(',body))) +code=''' +#include +#include +#include +typedef unsigned int u32; +struct coord {int x,y;}; +struct preset {int index;struct coord crop_offset,crop_size,output_dim;}; +struct isp_format {struct preset *preset;int strides[2];}; +struct hw {int fw_abi;bool lpdp;}; +struct apple_isp {struct hw *hw;bool preview_26;int temporal_filter;}; +static struct preset preset; +static struct isp_format fmt={.preset=&preset}; +static struct isp_format *isp_get_format(struct apple_isp *i,u32 ch){return &fmt;} +#define ISP_FW_ABI_H17 1 +#define ISP_MBNR_MODE_DISABLE 0 +#define ISP_MBNR_MODE_ENABLE 1 +#define CHECK(x) do {if(!(x)){fprintf(stderr,"failed %d\\n",__LINE__);return 1;}}while(0) +static int mbnr_mode,temporal_start,history,enable,disable,pools,fail_disable; +''' +for constant in sorted(set(re.findall(r'\bCISP_\w+',body))):code+=f'#define {constant} 0\n' +for name in names: + if name=='isp_cmd_ch_mbnr_enable': + code+='static int '+name+'(struct apple_isp *i,u32 ch,int a,int mode,int b){mbnr_mode=mode;return 0;}\n';continue + effect={'isp_cmd_apple_ch_temporal_filter_start':'temporal_start++;','isp_cmd_apple_ch_motion_history_start':'history++;','isp_cmd_apple_ch_temporal_filter_enable':'enable++;','isp_cmd_apple_ch_temporal_filter_disable':'disable++;if(fail_disable)return -EIO;','isp_ch_configure_pools':'pools++;'}.get(name,'') + code+='static int '+name+'(struct apple_isp *i,...){'+effect+'return 0;}\n' +code+=body+r''' +int main(void){ + struct hw hw={0};struct apple_isp isp={.hw=&hw}; + for(int profile=0;profile<3;profile++){ + temporal_start=history=enable=disable=pools=0;mbnr_mode=-1; + isp.preview_26=profile==1;hw.fw_abi=profile==2; + CHECK(!isp_ch_configure_capture(&isp,0)); + CHECK(mbnr_mode==(profile==1?0:1)); + CHECK(disable==(profile==1)); + CHECK(temporal_start==(profile!=1)); + CHECK(history==(profile!=1));CHECK(enable==(profile!=1));CHECK(pools==1); + } + isp.preview_26=true;hw.fw_abi=0;pools=0;fail_disable=1; + CHECK(isp_ch_configure_capture(&isp,0)==-EIO);CHECK(pools==0); + puts("PASS: explicit MBNR disable, no temporal/history startup, legacy/H17 unchanged, disable failure propagation");return 0; +} +''' +with tempfile.TemporaryDirectory() as d: + p=Path(d);(p/'test.c').write_text(code) + subprocess.run(['clang','-std=gnu11','-Werror','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/asahi/isp-preview-tests.py b/tools/asahi/isp-preview-tests.py new file mode 100644 index 0000000000000..1dcb7047ecc16 --- /dev/null +++ b/tools/asahi/isp-preview-tests.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise production OF parsing/admission with synthetic device descriptions.""" +import importlib.util +from pathlib import Path +import subprocess, sys, tempfile +root = Path(__file__).resolve().parents[2] +spec = importlib.util.spec_from_file_location('isp', root/'tools/asahi/isp-tests.py') +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) +source = (root/'drivers/media/platform/apple/isp/isp-drv.c').read_text() +header = (root/'drivers/media/platform/apple/isp/isp-drv.h').read_text() +a=header.index('enum isp_firmware_version {'); b=header.index('};',a)+2 +preamble = r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +struct device {void *of_node;}; +static bool preview_26, board, soc; +static int cells=3, bytes=12; +static u32 values[5]={26,6,2}; +#define ISP_FW_VERSION_MIN_LEN 3 +#define ISP_FW_VERSION_MAX_LEN 5 +#define dev_warn(...) ((void)0) +static void *of_find_property(void *n,const char *p,int *len) {*len=bytes; return bytes<0?NULL:values;} +static int of_property_read_variable_u32_array(void *n,const char *p,u32 *out,int min,int max) { + if(cellsmax) return -EINVAL; + memcpy(out,values,cells*sizeof(u32));return cells; +} +static bool of_machine_is_compatible(const char *s) {return board;} +static bool of_device_is_compatible(void *n,const char *s) {return soc;} +#define CHECK(x) do {if (!(x)) {fprintf(stderr,"failed line %d\n",__LINE__);return 1;}} while(0) +''' +main = r''' +int main(void) { + struct device dev={0}; + for(int flags=0;flags<8;flags++) { + preview_26=flags&1;board=flags&2;soc=flags&4; + CHECK(isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)==(flags==7)); + } + preview_26=board=soc=true; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_14_7)); + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_UNKNOWN)); + for(int field=0;field<3;field++) { + values[field]++; CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2));values[field]--; + } + for(int n=-1;n<=6;n++) { + if(n==3)continue; + cells=n;bytes=n*4; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + } + cells=3;bytes=13;CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + puts("PASS: opt-in/board/SoC combinations, compat mismatch, version mismatch, missing/truncated/extended cells"); + return 0; +} +''' +code=preamble+header[a:b]+'\n'+mod.function(source,'isp_read_fw_version')+mod.function(source,'isp_preview26_admitted')+main +with tempfile.TemporaryDirectory() as d: + p=Path(d);(p/'test.c').write_text(code) + subprocess.run(['clang','-std=gnu11','-Werror','-Wno-unused-function','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) From d840ffc899a9352be8bc2341704a014c9f53ae2d Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 13:55:12 -0500 Subject: [PATCH 07/12] ci: register Apple ISP preview host tests Signed-off-by: Satya Benson --- tools/asahi/run-host-tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 5c5ca93c68594..37d43ef1b2aab 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -53,6 +53,9 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { + 'isp-preview-tests.py': [([], PASS)], + 'isp-preview-flow-tests.py': [([], PASS)], + 'isp-tests.py': [(['--suite', suite] + out(), PASS) for suite in ['session', 'buffers', 'receive', 'returns', 'queue']], 'aop-als-channel-tests.py': [([], PASS)], 'mtp-pm-tests.py': [(out(), PASS), (out() + ['--old-transport', '4db3a5a22c43'], PASS)], From fe6c71624d039699cf1cb34e1b217156417eb2dd Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 17:25:33 -0500 Subject: [PATCH 08/12] media: apple: isp: default to qualified J613 26.6.2 preview Enable the preview profile by default only when the existing exact board, SoC, firmware and compatibility admission checks pass. Retain preview_26=0 as an opt-out. C13k on J613 with 26.6.2 firmware qualified preview through PipeWire, open/close cycles and suspend. That boot explicitly passed preview_26=1; default selection is covered by the host admission test. Image noise remains a quality follow-up. Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-drv.c | 6 +++--- tools/asahi/isp-preview-tests.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 79951e191d555..41065fde8e16f 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -326,9 +326,9 @@ static int apple_isp_init_presets(struct apple_isp *isp) } /* Preview only; processing and image quality need separate qualification. */ -static bool preview_26; +static bool preview_26 = true; module_param(preview_26, bool, 0400); -MODULE_PARM_DESC(preview_26, "Allow J613 preview on ISP firmware 26.6.2"); +MODULE_PARM_DESC(preview_26, "Enable J613 preview on ISP firmware 26.6.2 (default: true; false disables)"); static const char * isp_fw2str(enum isp_firmware_version version) { @@ -470,7 +470,7 @@ static int apple_isp_probe(struct platform_device *pdev) ISP_FIRMWARE_V_26_6_2)) { if (!isp_preview26_admitted(dev, isp->fw_compat)) { err = dev_err_probe(dev, -ENODEV, - "26.6.2 preview requires J613/T8122 and preview_26=1\n"); + "26.6.2 preview requires J613/T8122; preview_26=0 disables it\n"); goto free_isp; } isp->preview_26 = true; diff --git a/tools/asahi/isp-preview-tests.py b/tools/asahi/isp-preview-tests.py index 1dcb7047ecc16..06c9d328e60bf 100644 --- a/tools/asahi/isp-preview-tests.py +++ b/tools/asahi/isp-preview-tests.py @@ -20,7 +20,7 @@ #include typedef uint32_t u32; struct device {void *of_node;}; -static bool preview_26, board, soc; +static bool board, soc; static int cells=3, bytes=12; static u32 values[5]={26,6,2}; #define ISP_FW_VERSION_MIN_LEN 3 @@ -38,6 +38,11 @@ main = r''' int main(void) { struct device dev={0}; + board=soc=true; + CHECK(preview_26); + CHECK(isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); + preview_26=false; + CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); for(int flags=0;flags<8;flags++) { preview_26=flags&1;board=flags&2;soc=flags&4; CHECK(isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)==(flags==7)); @@ -54,11 +59,12 @@ CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); } cells=3;bytes=13;CHECK(!isp_preview26_admitted(&dev,ISP_FIRMWARE_V_26_6_2)); - puts("PASS: opt-in/board/SoC combinations, compat mismatch, version mismatch, missing/truncated/extended cells"); + puts("PASS: production default, opt-out/board/SoC combinations, compat mismatch, version mismatch, missing/truncated/extended cells"); return 0; } ''' -code=preamble+header[a:b]+'\n'+mod.function(source,'isp_read_fw_version')+mod.function(source,'isp_preview26_admitted')+main +default=source[source.index('static bool preview_26'):source.index(';',source.index('static bool preview_26'))+1] +code=preamble+default+'\n'+header[a:b]+'\n'+mod.function(source,'isp_read_fw_version')+mod.function(source,'isp_preview26_admitted')+main with tempfile.TemporaryDirectory() as d: p=Path(d);(p/'test.c').write_text(code) subprocess.run(['clang','-std=gnu11','-Werror','-Wno-unused-function','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True) From 6eab6e4933b743dda9b1812b46a2aa7105965b21 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:14:30 -0500 Subject: [PATCH 09/12] selftests: isp: follow the Aurora command and preview helper interfaces Signed-off-by: Satya Benson --- tools/asahi/isp-preview-flow-tests.py | 4 +++- tools/asahi/isp-tests.py | 4 ++-- tools/asahi/isp/session.c | 2 +- tools/asahi/run-host-tests.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/asahi/isp-preview-flow-tests.py b/tools/asahi/isp-preview-flow-tests.py index 4a8c2ebde2ba0..98e7d228f3ff1 100644 --- a/tools/asahi/isp-preview-flow-tests.py +++ b/tools/asahi/isp-preview-flow-tests.py @@ -8,7 +8,7 @@ spec=importlib.util.spec_from_file_location('isp',root/'tools/asahi/isp-tests.py') mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod) body=mod.function((root/'drivers/media/platform/apple/isp/isp-cam.c').read_text(),'isp_ch_configure_capture') -names=sorted(set(re.findall(r'\b(isp_(?:cmd_\w+|ch_configure_frame_rate|ch_configure_pools|ch_load_setfile))\(',body))) +names=sorted(set(re.findall(r'\b(isp_(?:cmd_\w+|ch_configure_frame_rate|ch_configure_pools|ch_load_setfile|ch_configure_power_line))\(',body))) code=''' #include #include @@ -34,6 +34,8 @@ code+='static int '+name+'(struct apple_isp *i,u32 ch,int a,int mode,int b){mbnr_mode=mode;return 0;}\n';continue effect={'isp_cmd_apple_ch_temporal_filter_start':'temporal_start++;','isp_cmd_apple_ch_motion_history_start':'history++;','isp_cmd_apple_ch_temporal_filter_enable':'enable++;','isp_cmd_apple_ch_temporal_filter_disable':'disable++;if(fail_disable)return -EIO;','isp_ch_configure_pools':'pools++;'}.get(name,'') code+='static int '+name+'(struct apple_isp *i,...){'+effect+'return 0;}\n' +code += 'static bool isp_is_p010(struct apple_isp *i){return false;}\n' +code += 'static int isp_ch_configure_temporal_filter(struct apple_isp *i,u32 ch){temporal_start++;history++;enable++;return 0;}\n' code+=body+r''' int main(void){ struct hw hw={0};struct apple_isp isp={.hw=&hw}; diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index 326af5db95cba..f2de14fd416a9 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -44,7 +44,7 @@ def main(): ['isp-ipc.c', 'isp-cmd.c', 'isp-cam.c', 'isp-fw.c']} groups = { 'isp-ipc.c': ['ipc_chan_send'], - 'isp-cmd.c': ['cisp_send'], + 'isp-cmd.c': ['isp_cmd_send'], 'isp-cam.c': ['apple_isp_stop_capture'], 'isp-fw.c': ['isp_firmware_stop', 'isp_firmware_shutdown', 'apple_isp_firmware_halt', 'isp_firmware_boot', @@ -61,7 +61,7 @@ def main(): bodies['ipc_chan_send'] = bodies['ipc_chan_send'].replace( 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') elif args.control == 'overwrite': - bodies['cisp_send'] = bodies['cisp_send'].replace( + bodies['isp_cmd_send'] = bodies['isp_cmd_send'].replace( 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') elif args.control == 'stop': bodies['apple_isp_stop_capture'] = bodies['apple_isp_stop_capture'].replace( diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c index 7e10e649985d6..1a6dfebb54f17 100644 --- a/tools/asahi/isp/session.c +++ b/tools/asahi/isp/session.c @@ -309,7 +309,7 @@ int main(void) for (unsigned int i = 0; i < 3; i++) { CHECK(ipc_chan_send(&isp, &ch, 1) == -ETIME); CHECK(ipc_chan_send(&isp, &ch, 0) == -ETIME); - CHECK(cisp_send(&isp, args, sizeof(args), 0, 1) == -ETIME); + CHECK(isp_cmd_send(&isp, args, sizeof(args), 0, 1) == -ETIME); } CHECK(writes == 1 && bells == 1 && cursors == 0); CHECK(area[0] == 1 && area[1] == 2 && area[2] == 3 && area[3] == 4); diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 37d43ef1b2aab..53f312fe38b59 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -191,7 +191,7 @@ def jobs(work, conf, dtc): if re.search(r'-tests\.(py|rs|sh)$', p.name) and p.name != Path(__file__).name} known = set(ASAHI_TESTS) | set(ASAHI_RUST) | set(ASAHI_SHELL) missing = sorted(found - known) - gone = sorted(known - found) + gone = sorted(name for name in known if not (ASAHI / name).is_file()) if missing or gone: for name in missing: print(f'not registered in {Path(__file__).name}: tools/asahi/{name}', file=sys.stderr) From 30ccdbf9d7cdf20ebebc0c067991db01b736f48c Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:15:56 -0500 Subject: [PATCH 10/12] selftests: isp: model the 7.3 transport fields in J613 fixtures Signed-off-by: Satya Benson --- tools/asahi/isp/buffers.c | 21 +++++++++++++++++++-- tools/asahi/isp/session.c | 14 +++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/tools/asahi/isp/buffers.c b/tools/asahi/isp/buffers.c index af2913f6e3505..d78873ffe27dc 100644 --- a/tools/asahi/isp/buffers.c +++ b/tools/asahi/isp/buffers.c @@ -180,6 +180,8 @@ struct isp_buffer { } vb; struct list_head link; struct isp_surf surfs[4]; + u64 tag; + int return_state; }; struct isp_message { @@ -207,14 +209,18 @@ struct apple_isp { struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; struct isp_surf *capmeta_surfs[ISP_CAPMETA_BUFFERS]; struct isp_surf *bt_surf; - struct list_head bufs_pending, bufs_submitted; + struct list_head bufs_pending, bufs_submitted, bufs_returned; void *cmd_virt; u64 cmd_iova, fw_iova_mask; int ipc_error, buf_lock, video_lock; unsigned int current_ch; struct vb2_queue vbq; unsigned long state; - void *exposure_priority; + void *exposure_priority, *power_line_frequency; + bool ipc_failed; + int fw_state; + u64 buffer_tag; + unsigned long meta_returned; unsigned int sequence; }; @@ -293,6 +299,17 @@ static void apple_isp_wdt_stop(struct apple_isp *isp) static void apple_isp_stop_capture(struct apple_isp *isp); +/* These fixtures exercise the non-P010/J613 path. P010 has kernel KUnit coverage. */ +#define ISP_FW_DEAD 3 +#define __set_bit set_bit +static bool isp_is_p010(struct apple_isp *isp) { return false; } +static bool isp_supports_p010(struct apple_isp *isp) { return false; } +static unsigned int isp_num_meta(struct apple_isp *isp) { return ISP_MAX_BUFFERS; } +static unsigned int isp_num_rendered(struct apple_isp *isp) { return ISP_MAX_BUFFERS; } +static int isp_ipc_resync(struct apple_isp *isp) { return 0; } +static void isp_meta_read_exposure(struct apple_isp *isp, struct isp_surf *surf) { } +static bool isp_p010_buffer_matches(struct isp_format *fmt, struct isp_buffer *buf, const struct isp_buflist_buffer *desc) { return false; } +static void apple_isp_firmware_halt(struct apple_isp *isp) { quiescent = true; } /* DRIVER FUNCTIONS */ int main(void) { diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c index 1a6dfebb54f17..5f09ca5081109 100644 --- a/tools/asahi/isp/session.c +++ b/tools/asahi/isp/session.c @@ -42,11 +42,13 @@ static void check(bool ok, int line) #define CHECK(x) check((x), __LINE__) struct isp_message { - u64 arg0, arg1, arg2; + u64 arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7; }; struct isp_channel { - struct isp_message req; + struct isp_message req, rsp; + unsigned long deadline; + bool deadline_active; unsigned int doorbell, num, cursor, remaining; int lock; }; @@ -58,7 +60,8 @@ struct hardware { struct apple_isp { int ipc_error, wait, fw_state, irq, vbq, buf_lock; unsigned long state; - bool ipc_quiescing; + bool ipc_quiescing, ipc_failed; + struct isp_channel *ipc_stale; struct isp_channel *chan_io, *chan_sm, *chan_tm, *chan_bt; const struct hardware *hw; void *dev, *wq, *cmd_virt; @@ -283,6 +286,11 @@ static void isp_free_released_surfaces(struct apple_isp *isp) freed_released++; } +static u64 get_unaligned_le64(const void *p) { u64 v; memcpy(&v, p, sizeof(v)); return v; } +#define IS_ENABLED(x) 0 +#define jiffies 0 +static bool isp_is_p010(struct apple_isp *isp) { return false; } +static int isp_ipc_resync(struct apple_isp *isp) { return 0; } /* DRIVER FUNCTIONS */ int main(void) { From e96cbeff2f6cf71f970e030cced8b27dd1ee81e0 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:16:39 -0500 Subject: [PATCH 11/12] selftests: isp: retain effective negative controls on Aurora Signed-off-by: Satya Benson --- tools/asahi/isp-tests.py | 6 ++++-- tools/asahi/run-host-tests.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py index f2de14fd416a9..92c412e2abfd5 100644 --- a/tools/asahi/isp-tests.py +++ b/tools/asahi/isp-tests.py @@ -61,8 +61,10 @@ def main(): bodies['ipc_chan_send'] = bodies['ipc_chan_send'].replace( 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') elif args.control == 'overwrite': - bodies['isp_cmd_send'] = bodies['isp_cmd_send'].replace( - 'int err = READ_ONCE(isp->ipc_error);', 'int err = 0;') + guard = 'if (READ_ONCE(isp->ipc_error))\n\t\treturn READ_ONCE(isp->ipc_error);' + if guard not in bodies['isp_cmd_send']: + raise RuntimeError('overwrite negative control no longer matches its guard') + bodies['isp_cmd_send'] = bodies['isp_cmd_send'].replace(guard, '') elif args.control == 'stop': bodies['apple_isp_stop_capture'] = bodies['apple_isp_stop_capture'].replace( 'if (!isp_cmd_ch_stop(isp, 0)) /* TODO channel mask */', diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 53f312fe38b59..ec63109b4d22f 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -39,7 +39,7 @@ # A killed mutant shows the failing control: a C assertion or abort (-6), or # a Rust panic (exit 101). A Python traceback means the harness itself broke. -KILL_SIGN = re.compile(r'Assertion|": -6\b|"exit": 101\b|panicked at') +KILL_SIGN = re.compile(r'FAIL line [0-9]+|Assertion|": -6\b|"exit": 101\b|panicked at') BROKEN_SIGN = re.compile(r'Traceback \(most recent call last\)') @@ -55,7 +55,7 @@ def mutations(flag, names, base, expect=KILL): ASAHI_TESTS = { 'isp-preview-tests.py': [([], PASS)], 'isp-preview-flow-tests.py': [([], PASS)], - 'isp-tests.py': [(['--suite', suite] + out(), PASS) for suite in ['session', 'buffers', 'receive', 'returns', 'queue']], + 'isp-tests.py': [(['--suite', suite] + out(), PASS) for suite in ['session', 'buffers', 'receive', 'returns', 'queue']] + [(['--suite', suite, '--control', control] + out(), KILL) for suite, controls in [('session', ['send', 'overwrite', 'stop', 'reset']), ('buffers', ['rollback']), ('receive', ['drain']), ('returns', ['address', 'pool', 'duplicate', 'snapshot']), ('queue', ['queue-error'])] for control in controls], 'aop-als-channel-tests.py': [([], PASS)], 'mtp-pm-tests.py': [(out(), PASS), (out() + ['--old-transport', '4db3a5a22c43'], PASS)], From 08a8be6b768da40feacb7f2dae1920bd61751d17 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 23:51:03 -0500 Subject: [PATCH 12/12] media: apple: isp: keep P010 late-ACK resynchronization on timeout Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-ipc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c index 634cb74fe4bf3..77ef0016a34de 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -282,6 +282,12 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, "%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n", chan->name, chan->req.arg0, chan->req.arg1, chan->req.arg2); + /* + * P010 resolves a late full-payload ACK before reusing the ring. + * Keep that recoverable timeout path; compact sessions stay poisoned. + */ + if (isp_is_p010(isp)) + return -ETIME; cmpxchg(&isp->ipc_error, 0, -ETIME); wake_up_all(&isp->wait); return READ_ONCE(isp->ipc_error);