From nobody Mon Sep 16 19:03:06 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) client-ip=66.175.222.108; envelope-from=bounce+27952+104930+1787277+3901457@groups.io; helo=mail02.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+104930+1787277+3901457@groups.io; dmarc=fail(p=none dis=none) header.from=intel.com ARC-Seal: i=1; a=rsa-sha256; t=1684231236; cv=none; d=zohomail.com; s=zohoarc; b=d54MhEWqpbH4nOBR5H8WS+AMBlVfyGSNPG7E9abk1SbdXRf3LhpY8mQMXcXAuftafHM5sAfEJ0A7CS7UsndgmV58Z01JeXzdrjFvdrW6+S3BCxeXKpwcsPomp0bH2R9o+nzvohjIOKbm82FGahLuw7qAsPASpHjjuNTLzslhTEk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1684231236; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Id:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=03kZ0Lfa+fquchFPjwcFMmbl5F6vlcKXOVntNA2UMfE=; b=JEYQ30rFbgt/7HGDep8+HP76288koupS0haTTLUNKstDLmDliZ+GKkETsfJWVJX7oLM/TgqbCBHiI1kwGd2Q/uPseXixEjX59IFaDvIr8xKwplybAmVDNGb3skugCjQbblwkDXcfg4XiBOFGlKVq4o4RZcCwWzO5Ifv6DHURn2k= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+104930+1787277+3901457@groups.io; dmarc=fail header.from= (p=none dis=none) Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by mx.zohomail.com with SMTPS id 168423123676523.177917594823498; Tue, 16 May 2023 03:00:36 -0700 (PDT) Return-Path: X-Received: by 127.0.0.2 with SMTP id 5p2RYY1788612xaNrCS0yPyH; Tue, 16 May 2023 03:00:36 -0700 X-Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) by mx.groups.io with SMTP id smtpd.web11.17915.1684231210780691710 for ; Tue, 16 May 2023 03:00:35 -0700 X-IronPort-AV: E=McAfee;i="6600,9927,10711"; a="417093323" X-IronPort-AV: E=Sophos;i="5.99,278,1677571200"; d="scan'208";a="417093323" X-Received: from fmsmga003.fm.intel.com ([10.253.24.29]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 May 2023 03:00:35 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10711"; a="791019648" X-IronPort-AV: E=Sophos;i="5.99,278,1677571200"; d="scan'208";a="791019648" X-Received: from shwdeopenlab702.ccr.corp.intel.com ([10.239.55.158]) by fmsmga003-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 16 May 2023 03:00:33 -0700 From: "duntan" To: devel@edk2.groups.io Cc: Eric Dong , Ray Ni , Rahul Kumar , Gerd Hoffmann Subject: [edk2-devel] [Patch V4 10/15] UefiCpuPkg: Add GenSmmPageTable() to create smm page table Date: Tue, 16 May 2023 17:59:27 +0800 Message-Id: <20230516095932.1525-11-dun.tan@intel.com> In-Reply-To: <20230516095932.1525-1-dun.tan@intel.com> References: <20230516095932.1525-1-dun.tan@intel.com> MIME-Version: 1.0 Precedence: Bulk List-Unsubscribe: List-Subscribe: List-Help: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,dun.tan@intel.com X-Gm-Message-State: plnU3wyC02G3o6jHV5g51EyJx1787277AA= Content-Transfer-Encoding: quoted-printable DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=groups.io; q=dns/txt; s=20140610; t=1684231236; bh=3HiKkeuUxFjbV1K0saCHH+/j7AqyLB1uww3eDj+0h1w=; h=Cc:Date:From:Reply-To:Subject:To; b=DlcwErhBQtb7dX193/EKkVzKTHr76hwXkflFt/zaEgk862GgyVvcJ0SWM0Jmi/Oq3d+ 4GmUysTEoMouedudV+qehhT1XHq+SEzOlVJUfKdw8BF7JBvL7teIm65sCAl1D/cxXT6Ti 89Em3wu3xoBXx5YHVbChequ+kzAK+QYKcRw= X-ZohoMail-DKIM: pass (identity @groups.io) X-ZM-MESSAGEID: 1684231238992100027 Content-Type: text/plain; charset="utf-8" This commit is code refinement to current smm pagetable generation code. Add a new GenSmmPageTable() API to create smm page table based on the PageTableMap() API in CpuPageTableLib. Caller only needs to specify the paging mode and the PhysicalAddressBits to map. This function can be used to create both IA32 pae paging and X64 5level, 4level paging. Signed-off-by: Dun Tan Cc: Eric Dong Cc: Ray Ni Cc: Rahul Kumar Cc: Gerd Hoffmann --- UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c | 2 +- UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h | 15 +++++++++++++++ UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c | 65 +++++++++++++++++= ++++++++++++++++++++++++++++++++++++++++++++++++ UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c | 220 +++++++++++++++++= +++++++++------------------------------------------------------------------= ---------------------------------------------------------------------------= ----------------------------------------------------- 4 files changed, 107 insertions(+), 195 deletions(-) diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c b/UefiCpuPkg/PiSmmCpu= DxeSmm/Ia32/PageTbl.c index 9c8107080a..b11264ce4a 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c @@ -63,7 +63,7 @@ SmmInitPageTable ( InitializeIDTSmmStackGuard (); } =20 - return Gen4GPageTable (TRUE); + return GenSmmPageTable (PagingPae, mPhysicalAddressBits); } =20 /** diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h b/UefiCpuPkg/PiSmmC= puDxeSmm/PiSmmCpuDxeSmm.h index a7da9673a5..5399659bc0 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h @@ -553,6 +553,21 @@ Gen4GPageTable ( IN BOOLEAN Is32BitPageTable ); =20 +/** + Create page table based on input PagingMode and PhysicalAddressBits in s= mm. + + @param[in] PagingMode The paging mode. + @param[in] PhysicalAddressBits The bits of physical address to map. + + @retval PageTable Address + +**/ +UINTN +GenSmmPageTable ( + IN PAGING_MODE PagingMode, + IN UINT8 PhysicalAddressBits + ); + /** Initialize global data for MP synchronization. =20 diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c b/UefiCpuPk= g/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c index ef0ba9a355..138ff43c9d 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c @@ -1642,6 +1642,71 @@ EdkiiSmmClearMemoryAttributes ( return SmmClearMemoryAttributes (BaseAddress, Length, Attributes); } =20 +/** + Create page table based on input PagingMode and PhysicalAddressBits in s= mm. + + @param[in] PagingMode The paging mode. + @param[in] PhysicalAddressBits The bits of physical address to map. + + @retval PageTable Address + +**/ +UINTN +GenSmmPageTable ( + IN PAGING_MODE PagingMode, + IN UINT8 PhysicalAddressBits + ) +{ + UINTN PageTableBufferSize; + UINTN PageTable; + VOID *PageTableBuffer; + IA32_MAP_ATTRIBUTE MapAttribute; + IA32_MAP_ATTRIBUTE MapMask; + RETURN_STATUS Status; + UINTN GuardPage; + UINTN Index; + UINT64 Length; + + Length =3D LShiftU64 (1, PhysicalAddressBits); + PageTable =3D 0; + PageTableBufferSize =3D 0; + MapMask.Uint64 =3D MAX_UINT64; + MapAttribute.Uint64 =3D mAddressEncMask; + MapAttribute.Bits.Present =3D 1; + MapAttribute.Bits.ReadWrite =3D 1; + MapAttribute.Bits.UserSupervisor =3D 1; + MapAttribute.Bits.Accessed =3D 1; + MapAttribute.Bits.Dirty =3D 1; + + Status =3D PageTableMap (&PageTable, PagingMode, NULL, &PageTableBufferS= ize, 0, Length, &MapAttribute, &MapMask, NULL); + ASSERT (Status =3D=3D RETURN_BUFFER_TOO_SMALL); + DEBUG ((DEBUG_INFO, "GenSMMPageTable: 0x%x bytes needed for initial SMM = page table\n", PageTableBufferSize)); + PageTableBuffer =3D AllocatePageTableMemory (EFI_SIZE_TO_PAGES (PageTabl= eBufferSize)); + ASSERT (PageTableBuffer !=3D NULL); + Status =3D PageTableMap (&PageTable, PagingMode, PageTableBuffer, &PageT= ableBufferSize, 0, Length, &MapAttribute, &MapMask, NULL); + ASSERT (Status =3D=3D RETURN_SUCCESS); + ASSERT (PageTableBufferSize =3D=3D 0); + + if (FeaturePcdGet (PcdCpuSmmStackGuard)) { + // + // Mark the 4KB guard page between known good stack and smm stack as n= on-present + // + for (Index =3D 0; Index < gSmmCpuPrivate->SmmCoreEntryContext.NumberOf= Cpus; Index++) { + GuardPage =3D mSmmStackArrayBase + EFI_PAGE_SIZE + Index * (mSmmStac= kSize + mSmmShadowStackSize); + Status =3D ConvertMemoryPageAttributes (PageTable, PagingMode, Gu= ardPage, SIZE_4KB, EFI_MEMORY_RP, TRUE, NULL); + } + } + + if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & BIT1) !=3D 0) { + // + // Mark [0, 4k] as non-present + // + Status =3D ConvertMemoryPageAttributes (PageTable, PagingMode, 0, SIZE= _4KB, EFI_MEMORY_RP, TRUE, NULL); + } + + return (UINTN)PageTable; +} + /** This function retrieves the attributes of the memory region specified by BaseAddress and Length. If different attributes are got from different p= art diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c b/UefiCpuPkg/PiSmmCpuD= xeSmm/X64/PageTbl.c index 25ced50955..060e6dc147 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c @@ -167,160 +167,6 @@ CalculateMaximumSupportAddress ( return PhysicalAddressBits; } =20 -/** - Set static page table. - - @param[in] PageTable Address of page table. - @param[in] PhysicalAddressBits The maximum physical address bits supp= orted. -**/ -VOID -SetStaticPageTable ( - IN UINTN PageTable, - IN UINT8 PhysicalAddressBits - ) -{ - UINT64 PageAddress; - UINTN NumberOfPml5EntriesNeeded; - UINTN NumberOfPml4EntriesNeeded; - UINTN NumberOfPdpEntriesNeeded; - UINTN IndexOfPml5Entries; - UINTN IndexOfPml4Entries; - UINTN IndexOfPdpEntries; - UINTN IndexOfPageDirectoryEntries; - UINT64 *PageMapLevel5Entry; - UINT64 *PageMapLevel4Entry; - UINT64 *PageMap; - UINT64 *PageDirectoryPointerEntry; - UINT64 *PageDirectory1GEntry; - UINT64 *PageDirectoryEntry; - - // - // IA-32e paging translates 48-bit linear addresses to 52-bit physical a= ddresses - // when 5-Level Paging is disabled. - // - ASSERT (PhysicalAddressBits <=3D 52); - if (!m5LevelPagingNeeded && (PhysicalAddressBits > 48)) { - PhysicalAddressBits =3D 48; - } - - NumberOfPml5EntriesNeeded =3D 1; - if (PhysicalAddressBits > 48) { - NumberOfPml5EntriesNeeded =3D (UINTN)LShiftU64 (1, PhysicalAddressBits= - 48); - PhysicalAddressBits =3D 48; - } - - NumberOfPml4EntriesNeeded =3D 1; - if (PhysicalAddressBits > 39) { - NumberOfPml4EntriesNeeded =3D (UINTN)LShiftU64 (1, PhysicalAddressBits= - 39); - PhysicalAddressBits =3D 39; - } - - NumberOfPdpEntriesNeeded =3D 1; - ASSERT (PhysicalAddressBits > 30); - NumberOfPdpEntriesNeeded =3D (UINTN)LShiftU64 (1, PhysicalAddressBits - = 30); - - // - // By architecture only one PageMapLevel4 exists - so lets allocate stor= age for it. - // - PageMap =3D (VOID *)PageTable; - - PageMapLevel4Entry =3D PageMap; - PageMapLevel5Entry =3D NULL; - if (m5LevelPagingNeeded) { - // - // By architecture only one PageMapLevel5 exists - so lets allocate st= orage for it. - // - PageMapLevel5Entry =3D PageMap; - } - - PageAddress =3D 0; - - for ( IndexOfPml5Entries =3D 0 - ; IndexOfPml5Entries < NumberOfPml5EntriesNeeded - ; IndexOfPml5Entries++, PageMapLevel5Entry++) - { - // - // Each PML5 entry points to a page of PML4 entires. - // So lets allocate space for them and fill them in in the IndexOfPml4= Entries loop. - // When 5-Level Paging is disabled, below allocation happens only once. - // - if (m5LevelPagingNeeded) { - PageMapLevel4Entry =3D (UINT64 *)((*PageMapLevel5Entry) & ~mAddressE= ncMask & gPhyMask); - if (PageMapLevel4Entry =3D=3D NULL) { - PageMapLevel4Entry =3D AllocatePageTableMemory (1); - ASSERT (PageMapLevel4Entry !=3D NULL); - ZeroMem (PageMapLevel4Entry, EFI_PAGES_TO_SIZE (1)); - - *PageMapLevel5Entry =3D (UINT64)(UINTN)PageMapLevel4Entry | mAddre= ssEncMask | PAGE_ATTRIBUTE_BITS; - } - } - - for (IndexOfPml4Entries =3D 0; IndexOfPml4Entries < (NumberOfPml5Entri= esNeeded =3D=3D 1 ? NumberOfPml4EntriesNeeded : 512); IndexOfPml4Entries++,= PageMapLevel4Entry++) { - // - // Each PML4 entry points to a page of Page Directory Pointer entrie= s. - // - PageDirectoryPointerEntry =3D (UINT64 *)((*PageMapLevel4Entry) & ~mA= ddressEncMask & gPhyMask); - if (PageDirectoryPointerEntry =3D=3D NULL) { - PageDirectoryPointerEntry =3D AllocatePageTableMemory (1); - ASSERT (PageDirectoryPointerEntry !=3D NULL); - ZeroMem (PageDirectoryPointerEntry, EFI_PAGES_TO_SIZE (1)); - - *PageMapLevel4Entry =3D (UINT64)(UINTN)PageDirectoryPointerEntry |= mAddressEncMask | PAGE_ATTRIBUTE_BITS; - } - - if (m1GPageTableSupport) { - PageDirectory1GEntry =3D PageDirectoryPointerEntry; - for (IndexOfPageDirectoryEntries =3D 0; IndexOfPageDirectoryEntrie= s < 512; IndexOfPageDirectoryEntries++, PageDirectory1GEntry++, PageAddress= +=3D SIZE_1GB) { - if ((IndexOfPml4Entries =3D=3D 0) && (IndexOfPageDirectoryEntrie= s < 4)) { - // - // Skip the < 4G entries - // - continue; - } - - // - // Fill in the Page Directory entries - // - *PageDirectory1GEntry =3D PageAddress | mAddressEncMask | IA32_P= G_PS | PAGE_ATTRIBUTE_BITS; - } - } else { - PageAddress =3D BASE_4GB; - for (IndexOfPdpEntries =3D 0; IndexOfPdpEntries < (NumberOfPml4Ent= riesNeeded =3D=3D 1 ? NumberOfPdpEntriesNeeded : 512); IndexOfPdpEntries++,= PageDirectoryPointerEntry++) { - if ((IndexOfPml4Entries =3D=3D 0) && (IndexOfPdpEntries < 4)) { - // - // Skip the < 4G entries - // - continue; - } - - // - // Each Directory Pointer entries points to a page of Page Direc= tory entires. - // So allocate space for them and fill them in in the IndexOfPag= eDirectoryEntries loop. - // - PageDirectoryEntry =3D (UINT64 *)((*PageDirectoryPointerEntry) &= ~mAddressEncMask & gPhyMask); - if (PageDirectoryEntry =3D=3D NULL) { - PageDirectoryEntry =3D AllocatePageTableMemory (1); - ASSERT (PageDirectoryEntry !=3D NULL); - ZeroMem (PageDirectoryEntry, EFI_PAGES_TO_SIZE (1)); - - // - // Fill in a Page Directory Pointer Entries - // - *PageDirectoryPointerEntry =3D (UINT64)(UINTN)PageDirectoryEnt= ry | mAddressEncMask | PAGE_ATTRIBUTE_BITS; - } - - for (IndexOfPageDirectoryEntries =3D 0; IndexOfPageDirectoryEntr= ies < 512; IndexOfPageDirectoryEntries++, PageDirectoryEntry++, PageAddress= +=3D SIZE_2MB) { - // - // Fill in the Page Directory entries - // - *PageDirectoryEntry =3D PageAddress | mAddressEncMask | IA32_P= G_PS | PAGE_ATTRIBUTE_BITS; - } - } - } - } - } -} - /** Create PageTable for SMM use. =20 @@ -332,15 +178,16 @@ SmmInitPageTable ( VOID ) { - EFI_PHYSICAL_ADDRESS Pages; - UINT64 *PTEntry; + UINTN PageTable; LIST_ENTRY *FreePage; UINTN Index; UINTN PageFaultHandlerHookAddress; IA32_IDT_GATE_DESCRIPTOR *IdtEntry; EFI_STATUS Status; + UINT64 *PdptEntry; UINT64 *Pml4Entry; UINT64 *Pml5Entry; + UINT8 PhysicalAddressBits; =20 // // Initialize spin lock @@ -357,59 +204,44 @@ SmmInitPageTable ( } else { mPagingMode =3D m1GPageTableSupport ? Paging4Level1GB : Paging4Level; } + DEBUG ((DEBUG_INFO, "5LevelPaging Needed - %d\n", m5LevelPag= ingNeeded)); DEBUG ((DEBUG_INFO, "1GPageTable Support - %d\n", m1GPageTab= leSupport)); DEBUG ((DEBUG_INFO, "PcdCpuSmmRestrictedMemoryAccess - %d\n", mCpuSmmRes= trictedMemoryAccess)); DEBUG ((DEBUG_INFO, "PhysicalAddressBits - %d\n", mPhysicalA= ddressBits)); - // - // Generate PAE page table for the first 4GB memory space - // - Pages =3D Gen4GPageTable (FALSE); =20 // - // Set IA32_PG_PMNT bit to mask this entry + // Generate initial SMM page table. + // Only map [0, 4G] when PcdCpuSmmRestrictedMemoryAccess is FALSE. // - PTEntry =3D (UINT64 *)(UINTN)Pages; - for (Index =3D 0; Index < 4; Index++) { - PTEntry[Index] |=3D IA32_PG_PMNT; - } - - // - // Fill Page-Table-Level4 (PML4) entry - // - Pml4Entry =3D (UINT64 *)AllocatePageTableMemory (1); - ASSERT (Pml4Entry !=3D NULL); - *Pml4Entry =3D Pages | mAddressEncMask | PAGE_ATTRIBUTE_BITS; - ZeroMem (Pml4Entry + 1, EFI_PAGE_SIZE - sizeof (*Pml4Entry)); - - // - // Set sub-entries number - // - SetSubEntriesNum (Pml4Entry, 3); - PTEntry =3D Pml4Entry; + PhysicalAddressBits =3D mCpuSmmRestrictedMemoryAccess ? mPhysicalAddress= Bits : 32; + PageTable =3D GenSmmPageTable (mPagingMode, PhysicalAddressBit= s); =20 if (m5LevelPagingNeeded) { + Pml5Entry =3D (UINT64 *)PageTable; // - // Fill PML5 entry - // - Pml5Entry =3D (UINT64 *)AllocatePageTableMemory (1); - ASSERT (Pml5Entry !=3D NULL); - *Pml5Entry =3D (UINTN)Pml4Entry | mAddressEncMask | PAGE_ATTRIBUTE_BIT= S; - ZeroMem (Pml5Entry + 1, EFI_PAGE_SIZE - sizeof (*Pml5Entry)); - // - // Set sub-entries number + // Set Pml5Entry sub-entries number for smm PF handler usage. // SetSubEntriesNum (Pml5Entry, 1); - PTEntry =3D Pml5Entry; + Pml4Entry =3D (UINT64 *)((*Pml5Entry) & ~mAddressEncMask & gPhyMask); + } else { + Pml4Entry =3D (UINT64 *)PageTable; + } + + // + // Set IA32_PG_PMNT bit to mask first 4 PdptEntry. + // + PdptEntry =3D (UINT64 *)((*Pml4Entry) & ~mAddressEncMask & gPhyMask); + for (Index =3D 0; Index < 4; Index++) { + PdptEntry[Index] |=3D IA32_PG_PMNT; } =20 - if (mCpuSmmRestrictedMemoryAccess) { + if (!mCpuSmmRestrictedMemoryAccess) { // - // When access to non-SMRAM memory is restricted, create page table - // that covers all memory space. + // Set Pml4Entry sub-entries number for smm PF handler usage. // - SetStaticPageTable ((UINTN)PTEntry, mPhysicalAddressBits); - } else { + SetSubEntriesNum (Pml4Entry, 3); + // // Add pages to page pool // @@ -466,7 +298,7 @@ SmmInitPageTable ( // // Return the address of PML4/PML5 (to set CR3) // - return (UINT32)(UINTN)PTEntry; + return (UINT32)PageTable; } =20 /** --=20 2.31.1.windows.1 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#104930): https://edk2.groups.io/g/devel/message/104930 Mute This Topic: https://groups.io/mt/98922936/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-