Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2217905/?format=api
{ "id": 2217905, "url": "http://patchwork.ozlabs.org/api/patches/2217905/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260330212817.992673-2-alejandro.j.jimenez@oracle.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260330212817.992673-2-alejandro.j.jimenez@oracle.com>", "list_archive_url": null, "date": "2026-03-30T21:28:16", "name": "[for-11.0,1/2] amd_iommu: Follow root pointer before page walk and use 1-based levels", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "7a5d200ef126b6fc2a1e9b3b265979ee05fd48bb", "submitter": { "id": 80257, "url": "http://patchwork.ozlabs.org/api/people/80257/?format=api", "name": "Alejandro Jimenez", "email": "alejandro.j.jimenez@oracle.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260330212817.992673-2-alejandro.j.jimenez@oracle.com/mbox/", "series": [ { "id": 498099, "url": "http://patchwork.ozlabs.org/api/series/498099/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=498099", "date": "2026-03-30T21:28:15", "name": "[for-11.0,1/2] amd_iommu: Follow root pointer before page walk and use 1-based levels", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/498099/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2217905/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2217905/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=oracle.com header.i=@oracle.com header.a=rsa-sha256\n header.s=corp-2025-04-25 header.b=Iqb5o+x3;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fl4Bp5TZcz1yGT\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 31 Mar 2026 08:29:21 +1100 (AEDT)", "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1w7K9z-0007u7-GX; Mon, 30 Mar 2026 17:28:35 -0400", "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <alejandro.j.jimenez@oracle.com>)\n id 1w7K9v-0007tb-Dy\n for qemu-devel@nongnu.org; Mon, 30 Mar 2026 17:28:31 -0400", "from mx0a-00069f02.pphosted.com ([205.220.165.32])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <alejandro.j.jimenez@oracle.com>)\n id 1w7K9t-0006FH-47\n for qemu-devel@nongnu.org; Mon, 30 Mar 2026 17:28:31 -0400", "from pps.filterd (m0246627.ppops.net [127.0.0.1])\n by mx0b-00069f02.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n 62UDE1SC2001947; Mon, 30 Mar 2026 21:28:20 GMT", "from iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com\n (iadpaimrmta03.appoci.oracle.com [130.35.103.27])\n by mx0b-00069f02.pphosted.com (PPS) with ESMTPS id 4d65jwb49g-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK);\n Mon, 30 Mar 2026 21:28:19 +0000 (GMT)", "from pps.filterd\n (iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com [127.0.0.1])\n by iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com (8.18.1.2/8.18.1.2)\n with ESMTP id 62UKMWet036408; Mon, 30 Mar 2026 21:28:18 GMT", "from pps.reinject (localhost [127.0.0.1])\n by iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com (PPS) with ESMTPS id\n 4d65efn542-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK);\n Mon, 30 Mar 2026 21:28:18 +0000", "from iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com\n (iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com [127.0.0.1])\n by pps.reinject (8.17.1.5/8.17.1.5) with ESMTP id 62ULSISx018888;\n Mon, 30 Mar 2026 21:28:18 GMT", "from alaljime-e5-test-20240903-1847.osdevelopmeniad.oraclevcn.com\n (alaljime-e5-test-20240903-1847.allregionaliads.osdevelopmeniad.oraclevcn.com\n [100.100.250.206])\n by iadpaimrmta03.imrmtpd1.prodappiadaev1.oraclevcn.com (PPS) with ESMTP id\n 4d65efn53s-2; Mon, 30 Mar 2026 21:28:18 +0000" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=oracle.com; h=cc\n :content-transfer-encoding:date:from:in-reply-to:message-id\n :mime-version:references:subject:to; s=corp-2025-04-25; bh=fttLD\n iltSJACdB2XElhG21tLldLlYiWL3RwDQ4QzDkk=; b=Iqb5o+x32+ZtOysTEDix8\n fr0xngdD7y8xqdDGbmJK6UUgapsY5LpyvtqQeFD1ghFBB7cs4EykxKOQuMl/7YRx\n P/2r2UxYkF+vZpAqKnpCFDOpdcj/4lnko0W6518zghtAsyz0og7eVeOLCOop00Qq\n lJqR0MFz8w5LRydm9KFN6mFAV/OOiNu2upQOAb4Ld0SRAIkAQ24wJt6sRHtxEtSS\n ykWcTtnufap8Xp7WQZDkLo9xTuXXSQBP2Eq4gf6slG3KTcaoQOmBkgM7qgEnhE1S\n gINGztUg+RvY6wIgjrnow4LlljB4jD3cZnxo244MIjNGaLwWpMNzApi9Eg/CRqgl\n w==", "From": "Alejandro Jimenez <alejandro.j.jimenez@oracle.com>", "To": "mst@redhat.com, sarunkod@amd.com, qemu@demindiro.com,\n qemu-devel@nongnu.org", "Cc": "alejandro.j.jimenez@oracle.com", "Subject": "[PATCH for-11.0 1/2] amd_iommu: Follow root pointer before page walk\n and use 1-based levels", "Date": "Mon, 30 Mar 2026 21:28:16 +0000", "Message-ID": "<20260330212817.992673-2-alejandro.j.jimenez@oracle.com>", "X-Mailer": "git-send-email 2.47.3", "In-Reply-To": "<20260330212817.992673-1-alejandro.j.jimenez@oracle.com>", "References": "<20260330212817.992673-1-alejandro.j.jimenez@oracle.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-Proofpoint-Virus-Version": "vendor=baseguard\n engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.51,FMLib:17.12.100.49\n definitions=2026-03-30_01,2026-03-28_01,2025-10-01_01", "X-Proofpoint-Spam-Details": "rule=notspam policy=default score=0 mlxlogscore=999\n mlxscore=0 adultscore=0\n bulkscore=0 phishscore=0 suspectscore=0 malwarescore=0 spamscore=0\n classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2603050001\n definitions=main-2603300182", "X-Proofpoint-Spam-Details-Enc": "AW1haW4tMjYwMzMwMDE4MiBTYWx0ZWRfX4lfd3vr1Cu7a\n NCX++UwamtEYjHszDFNoWdTeIIqN+mNev04+L0px5/JUQlAPLy1UYadhw10uLPr1KYT/jDElLuZ\n QgGEYYvvt4du/mGpUeyewEqL3fpzBlYMuv/UmcKDnvEMZ1RmkEp21L79AIJns34oeBBfm7x0GjN\n hEbirl+kgwcjAh96ztkqpFPsIBbBN7opSEt3mHIiXEof7HLDl1JTabq93Y/TI8ea347qU3X4irN\n O7Kcf8CgjhcIFjj+dCF/e6+95ftWLULQT8PUfWQraI3phoif/gHuUAXiD1inYLT4imAoMW6qkOB\n TV7EyaQwScSXqhCVtQSWtNPhxpR1G0yHPgcpwHqgGr08rMVTApdetJ5wlxbFxxaiFNeosVGIEOA\n MtxaSF8k5TA537qK7ehHNzTvHxwzzjM8Gv+GecwiojFNLo0aM2AMveOIzzeAl3adrfEWG5P1Fjt\n zxCxVlD8DiYJgOyAVEZ4kpFuQ1wkSYlU9oK50L80=", "X-Authority-Analysis": "v=2.4 cv=CJEnnBrD c=1 sm=1 tr=0 ts=69caeaf4 b=1 cx=c_pps\n a=qoll8+KPOyaMroiJ2sR5sw==:117\n a=qoll8+KPOyaMroiJ2sR5sw==:17\n a=Yq5XynenixoA:10 a=VkNPw1HP01LnGYTKEx00:22 a=jiCTI4zE5U7BLdzWsZGv:22\n a=RD47p0oAkeU5bO7t-o6f:22 a=69wJf7TsAAAA:8 a=RWxpEp7VAAAA:8 a=zd2uoN0lAAAA:8\n a=yPCof4ZbAAAA:8 a=y5qzoqqGynPG9JP8Bm8A:9 a=Fg1AiH1G6rFz08G2ETeA:22\n a=3unh6Pbajv6CBnL1DxgC:22 cc=ntf awl=host:12276", "X-Proofpoint-ORIG-GUID": "MQM5MYGdW0N_JXlyihtPmIxgzxBW580P", "X-Proofpoint-GUID": "MQM5MYGdW0N_JXlyihtPmIxgzxBW580P", "Received-SPF": "pass client-ip=205.220.165.32;\n envelope-from=alejandro.j.jimenez@oracle.com; helo=mx0a-00069f02.pphosted.com", "X-Spam_score_int": "-7", "X-Spam_score": "-0.8", "X-Spam_bar": "/", "X-Spam_report": "(-0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001,\n DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=-0.01, RCVD_IN_MSPIKE_WL=-0.01,\n RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=1, RCVD_IN_VALIDITY_RPBL_BLOCKED=1,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no", "X-Spam_action": "no action", "X-BeenThere": "qemu-devel@nongnu.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "qemu development <qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org" }, "content": "DTE[Mode] and PTE NextLevel encode page table levels as 1-based values, but\nfetch_pte() currently uses a 0-based level counter, making the logic\nharder to follow and requiring conversions between DTE mode and level.\n\nSwitch the page table walk logic to use 1-based level accounting in\nfetch_pte() and the relevant macro helpers. To further simplify the page\nwalking loop, split the root page table access from the walk i.e. rework\nfetch_pte() to follow the DTE Page Table Root Pointer and retrieve the top\nlevel pagetable entry before entering the loop, then iterate only over the\nPDE/PTE entries.\n\nThe reworked algorithm fixes a page walk bug where the page size was\ncalculated for the next level before checking if the current PTE was already\na leaf/hugepage. That caused hugepage mappings to be reported as 4K pages,\nleading to performance degradation and failures in some setups.\n\nFixes: a74bb3110a5b (\"amd_iommu: Add helpers to walk AMD v1 Page Table format\")\nCc: qemu-stable@nongnu.org\nReported-by: David Hoppenbrouwers <qemu@demindiro.com>\nReviewed-By: David Hoppenbrouwers <qemu@demindiro.com>\nReviewed-by: Sairaj Kodilkar <sarunkod@amd.com>\nSigned-off-by: Alejandro Jimenez <alejandro.j.jimenez@oracle.com>\n---\n hw/i386/amd_iommu.c | 132 ++++++++++++++++++++++++++++++--------------\n hw/i386/amd_iommu.h | 11 ++--\n 2 files changed, 97 insertions(+), 46 deletions(-)", "diff": "diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c\nindex 789e09d6f2..04acfa645f 100644\n--- a/hw/i386/amd_iommu.c\n+++ b/hw/i386/amd_iommu.c\n@@ -648,6 +648,52 @@ static uint64_t large_pte_page_size(uint64_t pte)\n return PTE_LARGE_PAGE_SIZE(pte);\n }\n \n+/*\n+ * Validate DTE fields and extract permissions and top level data required to\n+ * initiate the page table walk.\n+ *\n+ * On success, returns 0 and stores:\n+ * - top_level: highest page-table level encoded in DTE[Mode]\n+ * - dte_perms: effective permissions from the DTE\n+ *\n+ * On failure, returns -AMDVI_FR_PT_ROOT_INV. This includes cases where:\n+ * - DTE permissions disallow read AND write\n+ * - DTE[Mode] is invalid for translation\n+ * - IOVA exceeds the address width supported by DTE[Mode]\n+ * In all such cases a page walk must be aborted.\n+ */\n+static uint64_t amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte,\n+ uint8_t *top_level,\n+ IOMMUAccessFlags *dte_perms)\n+{\n+ *dte_perms = amdvi_get_perms(dte);\n+ if (*dte_perms == IOMMU_NONE) {\n+ return -AMDVI_FR_PT_ROOT_INV;\n+ }\n+\n+ /* Verifying a valid mode is encoded in DTE */\n+ *top_level = get_pte_translation_mode(dte);\n+\n+ /*\n+ * Page Table Root pointer is only valid for GPA->SPA translation on\n+ * supported modes.\n+ */\n+ if (*top_level == 0 || *top_level > 6) {\n+ return -AMDVI_FR_PT_ROOT_INV;\n+ }\n+\n+ /*\n+ * If IOVA is larger than the max supported by the highest pgtable level,\n+ * there is nothing to do.\n+ */\n+ if (address > PT_LEVEL_MAX_ADDR(*top_level)) {\n+ /* IOVA too large for the current DTE */\n+ return -AMDVI_FR_PT_ROOT_INV;\n+ }\n+\n+ return 0;\n+}\n+\n /*\n * Helper function to fetch a PTE using AMD v1 pgtable format.\n * On successful page walk, returns 0 and pte parameter points to a valid PTE.\n@@ -662,40 +708,49 @@ static uint64_t large_pte_page_size(uint64_t pte)\n static uint64_t fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte,\n uint64_t *pte, hwaddr *page_size)\n {\n- IOMMUAccessFlags perms = amdvi_get_perms(dte);\n-\n- uint8_t level, mode;\n uint64_t pte_addr;\n+ uint8_t pt_level, next_pt_level;\n+ IOMMUAccessFlags perms;\n+ int ret;\n \n- *pte = dte;\n *page_size = 0;\n \n- if (perms == IOMMU_NONE) {\n- return -AMDVI_FR_PT_ROOT_INV;\n- }\n-\n /*\n- * The Linux kernel driver initializes the default mode to 3, corresponding\n- * to a 39-bit GPA space, where each entry in the pagetable translates to a\n- * 1GB (2^30) page size.\n+ * Verify the DTE is properly configured before page walk, and extract\n+ * top pagetable level and permissions.\n */\n- level = mode = get_pte_translation_mode(dte);\n- assert(mode > 0 && mode < 7);\n+ ret = amdvi_get_top_pt_level_and_perms(address, dte, &pt_level, &perms);\n+ if (ret < 0) {\n+ return ret;\n+ }\n \n /*\n- * If IOVA is larger than the max supported by the current pgtable level,\n- * there is nothing to do.\n+ * Retrieve the top pagetable entry by following the DTE Page Table Root\n+ * Pointer and indexing the top level table using the IOVA from the request.\n */\n- if (address > PT_LEVEL_MAX_ADDR(mode - 1)) {\n- /* IOVA too large for the current DTE */\n+ pte_addr = NEXT_PTE_ADDR(dte, pt_level, address);\n+ *pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);\n+\n+ if (*pte == (uint64_t)-1) {\n+ /*\n+ * A returned PTE of -1 here indicates a failure to read the top level\n+ * page table from guest memory. A page walk is not possible and page\n+ * size must be returned as 0.\n+ */\n return -AMDVI_FR_PT_ROOT_INV;\n }\n \n- do {\n- level -= 1;\n+ /*\n+ * Calculate page size for the top level page table entry.\n+ * This ensures correct results for a single level Page Table setup.\n+ */\n+ *page_size = PTE_LEVEL_PAGE_SIZE(pt_level);\n \n- /* Update the page_size */\n- *page_size = PTE_LEVEL_PAGE_SIZE(level);\n+ /*\n+ * The root page table entry and its level have been determined. Begin the\n+ * page walk.\n+ */\n+ while (pt_level > 0) {\n \n /* Permission bits are ANDed at every level, including the DTE */\n perms &= amdvi_get_perms(*pte);\n@@ -708,37 +763,34 @@ static uint64_t fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte,\n return 0;\n }\n \n+ next_pt_level = PTE_NEXT_LEVEL(*pte);\n+\n /* Large or Leaf PTE found */\n- if (PTE_NEXT_LEVEL(*pte) == 7 || PTE_NEXT_LEVEL(*pte) == 0) {\n+ if (next_pt_level == 0 || next_pt_level == 7) {\n /* Leaf PTE found */\n break;\n }\n \n+ pt_level = next_pt_level;\n+\n /*\n- * Index the pgtable using the IOVA bits corresponding to current level\n- * and walk down to the lower level.\n+ * The current entry is a Page Directory Entry. Descend to the lower\n+ * page table level encoded in current pte, and index the new table\n+ * using the appropriate IOVA bits to retrieve the new entry.\n */\n- pte_addr = NEXT_PTE_ADDR(*pte, level, address);\n+ *page_size = PTE_LEVEL_PAGE_SIZE(pt_level);\n+\n+ pte_addr = NEXT_PTE_ADDR(*pte, pt_level, address);\n *pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);\n \n if (*pte == (uint64_t)-1) {\n- /*\n- * A returned PTE of -1 indicates a failure to read the page table\n- * entry from guest memory.\n- */\n- if (level == mode - 1) {\n- /* Failure to retrieve the Page Table from Root Pointer */\n- *page_size = 0;\n- return -AMDVI_FR_PT_ROOT_INV;\n- } else {\n- /* Failure to read PTE. Page walk skips a page_size chunk */\n- return -AMDVI_FR_PT_ENTRY_INV;\n- }\n+ /* Failure to read PTE. Page walk skips a page_size chunk */\n+ return -AMDVI_FR_PT_ENTRY_INV;\n }\n- } while (level > 0);\n+ }\n+\n+ assert(PTE_NEXT_LEVEL(*pte) == 0 || PTE_NEXT_LEVEL(*pte) == 7);\n \n- assert(PTE_NEXT_LEVEL(*pte) == 0 || PTE_NEXT_LEVEL(*pte) == 7 ||\n- level == 0);\n /*\n * Page walk ends when Next Level field on PTE shows that either a leaf PTE\n * or a series of large PTEs have been reached. In the latter case, even if\ndiff --git a/hw/i386/amd_iommu.h b/hw/i386/amd_iommu.h\nindex 302ccca512..7af3c742b7 100644\n--- a/hw/i386/amd_iommu.h\n+++ b/hw/i386/amd_iommu.h\n@@ -186,17 +186,16 @@\n \n #define IOMMU_PTE_PRESENT(pte) ((pte) & AMDVI_PTE_PR)\n \n-/* Using level=0 for leaf PTE at 4K page size */\n-#define PT_LEVEL_SHIFT(level) (12 + ((level) * 9))\n+/* Using level=1 for leaf PTE at 4K page size */\n+#define PT_LEVEL_SHIFT(level) (12 + (((level) - 1) * 9))\n \n /* Return IOVA bit group used to index the Page Table at specific level */\n #define PT_LEVEL_INDEX(level, iova) (((iova) >> PT_LEVEL_SHIFT(level)) & \\\n GENMASK64(8, 0))\n \n-/* Return the max address for a specified level i.e. max_oaddr */\n-#define PT_LEVEL_MAX_ADDR(x) (((x) < 5) ? \\\n- ((1ULL << PT_LEVEL_SHIFT((x + 1))) - 1) : \\\n- (~(0ULL)))\n+/* Return the maximum output address for a specified page table level */\n+#define PT_LEVEL_MAX_ADDR(level) (((level) > 5) ? (~(0ULL)) : \\\n+ ((1ULL << PT_LEVEL_SHIFT((level) + 1)) - 1))\n \n /* Extract the NextLevel field from PTE/PDE */\n #define PTE_NEXT_LEVEL(pte) (((pte) & AMDVI_PTE_NEXT_LEVEL_MASK) >> 9)\n", "prefixes": [ "for-11.0", "1/2" ] }