diff --git a/MAINTAINERS b/MAINTAINERS index 491823243a5fc1..f32127873ed190 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2723,6 +2723,10 @@ F: drivers/pwm/pwm-apple.c 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.c F: drivers/spmi/spmi-apple-controller.c diff --git a/drivers/media/platform/apple/isp/.kunitconfig b/drivers/media/platform/apple/isp/.kunitconfig new file mode 100644 index 00000000000000..55e7f3a7025be6 --- /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/Kconfig b/drivers/media/platform/apple/isp/Kconfig index b51d19ee115ded..a43387268bfb56 100644 --- a/drivers/media/platform/apple/isp/Kconfig +++ b/drivers/media/platform/apple/isp/Kconfig @@ -24,8 +24,8 @@ config VIDEO_APPLE_ISP_KUNIT_TEST depends on KUNIT=y || VIDEO_APPLE_ISP=m default KUNIT_ALL_TESTS help - Builds unit tests for the videobuf2 queue set-up and for the - shared memory the firmware frees into the Apple ISP driver. They + Builds unit tests for videobuf2 queue setup, return ownership, error + wakeups and shared memory the firmware frees into the ISP driver. They need no hardware. Only useful for kernel developers running KUnit. If unsure, say N. diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 5090ed2ed5b985..1bc333018d8c36 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -504,7 +504,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; @@ -538,6 +540,14 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch) return err; } + /* Preview has no consumer for temporal history on this firmware. */ + if (isp->preview_26) { + err = isp_cmd_apple_ch_temporal_filter_disable(isp, ch); + if (err) + return err; + return isp_ch_configure_pools(isp, ch); + } + err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter); if (err) return err; @@ -655,8 +665,8 @@ int apple_isp_start_capture(struct apple_isp *isp) void apple_isp_stop_capture(struct apple_isp *isp) { - 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 42c448fd0f30ce..1fd961f6cdb9ff 100644 --- a/drivers/media/platform/apple/isp/isp-cmd.c +++ b/drivers/media/platform/apple/isp/isp-cmd.c @@ -21,7 +21,11 @@ static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, { struct isp_channel *chan = isp->chan_io; struct isp_message *req = &chan->req; - int err; + int err = READ_ONCE(isp->ipc_error); + + /* Do not overwrite a command the firmware may still be reading. */ + if (err) + return err; req->arg0 = isp->cmd_iova; req->arg1 = insize; diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 2cbdcd2fb00bde..1ea243175a4afd 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -289,6 +289,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 = true; +module_param(preview_26, bool, 0400); +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) { switch (version) { @@ -300,6 +305,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"; } @@ -329,6 +336,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) @@ -379,6 +388,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; @@ -407,6 +428,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; preview_26=0 disables it\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 8f945c26f00e6d..27b2e2720c369b 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -76,6 +76,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 { @@ -262,6 +263,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; struct isp_preset *presets; @@ -310,6 +312,10 @@ 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; + /* 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 c96681173fd0fa..34b1360be46ebe 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -225,16 +225,24 @@ 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)); + + apple_isp_video_error(isp); return IRQ_HANDLED; } @@ -789,6 +797,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) { @@ -920,6 +931,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; } @@ -936,6 +948,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); @@ -953,8 +968,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; } @@ -969,6 +986,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); } @@ -991,7 +1010,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"); @@ -1022,7 +1041,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; } @@ -1033,9 +1052,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 f709f6e3057639..6e4aa4cb96358d 100644 --- a/drivers/media/platform/apple/isp/isp-ipc.c +++ b/drivers/media/platform/apple/isp/isp-ipc.c @@ -139,10 +139,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) { - int err = 0; + int err = -EAGAIN; mutex_lock(&chan->lock); - while (1) { + /* 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; @@ -150,8 +155,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); @@ -175,6 +183,10 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan, unsigned long timeout) { long t; + int err = READ_ONCE(isp->ipc_error); + + if (err) + return err; chan_write_msg(isp, chan, &chan->req); dma_wmb(); @@ -184,15 +196,22 @@ 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) { dev_err(isp->dev, "%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); } + 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-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 895a6d1299c220..3eb5c3ac5e344a 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -1,13 +1,15 @@ // 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 and error wakeups without any hardware. */ #include #include #include +#include +#include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -107,7 +109,97 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } +/* 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); +} + +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_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 072b8523279c28..46eb604de2f8c5 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -57,16 +57,55 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 +/* 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 ((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) { @@ -80,66 +119,74 @@ 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 < ARRAY_SIZE(isp->meta_surfs); j++) { - struct isp_surf *meta = isp->meta_surfs[j]; - if ((u32)bufd->iovas[0] == (u32)meta->iova) { - WARN_ON(!meta->submitted); - 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]; + } + } - if (meta && (u32)bufd->iovas[0] == (u32)meta->iova) - meta->submitted = false; + /* 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 (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; + 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 ((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; - vb2_buffer_done(&buf->vb.vb2_buf, - state); - list_del(&buf->link); - } - } + 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; } /* @@ -152,7 +199,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; @@ -161,6 +208,9 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) struct isp_buflist *bl = isp->cmd_virt; struct isp_buflist_buffer *bufd = &bl->buffers[0]; + 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))); bl->type = 1; @@ -236,10 +286,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; @@ -248,43 +294,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) { - /* 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 < ARRAY_SIZE(isp->meta_surfs); 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; } @@ -393,6 +411,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); @@ -407,8 +436,9 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) - isp_submit_buffers(isp, false); + if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty && + isp_submit_buffers(isp, false)) + vb2_queue_error(&isp->vbq); } static int apple_isp_start_streaming(struct apple_isp *isp) @@ -423,18 +453,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) { @@ -458,7 +486,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; @@ -511,20 +548,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/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h index 622288296aaae0..62b9fe7eaf2039 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.h +++ b/drivers/media/platform/apple/isp/isp-v4l2.h @@ -11,6 +11,7 @@ void apple_isp_remove_video(struct apple_isp *isp); void apple_isp_free_video(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-preview-flow-tests.py b/tools/asahi/isp-preview-flow-tests.py new file mode 100644 index 00000000000000..4a8c2ebde2ba0e --- /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 00000000000000..06c9d328e60bfa --- /dev/null +++ b/tools/asahi/isp-preview-tests.py @@ -0,0 +1,71 @@ +#!/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 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}; + 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)); + } + 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: production default, opt-out/board/SoC combinations, compat mismatch, version mismatch, missing/truncated/extended cells"); + return 0; +} +''' +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) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/asahi/isp-tests.py b/tools/asahi/isp-tests.py new file mode 100644 index 00000000000000..326af5db95cbad --- /dev/null +++ b/tools/asahi/isp-tests.py @@ -0,0 +1,172 @@ +#!/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('--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', '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'}, + '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 + ['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'], + } + 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.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;') + 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) + 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']} + bodies['apple_isp_stop_capture'] = function( + (DRIVER / 'isp-cam.c').read_text(), 'apple_isp_stop_capture') + if args.control == 'rollback': + 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 == '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 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', 'queue']: + 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: + 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', '-Wno-unused-but-set-variable', + '-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) + 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/buffers.c b/tools/asahi/isp/buffers.c new file mode 100644 index 00000000000000..af2913f6e35050 --- /dev/null +++ b/tools/asahi/isp/buffers.c @@ -0,0 +1,415 @@ +// 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)) +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 + +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 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) \ + ({ \ + 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 apple_isp; +struct vb2_queue { + struct apple_isp *priv; + bool error; +}; + +struct vb2_buffer { + struct vb2_queue *vb2_queue; + u64 timestamp; + unsigned int done; +}; + +struct isp_buffer { + struct { + struct vb2_buffer 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, video_lock; + unsigned int current_ch; + struct vb2_queue vbq; + unsigned long state; + void *exposure_priority; + 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; +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, + ISP_CMD_AREA_SIZE(isp_num_capmeta(isp)) - 16); + } + 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 return_hook ? return_hook(isp) : 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 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); + 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, pre_start) == + send_error); + CHECK(acquisitions == locks_before + 1); + CHECK(isp.bufs_pending.next == + &rendered[caps && pre_start ? + 0 : + ISP_MAX_BUFFERS] + .link); + CHECK(isp.bufs_pending.prev == + &rendered[ISP_MAX_BUFFERS].link); + CHECK(isp.bufs_submitted.next == + (caps && pre_start ? &isp.bufs_submitted : + &rendered[0].link)); + CHECK(isp.bufs_submitted.prev == + (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 && !pre_start)); + if (fail) { + unsigned int before = sends; + unsigned char saved[sizeof(area)]; + + memcpy(saved, area, sizeof(area)); + CHECK(isp_submit_buffers(&isp, + pre_start) == + -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; +} diff --git a/tools/asahi/isp/queue.c b/tools/asahi/isp/queue.c new file mode 100644 index 00000000000000..25f6506e1f87ac --- /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 new file mode 100644 index 00000000000000..88d895246253b9 --- /dev/null +++ b/tools/asahi/isp/receive.c @@ -0,0 +1,138 @@ +// 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 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)) +#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, 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) +{ + 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); + /* 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; + 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); + 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/returns.c b/tools/asahi/isp/returns.c new file mode 100644 index 00000000000000..84fa75cca96ea8 --- /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; +} diff --git a/tools/asahi/isp/session.c b/tools/asahi/isp/session.c new file mode 100644 index 00000000000000..7e10e649985d65 --- /dev/null +++ b/tools/asahi/isp/session.c @@ -0,0 +1,380 @@ +// 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 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 +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, num, cursor, remaining; + int lock; +}; + +struct hardware { + bool resident_fw; +}; + +struct apple_isp { + 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; + void *dev, *wq, *cmd_virt; + u64 cmd_iova; + 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; +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 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; + return 0; +} + +static void isp_disable_irq(struct apple_isp *isp) +{ + if (flood) + apple_isp_isr_thread(isp->irq, 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 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++; +} + +/* DRIVER FUNCTIONS */ +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, + .chan_sm = &incoming, .chan_tm = &incoming, + .chan_bt = &incoming }; + + 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); + 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; +} diff --git a/tools/aurora-ci/host-tests.json b/tools/aurora-ci/host-tests.json index a1b93d695dfd6e..f8ff5344de8e3c 100644 --- a/tools/aurora-ci/host-tests.json +++ b/tools/aurora-ci/host-tests.json @@ -2282,6 +2282,85 @@ "{output}" ], "note": "Build only the host Kconfig tool in an isolated output directory; check all built-in/module dependency combinations." + }, + { + "name": "tools/asahi/isp-preview-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-preview-tests.py" + ] + }, + { + "name": "tools/asahi/isp-preview-flow-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-preview-flow-tests.py" + ] + }, + { + "name": "tools/asahi/isp-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-tests.py", + "--suite", + "session", + "--out", + "{output}" + ] + }, + { + "name": "isp-buffers", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-tests.py", + "--suite", + "buffers", + "--out", + "{output}" + ], + "commits": [ + "7876c8e8349c4f3d0a2f4ce982e3e13ce5bb5d70" + ] + }, + { + "name": "isp-receive", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-tests.py", + "--suite", + "receive", + "--out", + "{output}" + ] + }, + { + "name": "isp-returns", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-tests.py", + "--suite", + "returns", + "--out", + "{output}" + ] + }, + { + "name": "isp-queue", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/isp-tests.py", + "--suite", + "queue", + "--out", + "{output}" + ] } ], "helpers": {