get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2197005/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2197005,
    "url": "http://patchwork.ozlabs.org/api/patches/2197005/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/uboot/patch/05cc2003b7e0951a80b0078b3824d14466979b3d.1771275704.git.daniel@makrotopia.org/",
    "project": {
        "id": 18,
        "url": "http://patchwork.ozlabs.org/api/projects/18/?format=api",
        "name": "U-Boot",
        "link_name": "uboot",
        "list_id": "u-boot.lists.denx.de",
        "list_email": "u-boot@lists.denx.de",
        "web_url": null,
        "scm_url": null,
        "webscm_url": null,
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<05cc2003b7e0951a80b0078b3824d14466979b3d.1771275704.git.daniel@makrotopia.org>",
    "list_archive_url": null,
    "date": "2026-02-16T21:22:59",
    "name": "[RFC,11/20] test: boot: add image_loader unit tests",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "8b1aec9c16d9870a4f85e3df4812a5e43a5bee30",
    "submitter": {
        "id": 64091,
        "url": "http://patchwork.ozlabs.org/api/people/64091/?format=api",
        "name": "Daniel Golle",
        "email": "daniel@makrotopia.org"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/uboot/patch/05cc2003b7e0951a80b0078b3824d14466979b3d.1771275704.git.daniel@makrotopia.org/mbox/",
    "series": [
        {
            "id": 492351,
            "url": "http://patchwork.ozlabs.org/api/series/492351/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/uboot/list/?series=492351",
            "date": "2026-02-16T21:21:14",
            "name": "boot: add OpenWrt boot method and on-demand FIT loading",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/492351/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2197005/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2197005/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<u-boot-bounces@lists.denx.de>",
        "X-Original-To": "incoming@patchwork.ozlabs.org",
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de\n (client-ip=85.214.62.61; helo=phobos.denx.de;\n envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org)",
            "phobos.denx.de;\n dmarc=none (p=none dis=none) header.from=makrotopia.org",
            "phobos.denx.de;\n spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de",
            "phobos.denx.de; dmarc=none (p=none dis=none)\n header.from=makrotopia.org",
            "phobos.denx.de;\n spf=pass smtp.mailfrom=daniel@makrotopia.org"
        ],
        "Received": [
            "from phobos.denx.de (phobos.denx.de [85.214.62.61])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fFG523jpTz1xwD\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 17 Feb 2026 08:24:54 +1100 (AEDT)",
            "from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id 1799F83C72;\n\tMon, 16 Feb 2026 22:23:43 +0100 (CET)",
            "by phobos.denx.de (Postfix, from userid 109)\n id A48B283D3D; Mon, 16 Feb 2026 22:23:22 +0100 (CET)",
            "from pidgin.makrotopia.org (pidgin.makrotopia.org\n [IPv6:2a07:2ec0:3002::65])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))\n (No client certificate requested)\n by phobos.denx.de (Postfix) with ESMTPS id 5F4C083DEB\n for <u-boot@lists.denx.de>; Mon, 16 Feb 2026 22:23:19 +0100 (CET)",
            "from local\n by pidgin.makrotopia.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256)\n (Exim 4.99) (envelope-from <daniel@makrotopia.org>)\n id 1vs63a-000000002kb-2j5o; Mon, 16 Feb 2026 21:23:02 +0000"
        ],
        "X-Spam-Checker-Version": "SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de",
        "X-Spam-Level": "",
        "X-Spam-Status": "No, score=-1.9 required=5.0 tests=BAYES_00,\n RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham\n autolearn_force=no version=3.4.2",
        "Date": "Mon, 16 Feb 2026 21:22:59 +0000",
        "From": "Daniel Golle <daniel@makrotopia.org>",
        "To": "Tom Rini <trini@konsulko.com>, Simon Glass <sjg@chromium.org>,\n Quentin Schulz <quentin.schulz@cherry.de>,\n Daniel Golle <daniel@makrotopia.org>,\n Kory Maincent <kory.maincent@bootlin.com>,\n Mattijs Korpershoek <mkorpershoek@kernel.org>,\n Martin Schwan <m.schwan@phytec.de>, Anshul Dalal <anshuld@ti.com>,\n Ilias Apalodimas <ilias.apalodimas@linaro.org>,\n Sughosh Ganu <sughosh.ganu@arm.com>, Aristo Chen <jj251510319013@gmail.com>,\n\t=?utf-8?b?54mbIOW/l+Wujw==?= <Zone.Niuzh@hotmail.com>,\n Marek Vasut <marek.vasut+renesas@mailbox.org>,\n Heinrich Schuchardt <xypron.glpk@gmx.de>,\n Wolfgang Wallner <wolfgang.wallner@at.abb.com>,\n Frank Wunderlich <frank-w@public-files.de>,\n David Lechner <dlechner@baylibre.com>,\n Osama Abdelkader <osama.abdelkader@gmail.com>,\n Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>,\n Michael Trimarchi <michael@amarulasolutions.com>,\n Miquel Raynal <miquel.raynal@bootlin.com>,\n Andrew Goodbody <andrew.goodbody@linaro.org>,\n Yegor Yefremov <yegorslists@googlemail.com>,\n Mike Looijmans <mike.looijmans@topic.nl>,\n Weijie Gao <weijie.gao@mediatek.com>,\n Alexander Stein <alexander.stein@ew.tq-group.com>,\n Neil Armstrong <neil.armstrong@linaro.org>,\n Mayuresh Chitale <mchitale@ventanamicro.com>,\n Paul HENRYS <paul.henrys_ext@softathome.com>, u-boot@lists.denx.de",
        "Cc": "John Crispin <john@phrozen.org>, Paul Spooren <mail@aparcar.org>",
        "Subject": "[RFC PATCH 11/20] test: boot: add image_loader unit tests",
        "Message-ID": "\n <05cc2003b7e0951a80b0078b3824d14466979b3d.1771275704.git.daniel@makrotopia.org>",
        "References": "<cover.1771275704.git.daniel@makrotopia.org>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=utf-8",
        "Content-Disposition": "inline",
        "Content-Transfer-Encoding": "8bit",
        "In-Reply-To": "<cover.1771275704.git.daniel@makrotopia.org>",
        "X-Mailman-Approved-At": "Mon, 16 Feb 2026 22:23:42 +0100",
        "X-BeenThere": "u-boot@lists.denx.de",
        "X-Mailman-Version": "2.1.39",
        "Precedence": "list",
        "List-Id": "U-Boot discussion <u-boot.lists.denx.de>",
        "List-Unsubscribe": "<https://lists.denx.de/options/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=unsubscribe>",
        "List-Archive": "<https://lists.denx.de/pipermail/u-boot/>",
        "List-Post": "<mailto:u-boot@lists.denx.de>",
        "List-Help": "<mailto:u-boot-request@lists.denx.de?subject=help>",
        "List-Subscribe": "<https://lists.denx.de/listinfo/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=subscribe>",
        "Errors-To": "u-boot-bounces@lists.denx.de",
        "Sender": "\"U-Boot\" <u-boot-bounces@lists.denx.de>",
        "X-Virus-Scanned": "clamav-milter 0.103.8 at phobos.denx.de",
        "X-Virus-Status": "Clean"
    },
    "content": "Add unit tests for the image_loader framework covering its core\nlogic with a mock storage backend:\n\n- map() allocates, reads and records a region\n- map() returns cached pointer for already-mapped range\n- map() returns correct offset within a larger region\n- map() re-reads when extending a region to a larger size\n- map_to() reads to a specified address and records it\n- lookup() returns NULL for unmapped ranges\n- alloc_ptr advances with correct alignment\n- map() returns NULL when the translation table is full\n- cleanup() calls backend and resets state\n- map() with multiple disjoint regions\n- read beyond image size returns error\n\nAlso fix IMAGE_LOADER_MAX_REGIONS Kconfig to depend on IMAGE_LOADER\nand default to 16 unconditionally (the previous 'default 0' fallback\ncaused the regions array to be zero-sized when IMAGE_LOADER was\nenabled after initial defconfig generation).\n\nRegister the new 'image_loader' test suite in test/cmd_ut.c so it\ncan be run via 'ut image_loader'.\n\nSigned-off-by: Daniel Golle <daniel@makrotopia.org>\n---\n boot/Kconfig             |   4 +-\n test/boot/Makefile       |   2 +\n test/boot/image_loader.c | 429 +++++++++++++++++++++++++++++++++++++++\n test/cmd_ut.c            |   2 +\n 4 files changed, 435 insertions(+), 2 deletions(-)\n create mode 100644 test/boot/image_loader.c",
    "diff": "diff --git a/boot/Kconfig b/boot/Kconfig\nindex 1f870c7d251..efc06f3cd1a 100644\n--- a/boot/Kconfig\n+++ b/boot/Kconfig\n@@ -1179,8 +1179,8 @@ config IMAGE_LOADER\n \n config IMAGE_LOADER_MAX_REGIONS\n \tint \"Maximum number of mapped regions in image loader\"\n-\tdefault 16 if IMAGE_LOADER\n-\tdefault 0\n+\tdepends on IMAGE_LOADER\n+\tdefault 16\n \thelp\n \t  Maximum number of distinct image regions that can be mapped\n \t  into RAM simultaneously. 16 is sufficient for typical FIT\ndiff --git a/test/boot/Makefile b/test/boot/Makefile\nindex 89538d4f0a6..6fd349a65bc 100644\n--- a/test/boot/Makefile\n+++ b/test/boot/Makefile\n@@ -23,3 +23,5 @@ endif\n obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o\n \n obj-$(CONFIG_UPL) += upl.o\n+\n+obj-$(CONFIG_IMAGE_LOADER) += image_loader.o\ndiff --git a/test/boot/image_loader.c b/test/boot/image_loader.c\nnew file mode 100644\nindex 00000000000..dc4b0b4173a\n--- /dev/null\n+++ b/test/boot/image_loader.c\n@@ -0,0 +1,429 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * Tests for image_loader framework\n+ *\n+ * Copyright (C) 2026 Daniel Golle <daniel@makrotopia.org>\n+ */\n+\n+#include <image-loader.h>\n+#include <mapmem.h>\n+#include <malloc.h>\n+#include <asm/cache.h>\n+#include <test/test.h>\n+#include <test/ut.h>\n+\n+#define IMG_LOADER_TEST(_name, _flags) \\\n+\tUNIT_TEST(_name, _flags, image_loader)\n+\n+/* Synthetic image size used throughout the tests */\n+#define IMAGE_SIZE\t4096\n+\n+/**\n+ * struct mock_priv - private data for the mock storage backend\n+ *\n+ * @image:\tpointer to synthetic image data in RAM\n+ * @image_size:\tsize of the synthetic image\n+ * @read_count:\tnumber of times .read() was called\n+ * @last_off:\toffset from the most recent .read() call\n+ * @last_size:\tsize from the most recent .read() call\n+ */\n+struct mock_priv {\n+\tconst void *image;\n+\tsize_t image_size;\n+\tint read_count;\n+\tulong last_off;\n+\tulong last_size;\n+};\n+\n+static int mock_read(struct image_loader *ldr, ulong src, ulong size,\n+\t\t     void *dst)\n+{\n+\tstruct mock_priv *p = ldr->priv;\n+\n+\tif (src + size > p->image_size)\n+\t\treturn -EINVAL;\n+\n+\tmemcpy(dst, (const char *)p->image + src, size);\n+\tp->read_count++;\n+\tp->last_off = src;\n+\tp->last_size = size;\n+\n+\treturn 0;\n+}\n+\n+static void mock_cleanup(struct image_loader *ldr)\n+{\n+\t/* Nothing dynamic to free — just verify it's called */\n+}\n+\n+/**\n+ * init_mock_loader() - set up a loader with the mock backend\n+ *\n+ * @ldr:\tloader to initialise\n+ * @priv:\tmock private data (caller-allocated)\n+ * @image:\tsynthetic image buffer\n+ * @image_size:\tsize of @image\n+ * @alloc_base:\tRAM address to use as alloc_ptr base\n+ */\n+static void init_mock_loader(struct image_loader *ldr, struct mock_priv *priv,\n+\t\t\t     const void *image, size_t image_size,\n+\t\t\t     ulong alloc_base)\n+{\n+\tmemset(ldr, 0, sizeof(*ldr));\n+\tmemset(priv, 0, sizeof(*priv));\n+\n+\tpriv->image = image;\n+\tpriv->image_size = image_size;\n+\n+\tldr->read = mock_read;\n+\tldr->cleanup = mock_cleanup;\n+\tldr->priv = priv;\n+\tldr->alloc_ptr = alloc_base;\n+}\n+\n+/* Test: map() allocates, reads and records a region */\n+static int image_loader_test_map_basic(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p;\n+\n+\t/* Fill image with a recognisable pattern */\n+\tfor (int i = 0; i < IMAGE_SIZE; i++)\n+\t\timage[i] = (u8)(i & 0xff);\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Map a 64-byte region at offset 0 */\n+\tp = image_loader_map(&ldr, 0, 64);\n+\tut_assertnonnull(p);\n+\tut_asserteq_mem(image, p, 64);\n+\tut_asserteq(1, mock.read_count);\n+\tut_asserteq(1, ldr.nr_regions);\n+\tut_asserteq(0, (int)ldr.regions[0].img_offset);\n+\tut_asserteq(64, (int)ldr.regions[0].size);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_map_basic, 0);\n+\n+/* Test: map() returns cached pointer for already-mapped range */\n+static int image_loader_test_map_cached(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p1, *p2;\n+\n+\tmemset(image, 0xaa, IMAGE_SIZE);\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\tp1 = image_loader_map(&ldr, 0, 128);\n+\tut_assertnonnull(p1);\n+\tut_asserteq(1, mock.read_count);\n+\n+\t/* Same range — should return same pointer, no new read */\n+\tp2 = image_loader_map(&ldr, 0, 128);\n+\tut_asserteq_ptr(p1, p2);\n+\tut_asserteq(1, mock.read_count);\n+\n+\t/* Subset of the already-mapped range — still cached */\n+\tp2 = image_loader_map(&ldr, 0, 64);\n+\tut_asserteq_ptr(p1, p2);\n+\tut_asserteq(1, mock.read_count);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_map_cached, 0);\n+\n+/* Test: map() returns correct offset within a larger region */\n+static int image_loader_test_map_offset(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p1, *p2;\n+\n+\tfor (int i = 0; i < IMAGE_SIZE; i++)\n+\t\timage[i] = (u8)(i & 0xff);\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Map a 256-byte region starting at offset 0 */\n+\tp1 = image_loader_map(&ldr, 0, 256);\n+\tut_assertnonnull(p1);\n+\n+\t/* Request a sub-range within the previously mapped region */\n+\tp2 = image_loader_map(&ldr, 64, 64);\n+\tut_assertnonnull(p2);\n+\t/* p2 should point 64 bytes into p1 */\n+\tut_asserteq_ptr((char *)p1 + 64, p2);\n+\tut_asserteq_mem(image + 64, p2, 64);\n+\t/* Only one read should have occurred */\n+\tut_asserteq(1, mock.read_count);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_map_offset, 0);\n+\n+/* Test: map() re-reads when extending a region to a larger size */\n+static int image_loader_test_map_extend(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p1, *p2;\n+\n+\tfor (int i = 0; i < IMAGE_SIZE; i++)\n+\t\timage[i] = (u8)(i & 0xff);\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Initial small mapping */\n+\tp1 = image_loader_map(&ldr, 0, 64);\n+\tut_assertnonnull(p1);\n+\tut_asserteq(1, mock.read_count);\n+\n+\t/* Request larger range at same base — should re-read (extend) */\n+\tp2 = image_loader_map(&ldr, 0, 256);\n+\tut_assertnonnull(p2);\n+\tut_asserteq(2, mock.read_count);\n+\tut_asserteq_ptr(p1, p2);\t/* same RAM base */\n+\tut_asserteq_mem(image, p2, 256);\n+\n+\t/* Region count should still be 1 (updated, not added) */\n+\tut_asserteq(1, ldr.nr_regions);\n+\tut_asserteq(256, (int)ldr.regions[0].size);\n+\n+\t/* alloc_ptr must have advanced past the extended region */\n+\tut_assert(ldr.alloc_ptr >= 0x1000000 + 256);\n+\n+\t/*\n+\t * Map a new region after the extend — it must not overlap the\n+\t * extended first region. This is the exact pattern that bit us\n+\t * on real hardware: FIT header at offset 0 extended from 64 to\n+\t * 4096, then kernel payload at offset 4096 was allocated at\n+\t * alloc_ptr that hadn't been advanced, clobbering the header.\n+\t */\n+\t{\n+\t\tvoid *p3 = image_loader_map(&ldr, 256, 128);\n+\n+\t\tut_assertnonnull(p3);\n+\t\tut_asserteq(2, ldr.nr_regions);\n+\t\t/* New region must start at or after the extended region end */\n+\t\tut_assert((ulong)map_to_sysmem(p3) >= 0x1000000 + 256);\n+\t\tut_asserteq_mem(image + 256, p3, 128);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_map_extend, 0);\n+\n+/* Test: map_to() reads to a specified address and records it */\n+static int image_loader_test_map_to(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tu8 dst[256];\n+\tvoid *p;\n+\n+\tfor (int i = 0; i < IMAGE_SIZE; i++)\n+\t\timage[i] = (u8)(i & 0xff);\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\tp = image_loader_map_to(&ldr, 128, 256, dst);\n+\tut_asserteq_ptr(dst, p);\n+\tut_asserteq_mem(image + 128, dst, 256);\n+\tut_asserteq(1, mock.read_count);\n+\tut_asserteq(1, ldr.nr_regions);\n+\tut_asserteq(128, (int)ldr.regions[0].img_offset);\n+\tut_asserteq(256, (int)ldr.regions[0].size);\n+\tut_asserteq_ptr(dst, ldr.regions[0].ram);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_map_to, 0);\n+\n+/* Test: lookup() returns NULL for unmapped ranges */\n+static int image_loader_test_lookup_miss(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p;\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Nothing mapped yet — should return NULL */\n+\tp = image_loader_lookup(&ldr, 0, 64);\n+\tut_assertnull(p);\n+\n+\t/* Map a region at offset 0 */\n+\tut_assertnonnull(image_loader_map(&ldr, 0, 64));\n+\n+\t/* Lookup within the mapped region — should succeed */\n+\tp = image_loader_lookup(&ldr, 0, 32);\n+\tut_assertnonnull(p);\n+\n+\t/* Lookup at a different offset — should miss */\n+\tp = image_loader_lookup(&ldr, 128, 32);\n+\tut_assertnull(p);\n+\n+\t/* Lookup extending beyond the mapped region — should miss */\n+\tp = image_loader_lookup(&ldr, 0, 128);\n+\tut_assertnull(p);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_lookup_miss, 0);\n+\n+/* Test: alloc_ptr advances with correct alignment */\n+static int image_loader_test_alloc_advance(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tulong base = 0x1000000;\n+\tulong expected;\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, base);\n+\n+\t/* Map 100 bytes — alloc_ptr should advance to ALIGN(base + 100) */\n+\tut_assertnonnull(image_loader_map(&ldr, 0, 100));\n+\texpected = ALIGN(base + 100, ARCH_DMA_MINALIGN);\n+\tut_asserteq(expected, ldr.alloc_ptr);\n+\n+\t/* Map another 200 bytes at a different offset */\n+\tut_assertnonnull(image_loader_map(&ldr, 200, 200));\n+\texpected = ALIGN(expected + 200, ARCH_DMA_MINALIGN);\n+\tut_asserteq(expected, ldr.alloc_ptr);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_alloc_advance, 0);\n+\n+/* Test: map() returns NULL when the translation table is full */\n+static int image_loader_test_table_full(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p;\n+\tint i;\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Fill all region slots with distinct offsets */\n+\tfor (i = 0; i < CONFIG_IMAGE_LOADER_MAX_REGIONS; i++) {\n+\t\tp = image_loader_map(&ldr, i * 16, 8);\n+\t\tut_assertnonnull(p);\n+\t}\n+\n+\tut_asserteq(CONFIG_IMAGE_LOADER_MAX_REGIONS, ldr.nr_regions);\n+\n+\t/* Next map at a new offset should fail (table full) */\n+\tp = image_loader_map(&ldr, CONFIG_IMAGE_LOADER_MAX_REGIONS * 16, 8);\n+\tut_assertnull(p);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_table_full, 0);\n+\n+/* Test: cleanup() calls the backend and resets state */\n+static int image_loader_test_cleanup(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Map something so nr_regions > 0 */\n+\tut_assertnonnull(image_loader_map(&ldr, 0, 64));\n+\tut_asserteq(1, ldr.nr_regions);\n+\n+\timage_loader_cleanup(&ldr);\n+\n+\tut_assertnull(ldr.read);\n+\tut_assertnull(ldr.cleanup);\n+\tut_assertnull(ldr.priv);\n+\tut_asserteq(0, ldr.nr_regions);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_cleanup, 0);\n+\n+/* Test: map() with multiple disjoint regions */\n+static int image_loader_test_multi_region(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p1, *p2, *p3;\n+\n+\tfor (int i = 0; i < IMAGE_SIZE; i++)\n+\t\timage[i] = (u8)(i & 0xff);\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\tp1 = image_loader_map(&ldr, 0, 64);\n+\tut_assertnonnull(p1);\n+\tp2 = image_loader_map(&ldr, 512, 128);\n+\tut_assertnonnull(p2);\n+\tp3 = image_loader_map(&ldr, 1024, 256);\n+\tut_assertnonnull(p3);\n+\n+\tut_asserteq(3, ldr.nr_regions);\n+\tut_asserteq(3, mock.read_count);\n+\n+\t/* Verify data in each region */\n+\tut_asserteq_mem(image, p1, 64);\n+\tut_asserteq_mem(image + 512, p2, 128);\n+\tut_asserteq_mem(image + 1024, p3, 256);\n+\n+\t/* Lookup each region */\n+\tut_asserteq_ptr(p1, image_loader_lookup(&ldr, 0, 64));\n+\tut_asserteq_ptr(p2, image_loader_lookup(&ldr, 512, 128));\n+\tut_asserteq_ptr(p3, image_loader_lookup(&ldr, 1024, 256));\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_multi_region, 0);\n+\n+/* Test: read beyond image size returns error */\n+static int image_loader_test_read_oob(struct unit_test_state *uts)\n+{\n+\tstruct image_loader ldr;\n+\tstruct mock_priv mock;\n+\tu8 image[IMAGE_SIZE];\n+\tvoid *p;\n+\n+\tinit_mock_loader(&ldr, &mock, image, IMAGE_SIZE, 0x1000000);\n+\n+\t/* Attempt to map beyond the end of the image */\n+\tp = image_loader_map(&ldr, IMAGE_SIZE - 32, 64);\n+\tut_assertnull(p);\n+\n+\t/* map_to should also fail */\n+\tu8 dst[64];\n+\n+\tp = image_loader_map_to(&ldr, IMAGE_SIZE - 32, 64, dst);\n+\tut_assertnull(p);\n+\n+\treturn 0;\n+}\n+\n+IMG_LOADER_TEST(image_loader_test_read_oob, 0);\ndiff --git a/test/cmd_ut.c b/test/cmd_ut.c\nindex 44e5fdfdaa6..3b907a12e4e 100644\n--- a/test/cmd_ut.c\n+++ b/test/cmd_ut.c\n@@ -71,6 +71,7 @@ SUITE_DECL(optee);\n SUITE_DECL(pci_mps);\n SUITE_DECL(seama);\n SUITE_DECL(setexpr);\n+SUITE_DECL(image_loader);\n SUITE_DECL(upl);\n \n static struct suite suites[] = {\n@@ -98,6 +99,7 @@ static struct suite suites[] = {\n \tSUITE(pci_mps, \"PCI Express Maximum Payload Size\"),\n \tSUITE(seama, \"seama command parameters loading and decoding\"),\n \tSUITE(setexpr, \"setexpr command\"),\n+\tSUITE(image_loader, \"image_loader on-demand storage loading\"),\n \tSUITE(upl, \"Universal payload support\"),\n };\n \n",
    "prefixes": [
        "RFC",
        "11/20"
    ]
}