From nobody Thu Apr 2 06:27:19 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4F88C27057D for ; Mon, 9 Mar 2026 02:57:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773025056; cv=none; b=W7SyvBsNOVx0TbkXDk1K6mbR3vopgIfMr8q+uAIaYO2tbNzZwzKnXJorLDq+71Q0u1Omg0yl2CPEXPSwoGvbe8BwK9tA90tDhe9OIyj+Inycw44y7c2ug6W6qsMRmUyyXax6Zx27z2TryLjO53xYOP7J+QYtNnMya87DgTzTuYw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773025056; c=relaxed/simple; bh=t/rVXxvWOVPBnlSxuySiGUDxh4xnB+eVLNK03O+zBsE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=uN2wtpRY5YctuD2pz/uq63ENbFzFuMDDXAC3dAYb0VKL5kcRY1qLoDslYVSnLjevtYyHD8Z+dvnKU7KgDgXP2cm08i1VtX+MtJBntD9zY4x8DwbxkNy913/G4lRICTZbZAXOY8VGr3rQCGsXS2Ycdmw4SvcOu5vDUUm3gpvi+uo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 89E5B1D6F; Sun, 8 Mar 2026 19:57:28 -0700 (PDT) Received: from ergosum.cambridge.arm.com (ergosum.cambridge.arm.com [10.1.196.45]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 69BF73F694; Sun, 8 Mar 2026 19:57:33 -0700 (PDT) From: Anshuman Khandual To: linux-arm-kernel@lists.infradead.org Cc: Anshuman Khandual , Catalin Marinas , Will Deacon , Ryan Roberts , David Hildenbrand , Yang Shi , Christoph Lameter , linux-kernel@vger.kernel.org Subject: [PATCH V5 2/2] arm64/mm: Reject memory removal that splits a kernel leaf mapping Date: Mon, 9 Mar 2026 02:57:25 +0000 Message-Id: <20260309025725.455004-3-anshuman.khandual@arm.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20260309025725.455004-1-anshuman.khandual@arm.com> References: <20260309025725.455004-1-anshuman.khandual@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Linear and vmemmap mappings that get torn down during a memory hot remove operation might contain leaf level entries on any page table level. If the requested memory range's linear or vmemmap mappings falls within such leaf entries, new mappings need to be created for the remaining memory mapped on the leaf entry earlier, following standard break before make aka BBM rules. But kernel cannot tolerate BBM and hence remapping to fine grained leaves would not be possible on systems without BBML2_NOABORT. Currently memory hot remove operation does not perform such restructuring, and so removing memory ranges that could split a kernel leaf level mapping need to be rejected. While memory_hotplug.c does appear to permit hot removing arbitrary ranges of memory, the higher layers that drive memory_hotplug (e.g. ACPI, virtio, ...) all appear to treat memory as fixed size devices. So it is impossible to hot unplug a different amount than was previously hot plugged, and hence we should never see a rejection in practice, but adding the check makes us robust against a future change. Cc: Catalin Marinas Cc: Will Deacon Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Link: https://lore.kernel.org/all/aWZYXhrT6D2M-7-N@willie-the-truck/ Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Ryan Roberts Suggested-by: Ryan Roberts Signed-off-by: Anshuman Khandual --- arch/arm64/mm/mmu.c | 120 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 5dbf988120c8..5fb9a66f0754 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -2014,6 +2014,107 @@ void arch_remove_memory(u64 start, u64 size, struct= vmem_altmap *altmap) __remove_pgd_mapping(swapper_pg_dir, __phys_to_virt(start), size); } =20 + +static bool addr_splits_kernel_leaf(unsigned long addr) +{ + pgd_t *pgdp, pgd; + p4d_t *p4dp, p4d; + pud_t *pudp, pud; + pmd_t *pmdp, pmd; + pte_t *ptep, pte; + + /* + * If the given address points at a the start address of + * a possible leaf, we certainly won't split. Otherwise, + * check if we would actually split a leaf by traversing + * the page tables further. + */ + if (IS_ALIGNED(addr, PGDIR_SIZE)) + return false; + + pgdp =3D pgd_offset_k(addr); + pgd =3D pgdp_get(pgdp); + if (!pgd_present(pgd)) + return false; + + if (IS_ALIGNED(addr, P4D_SIZE)) + return false; + + p4dp =3D p4d_offset(pgdp, addr); + p4d =3D p4dp_get(p4dp); + if (!p4d_present(p4d)) + return false; + + if (IS_ALIGNED(addr, PUD_SIZE)) + return false; + + pudp =3D pud_offset(p4dp, addr); + pud =3D pudp_get(pudp); + if (!pud_present(pud)) + return false; + + if (pud_leaf(pud)) + return true; + + if (IS_ALIGNED(addr, CONT_PMD_SIZE)) + return false; + + pmdp =3D pmd_offset(pudp, addr); + pmd =3D pmdp_get(pmdp); + if (!pmd_present(pmd)) + return false; + + if (pmd_cont(pmd)) + return true; + + if (IS_ALIGNED(addr, PMD_SIZE)) + return false; + + if (pmd_leaf(pmd)) + return true; + + if (IS_ALIGNED(addr, CONT_PTE_SIZE)) + return false; + + ptep =3D pte_offset_kernel(pmdp, addr); + pte =3D __ptep_get(ptep); + if (!pte_present(pte)) + return false; + + if (pte_cont(pte)) + return true; + + return !IS_ALIGNED(addr, PAGE_SIZE); +} + +static bool can_unmap_without_split(unsigned long pfn, unsigned long nr_pa= ges) +{ + unsigned long phys_start, phys_end, start, end; + + phys_start =3D PFN_PHYS(pfn); + phys_end =3D phys_start + nr_pages * PAGE_SIZE; + + /* PFN range's linear map edges are leaf entry aligned */ + start =3D __phys_to_virt(phys_start); + end =3D __phys_to_virt(phys_end); + if (addr_splits_kernel_leaf(start) || addr_splits_kernel_leaf(end)) { + pr_warn("[%lx %lx] splits a leaf entry in linear map\n", + phys_start, phys_end); + return false; + } + + /* PFN range's vmemmap edges are leaf entry aligned */ + BUILD_BUG_ON(!IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP)); + start =3D (unsigned long)pfn_to_page(pfn); + end =3D (unsigned long)pfn_to_page(pfn + nr_pages); + if (addr_splits_kernel_leaf(start) || addr_splits_kernel_leaf(end)) { + pr_warn("[%lx %lx] splits a leaf entry in vmemmap\n", + phys_start, phys_end); + return false; + } + return true; +} + /* * This memory hotplug notifier helps prevent boot memory from being * inadvertently removed as it blocks pfn range offlining process in @@ -2022,8 +2123,11 @@ void arch_remove_memory(u64 start, u64 size, struct = vmem_altmap *altmap) * In future if and when boot memory could be removed, this notifier * should be dropped and free_hotplug_page_range() should handle any * reserved pages allocated during boot. + * + * This also blocks any memory remove that would have caused a split + * in leaf entry in kernel linear or vmemmap mapping. */ -static int prevent_bootmem_remove_notifier(struct notifier_block *nb, +static int prevent_memory_remove_notifier(struct notifier_block *nb, unsigned long action, void *data) { struct mem_section *ms; @@ -2069,11 +2173,15 @@ static int prevent_bootmem_remove_notifier(struct n= otifier_block *nb, return NOTIFY_DONE; } } + + if (!can_unmap_without_split(pfn, arg->nr_pages)) + return NOTIFY_BAD; + return NOTIFY_OK; } =20 -static struct notifier_block prevent_bootmem_remove_nb =3D { - .notifier_call =3D prevent_bootmem_remove_notifier, +static struct notifier_block prevent_memory_remove_nb =3D { + .notifier_call =3D prevent_memory_remove_notifier, }; =20 /* @@ -2123,7 +2231,7 @@ static void validate_bootmem_online(void) } } =20 -static int __init prevent_bootmem_remove_init(void) +static int __init prevent_memory_remove_init(void) { int ret =3D 0; =20 @@ -2131,13 +2239,13 @@ static int __init prevent_bootmem_remove_init(void) return ret; =20 validate_bootmem_online(); - ret =3D register_memory_notifier(&prevent_bootmem_remove_nb); + ret =3D register_memory_notifier(&prevent_memory_remove_nb); if (ret) pr_err("%s: Notifier registration failed %d\n", __func__, ret); =20 return ret; } -early_initcall(prevent_bootmem_remove_init); +early_initcall(prevent_memory_remove_init); #endif =20 pte_t modify_prot_start_ptes(struct vm_area_struct *vma, unsigned long add= r, --=20 2.30.2