From nobody Mon Sep 28 04:55:37 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 303323F7892; Wed, 26 Aug 2026 12:04:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745888; cv=none; b=ar8OmY9j3BaHD5+5n5j6oxfGshU+l/8+JBNYFebvYikb3qO4CrvSd9hgj3ePYwzlM5wYOSA01xCWisM+VOUabxkL8isSBIMXuLUODwdMUTPdYdFe1nTlc0NifqXZ+RoIvADXhEF6sCInPT+/O0nqWyfUlHs+piW7cSRjo53GOYY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745888; c=relaxed/simple; bh=XZUS/f25dq0I8Z+98iNLJoB6vzCCwIuyygNx7FuPAYw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qDUlzF/R8Pgmx5WoshcEQZ4k6Gkft39V+xoudBrxl2LMltb0BXTeGfikToQDPI7mjVTbHJLTFgxy3INy4ibQFk5A0j49O6HcTVtPi6ImLaxwtto/zmS4TwXIrnmTxXDGfXW2M+ZCXJ7/6ChUa+BnkvxxUb2+PLY6lWv0lEzs1HE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=PyAz2+CV; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="PyAz2+CV" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=UhikRt0HmqGxXvzztoXS5tGgSGD7w/+eDypjfVVuNCU=; b=PyAz2+CVX/MmA487mIUvti4/+X zDA4hLm7x0EkEu+dr1+T5Wmam3SKWWaSJ6cTvAT+FxzIu4ZOkaD43tFqMrOluM35SU2O2LtAY2H+P eQDeMxTeRUy8ho8AOqlrIqBGd7nqC2Ilt7ycp0cnQ1mwW1f2h/eWZW9jq1Z8r78nqpKIRaLe0a55i M1LZoG23NsekZL0h7bjuMdqOQkR75FohUp3dMvV4TQqqEtD71+E+Y3LGSn9XJtLMvB//VeNe1aCzR jpdggdqeZRaM5zi9RAYDm2AtgeOfg+NbiFSMWfmD7w3gxspgQGpdH4czTejkcb+4JnJuI7dvWhcYw L9AmG01w==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wzCMw-00FCWi-2F; Wed, 26 Aug 2026 12:04:39 +0000 From: Breno Leitao Date: Wed, 26 Aug 2026 05:03:52 -0700 Subject: [PATCH v3 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260826-hwpoison-kho-v3-1-6f79c4b605bc@debian.org> References: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> In-Reply-To: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> To: Ard Biesheuvel , Ilias Apalodimas , Miaohe Lin , Naoya Horiguchi , Andrew Morton , kas@kernel.org, kexec@lists.infradead.org, David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko Cc: linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, rmikey@meta.com, riel@surriel.com, Breno Leitao , kernel-team@meta.com X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=4813; i=leitao@debian.org; h=from:subject:message-id; bh=XZUS/f25dq0I8Z+98iNLJoB6vzCCwIuyygNx7FuPAYw=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqjtZKIy44p0OfgXtXkaiRAIy9xVaHpssqtdzOS lq7xv41s1CJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCao7WSgAKCRA1o5Of/Hh3 bdVZD/47lVPd3xCInezUT9l7ojDgBYE3rG7/KqPxMTr7D26pl7QUZ5iRnmOa/oYXIODPtbMJNxP 0AOlMnnBfrspZC6BaebOZMKGhL1fi9VwJ2VpShahrPm29UJG8CZ55YheG5Fq+tE+Uw5zrJHTYrM V0tpxo0LtP7Za7l/hFvgHJUmhoiHvKsEEKF61iaHuEFetuOAC/RiZjGLsl5r0Vuv6h9gq44dUZd NtIAbECTx05b6eMDlBbtTIVK1FStG0H4bL/M034ORA4zxC5SfGRXNFQA4EF2+MwyMHRa365bXkj +uOlUVIuo8nPwuQTiGryGPHzOvTQ9htIgEQQFM2DOFmg/K7tRU1YEiWoqjsz4nfoyU6stgzdUHm SnNP9vOsHfFaqQIhwKMRFIxEt3d2JvofMLuVvc84GN1hsb/tQF07t+LbNWphFk5sbdaxj1MOSlI hbYy7v6qRb6Ejz7wtOD7svHS1s4vzL0jyILVtLVnT9aKK3rosg5OEtQ0joYBsrU04gFa34UvMfh 6VVD6JPtjMXH27vAEkmUlr83z31KG6+R5t+i99B4a4l7iyM+eVE9/C8MhKpiHUaYO8oxb+ZEGi1 HWK2fRFU+2rQYnKQk8NmEvH1olCgdyY6LQeE8sG4DR1FWgTA18lMnr/0nROVTN1/EsddqeXn5Uh V2JggMOfw5h3XlA== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao Hardware-poisoned page frames are tracked only in the running kernel's data structures, so a kexec loses them and the next kernel doesn't have this information, thus, tripping into them again. Add an EFI configuration table to carry that information across kexec. It is a bitmap with one bit per EFI_POISON_UNIT_SIZE (2MiB) of physical memory, modeled on the LINUX_EFI_UNACCEPTED_MEMORY table, and it rides the EFI system table to every kernel in the chain. The Kconfig symbol has no prompt. There is nothing for a user to decide, so it is on wherever it can be, and it only costs 64K per TiB of RAM on a kernel that already has the EFI stub and MEMORY_FAILURE. It is restricted to 64BIT because the unit arithmetic would need div_u64() on 32-bit, and there is no 32-bit EFI configuration with MEMORY_FAILURE to test that on. Suggested-by: Kiryl Shutsemau Signed-off-by: Breno Leitao --- drivers/firmware/efi/Kconfig | 8 ++++++++ drivers/firmware/efi/efi.c | 6 ++++++ include/linux/efi.h | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig index 29e0729299f5b..aafcd41bc0063 100644 --- a/drivers/firmware/efi/Kconfig +++ b/drivers/firmware/efi/Kconfig @@ -263,6 +263,14 @@ config EFI_COCO_SECRET virt/coco/efi_secret module to access the secrets, which in turn allows userspace programs to access the injected secrets. =20 +config EFI_POISONED_MEMORY + def_bool y + depends on EFI_STUB && MEMORY_FAILURE && 64BIT + help + Record page frames that are hardware-poisoned while this kernel runs + into an EFI configuration table, and honor that table early on the + next kernel so a kexec does not hand known-bad RAM back out. + config OVMF_DEBUG_LOG bool "Expose OVMF firmware debug log via sysfs" depends on EFI diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 0327a39d31fa5..111e60479211a 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -55,6 +55,9 @@ struct efi __read_mostly efi =3D { #ifdef CONFIG_UNACCEPTED_MEMORY .unaccepted =3D EFI_INVALID_TABLE_ADDR, #endif +#ifdef CONFIG_EFI_POISONED_MEMORY + .poisoned_memory =3D EFI_INVALID_TABLE_ADDR, +#endif }; EXPORT_SYMBOL(efi); =20 @@ -646,6 +649,9 @@ static const efi_config_table_type_t common_tables[] __= initconst =3D { #ifdef CONFIG_UNACCEPTED_MEMORY {LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID, &efi.unaccepted, "Unaccepted" }, #endif +#ifdef CONFIG_EFI_POISONED_MEMORY + {LINUX_EFI_POISONED_MEMORY_TABLE_GUID, &efi.poisoned_memory, "POISON" }, +#endif #ifdef CONFIG_EFI_GENERIC_STUB {LINUX_EFI_PRIMARY_DISPLAY_TABLE_GUID, &primary_display_table }, #endif diff --git a/include/linux/efi.h b/include/linux/efi.h index b3c83516593d1..c7b4a37f760ec 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -23,6 +23,7 @@ #include #include #include +#include #include =20 #include @@ -422,6 +423,7 @@ void efi_native_runtime_setup(void); #define LINUX_EFI_COCO_SECRET_AREA_GUID EFI_GUID(0xadf956ad, 0xe98c, 0x48= 4c, 0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47) #define LINUX_EFI_BOOT_MEMMAP_GUID EFI_GUID(0x800f683f, 0xd08b, 0x423a, = 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4) #define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID EFI_GUID(0xd5d1de3c, 0x105c, 0= x44f9, 0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31) +#define LINUX_EFI_POISONED_MEMORY_TABLE_GUID EFI_GUID(0xaf828a15, 0x0ef4, = 0x439a, 0xb8, 0x6a, 0xd6, 0xd6, 0x9e, 0xaf, 0xba, 0xfa) =20 #define RISCV_EFI_BOOT_PROTOCOL_GUID EFI_GUID(0xccd15fec, 0x6f73, 0x4eec,= 0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf) =20 @@ -650,6 +652,7 @@ extern struct efi { unsigned long mokvar_table; /* MOK variable config table */ unsigned long coco_secret; /* Confidential computing secret table */ unsigned long unaccepted; /* Unaccepted memory table */ + unsigned long poisoned_memory; /* Hardware-poisoned memory table */ =20 efi_get_time_t *get_time; efi_set_time_t *set_time; @@ -1271,6 +1274,16 @@ struct linux_efi_memreserve { #define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memr= eserve)) \ / sizeof_field(struct linux_efi_memreserve, entry[0])) =20 +/* Bit N covers unit N of physical address space, counting from address 0.= */ +struct linux_efi_poisoned_memory { + u32 version; + u32 unit_size; /* bytes of phys space per bitmap bit */ + u64 size; /* bitmap size in bytes */ + unsigned long bitmap[]; +}; + +#define EFI_POISON_UNIT_SIZE SZ_2M + void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size); =20 /* --=20 2.53.0-Meta From nobody Mon Sep 28 04:55:37 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 82AB53B27C3; Wed, 26 Aug 2026 12:04:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745890; cv=none; b=dpsPSNPNF5w8pGv2qoRWwH2RV1x35BT/tTEI9VgCs++bqbCZy0o/vTJ2OH9l0QT+YniPvL0+dC21qciyTVuEK9bRDYPFQAeLnPM7xJ8JgtkazXkxTW5vTlwcQYRiO3aeF+QStc9X3wqktp8HpbY4AfkozwL1JK/FUd/hMQL7a2I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745890; c=relaxed/simple; bh=Z8b00DBJDYmFMxDESa/wECCjyj43cNByhLt+GnEMGjE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JRyX4x6hlFlkvGzo6ftyXaS/R3LxFgktLIXyfsvkaopXYur52fkZ39+/SbyGJ3awcy/5TnrZzgS2EnXQPF0+nlDRuXOCRNpB2P/pVGMaTz3+P3QWbAoy4DAVCEPukHOcyjLJa1rDqWlP2sKtYF21yqc59XPlGYc1W+gX8SX3NuY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=cZ0z3oge; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="cZ0z3oge" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=SbhxHAltudSE0Y7J28CXSXgoY/4tm3ypaV92q+OPD6w=; b=cZ0z3ogevdcXQzQ5GhLC05zUwp M+5wDzf9EsLcy4LYngVhl89EkZq1EaPRSYy1/U9V6MJLBPspg93eLTBCnN54vrNDP0bMvrcKYj4f5 Sl8ZQ7M8kJWInXTLznOQczUcDhXA5l1BwxJa/ZvKLde36p/XJZOjP1cGZDCeS6BlUcn7eVfLE4gdl WNhHwWRoaX1J5zzCjfFMOSa2brMeTYTBUVv4SNpy9ittK9K3X7h7Eyuc/IKpUTQ3vy0VIZghn+aTg k+9mYbvHt2vWGPdOI6fadMm3BqnmI91y9Va1A1FkVHqwZhoAlUDlz1S18zY/62HgnxSyHkpc4Z2m0 GuhUaVSQ==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wzCN2-00FCX7-0W; Wed, 26 Aug 2026 12:04:44 +0000 From: Breno Leitao Date: Wed, 26 Aug 2026 05:03:53 -0700 Subject: [PATCH v3 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260826-hwpoison-kho-v3-2-6f79c4b605bc@debian.org> References: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> In-Reply-To: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> To: Ard Biesheuvel , Ilias Apalodimas , Miaohe Lin , Naoya Horiguchi , Andrew Morton , kas@kernel.org, kexec@lists.infradead.org, David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko Cc: linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, rmikey@meta.com, riel@surriel.com, Breno Leitao , kernel-team@meta.com X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=6932; i=leitao@debian.org; h=from:subject:message-id; bh=Z8b00DBJDYmFMxDESa/wECCjyj43cNByhLt+GnEMGjE=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqjtZKCfXr+dnXUwLgGGNeFWUmXAQhZ+kSTWYJA Hzxms8X/JCJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCao7WSgAKCRA1o5Of/Hh3 be/zEACHP8bvz1QjTQwWBaPet8gNeZG1+KYIya8WM1+MgshAC5+ey7drOS8BCpNxrysa3F5fkf4 LdifpMwKwcvWpCS/sfge65xHAa0pXw0HuZSfRZRYs3W0aJiecqi8VnSBcnvWqZMC4YmzL4pSAJT eJtIV9KPHpkSosP5uWNVt5zUmoRkPkW0xabbHeT6a58/EhrWCHbMECL9h1FQe8cgi9i2SFOO01w YoWawYBXm5yHibTA51AdSYv0ECDNBkkkWCcYq2+8fCYdT8NvJlvZN+mf+qvdKIuHimuY1gIhXnn JHxcL7ydriKL0s+iG0TjR28UUFtue+27taHs0LTcdyzshGXFcUIqEHC5SUz3s9R800igEJ+qruo AwPLy1ksEHFiSMx3WAYfzlZaJwCbB6E12NF53/lSm9egtUvOaC6M8HQeDYM9gUhWuAIa8CeXucH suA1ZOmv2IWK2/81u53OpinphIrC6lgjyYQHhQhrUazMBCI89Wc2K56zZn5IYQLVbyaHGnlQ+sw /8TmSweReLtzOhr4C4HA63UGKykv3+ToyEmgSmCUqBnA6DPqSzcyJIMn6IfLY7lEEfbxGS3IV1F BYM/epE4dzBB/KYas4VRhdb6sNDzNs8yI7msKFbFckPofKxc6p63TY6maeu/1pTNWjZahl2o0fo 38kI/QCzNEBAGEw== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao A EFI config table can only be installed while boot services are still up, so the stub has to create it; the running kernel can only flip bits in a table that already exists. Size the bitmap from the top of usable RAM, which efi_get_ram_top() works out by walking the UEFI memory map, since the stub has no max_pfn. Bit N covers unit N counting from address 0, so the table tracks max_pfn. Memory the firmware hot-adds later sits above it and is not carried across a kexec. One table has to serve every architecture, so efi_get_ram_top() takes the union of the memory types they turn into RAM: what setup_e820() maps to E820_TYPE_RAM on x86, plus the EFI_ACPI_RECLAIM_MEMORY and EFI_PERSISTENT_MEMORY that is_usable_memory() accepts on arm64. Sizing wide only costs bitmap bytes; sizing narrow silently drops the records for every frame above the top. At one bit per 2M that is 64K per TiB, and 256M at the 4PB x86 architectural maximum. The 2M granule is called "unit" here, and the table carries it so the granule can change later without breaking the kernels already reading it. Allocate it as EFI_ACPI_RECLAIM_MEMORY so the next kernel does not take it for free RAM, and install it empty. A table installed by an earlier boot rides the system table across kexec and is reused as-is. x86 does not go through efi_stub_common(), so the generic stub and the x86 stub each need the call; on x86 it has to come before exit_boot(), which is the last point a configuration table can be installed. Signed-off-by: Breno Leitao --- drivers/firmware/efi/libstub/efi-stub-helper.c | 106 +++++++++++++++++++++= ++++ drivers/firmware/efi/libstub/efi-stub.c | 1 + drivers/firmware/efi/libstub/efistub.h | 6 ++ drivers/firmware/efi/libstub/x86-stub.c | 2 + 4 files changed, 115 insertions(+) diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmw= are/efi/libstub/efi-stub-helper.c index f27f2e1f00199..b8b80846239d0 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -774,3 +774,109 @@ void efi_remap_image(unsigned long image_base, unsign= ed alloc_size, efi_warn("Failed to remap data region non-executable\n"); } } + +#ifdef CONFIG_EFI_POISONED_MEMORY +/* Erring wide only costs bitmap bytes, so the type list is permissive. */ +static efi_status_t efi_get_ram_top(u64 *top) +{ + struct efi_boot_memmap *map __free(efi_pool) =3D NULL; + efi_status_t status; + u64 ram_top =3D 0; + int i, nr_desc; + + status =3D efi_get_memory_map(&map, false); + if (status !=3D EFI_SUCCESS) + return status; + + nr_desc =3D map->map_size / map->desc_size; + for (i =3D 0; i < nr_desc; i++) { + efi_memory_desc_t *d; + + d =3D efi_memdesc_ptr((unsigned long)map->map, map->desc_size, i); + switch (d->type) { + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + case EFI_CONVENTIONAL_MEMORY: + case EFI_UNACCEPTED_MEMORY: + case EFI_ACPI_RECLAIM_MEMORY: + case EFI_PERSISTENT_MEMORY: + ram_top =3D max(ram_top, + d->phys_addr + d->num_pages * EFI_PAGE_SIZE); + break; + default: + break; + } + } + if (!ram_top) + return EFI_NOT_FOUND; + + *top =3D ram_top; + return EFI_SUCCESS; +} + +/* Whole words: the kernel reaches the bitmap with set_bit(). */ +static u64 efi_poison_bitmap_size(u64 ram_top) +{ + u64 bytes =3D DIV_ROUND_UP(DIV_ROUND_UP(ram_top, EFI_POISON_UNIT_SIZE), + BITS_PER_BYTE); + + return round_up(bytes, sizeof(unsigned long)); +} + +/* ACPI reclaim memory, so the next kernel does not treat it as free RAM. = */ +static struct linux_efi_poisoned_memory *efi_poison_alloc(u64 bitmap_size) +{ + struct linux_efi_poisoned_memory *pm; + efi_status_t status; + + status =3D efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY, + sizeof(*pm) + bitmap_size, (void **)&pm); + if (status !=3D EFI_SUCCESS) + return NULL; + + pm->version =3D 1; + pm->unit_size =3D EFI_POISON_UNIT_SIZE; + pm->size =3D bitmap_size; + memset(pm->bitmap, 0, bitmap_size); + + return pm; +} + +void install_poisoned_memory_table(void) +{ + efi_guid_t poisoned_memory_table_guid =3D LINUX_EFI_POISONED_MEMORY_TABLE= _GUID; + struct linux_efi_poisoned_memory *pm; + u64 ram_top, bitmap_size; + efi_status_t status; + + /* A table installed by an earlier boot rides the system table across kex= ec. */ + pm =3D get_efi_config_table(poisoned_memory_table_guid); + if (pm) { + if (pm->version !=3D 1) + efi_err("Unknown version of poisoned-memory table\n"); + return; + } + + if (efi_get_ram_top(&ram_top) !=3D EFI_SUCCESS) { + efi_err("Failed to size the poisoned-memory table!\n"); + return; + } + + bitmap_size =3D efi_poison_bitmap_size(ram_top); + + pm =3D efi_poison_alloc(bitmap_size); + if (!pm) { + efi_err("Failed to allocate poisoned-memory table!\n"); + return; + } + + status =3D efi_bs_call(install_configuration_table, + &poisoned_memory_table_guid, pm); + if (status !=3D EFI_SUCCESS) { + efi_bs_call(free_pool, pm); + efi_err("Failed to install poisoned-memory config table!\n"); + } +} +#endif diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi= /libstub/efi-stub.c index 42d6073bcd062..008635eb5027a 100644 --- a/drivers/firmware/efi/libstub/efi-stub.c +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -179,6 +179,7 @@ efi_status_t efi_stub_common(efi_handle_t handle, EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP); =20 install_memreserve_table(); + install_poisoned_memory_table(); =20 status =3D efi_boot_kernel(handle, image, image_addr, cmdline_ptr); =20 diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/= libstub/efistub.h index fd91fc15ec810..44436869c4efe 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -1169,6 +1169,12 @@ efi_enable_reset_attack_mitigation(void) { } =20 void efi_retrieve_eventlog(void); =20 +#ifdef CONFIG_EFI_POISONED_MEMORY +void install_poisoned_memory_table(void); +#else +static inline void install_poisoned_memory_table(void) { } +#endif + struct sysfb_display_info *alloc_primary_display(void); struct sysfb_display_info *__alloc_primary_display(void); void free_primary_display(struct sysfb_display_info *dpy); diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi= /libstub/x86-stub.c index cef32e2c82d8f..f90de11bc8855 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -1023,6 +1023,8 @@ void __noreturn efi_stub_entry(efi_handle_t handle, =20 setup_unaccepted_memory(); =20 + install_poisoned_memory_table(); + status =3D exit_boot(boot_params, handle); if (status !=3D EFI_SUCCESS) { efi_err("exit_boot() failed!\n"); --=20 2.53.0-Meta From nobody Mon Sep 28 04:55:37 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF83926ED3D; Wed, 26 Aug 2026 12:04:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745896; cv=none; b=fwmzHGoAdAjklqKe1jycE5d4LpnkFIbJh04XMssIQx+c8EdchNLEZQi9RLggeq1BZxNBXwi6C4t1fN+W3Sakdx8bgfg5y+TLG2/GtlwdMBzDvl8FrrmU0/mp2CXsK5AG2j8PaFaQS1gzuv7Y6guktJ9OlytG23ADjlJuG321Vzw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745896; c=relaxed/simple; bh=S2cKpA7kUQ+R7GBTrg8fm7myqPM0I7wx+hvjWQ9x6z4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GYPz3rgx721TVuajNimP0rtUScdPHylwOFf2cXzaAi/ewTzTafmp3wdB3Z2lxE7BA1Npt3xarUdPdM5OrvSTWgTBHsCDLIXyHap8wydlM60jNafdaP3edFxtEvaRkEEYbfK+5y+veIxI6nalBXVqWsp7fPB9OGikSEFo3OxD5GE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=fAkhlHRO; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="fAkhlHRO" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=gNmGsxm7/3k+LNhJKR8mNefNQdIE2fnt5a+0XM15yqM=; b=fAkhlHRO91Pu1CJ21hYPfrNbTS dX16DffjA5WDOzEjg5E+3Kp8rpQt76YYGHNZsVVqx96im6zNoAucLD8fDa7TvlWYYvXjCsqTBkGqu KIjEXpGFtP3Rt+xDZV9Fd9jJXI8m6v5Yljquu+P5fgtW9r6/x0TiKFytQbQRntWaDUP4Dk12xb7qd xl7t2ETg2rARMtzSUt5KHlN8nKuCip71AF1AFV28fNQN/JK1L836Z7aPzogEpS0y8mMpzlIpX14Ag iZzMIZa+UisUJ1xtOAC2g9VuTQEdkU+jABoNCCRIKn5xlCOANiN/k/kyYydl1hL7QXzoXzqWOBR7i QneAkv8Q==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wzCN7-00FCXJ-26; Wed, 26 Aug 2026 12:04:50 +0000 From: Breno Leitao Date: Wed, 26 Aug 2026 05:03:54 -0700 Subject: [PATCH v3 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260826-hwpoison-kho-v3-3-6f79c4b605bc@debian.org> References: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> In-Reply-To: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> To: Ard Biesheuvel , Ilias Apalodimas , Miaohe Lin , Naoya Horiguchi , Andrew Morton , kas@kernel.org, kexec@lists.infradead.org, David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko Cc: linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, rmikey@meta.com, riel@surriel.com, Breno Leitao , kernel-team@meta.com X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=6496; i=leitao@debian.org; h=from:subject:message-id; bh=S2cKpA7kUQ+R7GBTrg8fm7myqPM0I7wx+hvjWQ9x6z4=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqjtZK9sTBgNQCxBfhvQIZobnXmKdgrZidzrOiy C1uL+pXY42JAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCao7WSgAKCRA1o5Of/Hh3 bTreEACYtvZAq+YqtwgWUmjn0XVJ0TqEgbYc10cKuFOW7pLckNCImqjBauSLq7RMmrkgP0wJGL9 GSKMAAwwjDZnNYxMAEqUCsAe4y1XrUK0LzXWS6R54MOd3WQSYrVrda3NOcuBDQNfawa6C27fuMZ U2avXXwj8ynCp/kRC+1yziS1wbbUMdj/56D2NkbgWUO4EzcOtlwsaxQnKQKZN6QeEcwOsCaV1Ze +RgiW+3PcwQugvLNj8Vqz7PvK47tRVJmBrK5/fNR62cK9RDYBXLjJQ7t9HbeHQreDbcy8MdNmy7 XdqKVMU1A+QB5rlgI/Ir/NunyfUqdkD+zR7t8WHqpzLnkWr3GEmpS6iTQXlPAY3WwiNTiWLqNn2 mTZLpuSjbYhoBuSXN4W9J/ktPV2i6t0KVo/8ystypLHt/LDbdCDwYAJmXnv2ohjvWA0+DnefZ5c OCCt5slyt0NG+tSP+l73uTSRnrs33NHRyVDn5RPVYIN9gcEz3V4SHrsCPMNvctstjspp/wLQq+g +WKTLS/0u0MosGwRV+e6SA3pPqZU4CjFJjohFuMZ7fcew7f6a2XglFT0E2JXrAy91cfk2vjgFVT Z1IuYXANBBTkF+vdL60csjfJWITDaCHIVTLBnCYL2gwE1jGDaibXCXKcVOLxoXtGonk4box8nJ6 pnHPgLTxCFRVd+g== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao action_result() is where memory_failure() reports the outcome of a hard offline, so hook it to set the frame's bit in the LINUX_EFI_POISONED_MEMORY bitmap. Soft-offlined pages reach num_poisoned_pages_inc() through page_handle_poison() and are deliberately left out: they are still functional and were offlined predictively, so recording them would turn a prediction into a permanent loss for every kernel further down the kexec chain. A bit is only ever set, never cleared, given that multiple pages can set the same bit, and it is not trivial to decide if the bit should be unset when a page is unrecorded. Unpoisoning a frame therefore does not hand its unit back to the next kernel. That is a known limitation. memory_failure() has already taken the frame out of this kernel's allocator, so only the cross-kexec record happens here. Suggested-by: Kiryl Shutsemau Signed-off-by: Breno Leitao --- drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/poison.c | 126 ++++++++++++++++++++++++++++++++++++++= ++++ include/linux/efi.h | 6 ++ mm/memory-failure.c | 3 + 4 files changed, 136 insertions(+) diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile index 8efbcf699e4ff..05d0a490923e5 100644 --- a/drivers/firmware/efi/Makefile +++ b/drivers/firmware/efi/Makefile @@ -43,4 +43,5 @@ obj-$(CONFIG_EFI_EARLYCON) +=3D earlycon.o obj-$(CONFIG_UEFI_CPER_ARM) +=3D cper-arm.o obj-$(CONFIG_UEFI_CPER_X86) +=3D cper-x86.o obj-$(CONFIG_UNACCEPTED_MEMORY) +=3D unaccepted_memory.o +obj-$(CONFIG_EFI_POISONED_MEMORY) +=3D poison.o obj-$(CONFIG_TEE_STMM_EFI) +=3D stmm/tee_stmm_efi.o diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c new file mode 100644 index 0000000000000..d6855e712832c --- /dev/null +++ b/drivers/firmware/efi/poison.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Runtime side of the LINUX_EFI_POISONED_MEMORY table: one bit per + * EFI_POISON_UNIT_SIZE, set here as frames go bad, honored by the next ke= rnel. + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. + * Copyright (c) 2026 Breno Leitao + */ + +#define pr_fmt(fmt) "efi: " fmt + +#include +#include +#include +#include +#include +#include +#include + +static struct linux_efi_poisoned_memory *efi_poison __ro_after_init; +static u64 efi_poison_nbits __ro_after_init; + +static u64 __init +efi_poison_usable_size(const struct linux_efi_poisoned_memory *pm) +{ + u64 nr_units =3D DIV_ROUND_UP(PFN_PHYS(max_pfn), pm->unit_size); + u64 bytes =3D DIV_ROUND_UP(nr_units, BITS_PER_BYTE); + + /* Whole words: the bitmap is reached an unsigned long at a time. */ + return min(round_up(bytes, sizeof(unsigned long)), pm->size); +} + +static bool __init +efi_poison_geometry_valid(const struct linux_efi_poisoned_memory *pm) +{ + if (!pm->size || !IS_ALIGNED(pm->size, sizeof(unsigned long))) + return false; + + return pm->unit_size >=3D PAGE_SIZE && is_power_of_2(pm->unit_size); +} + +static bool __init +efi_poison_range_valid(const struct linux_efi_poisoned_memory *pm) +{ + u64 nbits, span; + + if (check_mul_overflow(pm->size, (u64)BITS_PER_BYTE, &nbits)) + return false; + + return !check_mul_overflow(nbits, (u64)pm->unit_size, &span); +} + +/* The table may come from an earlier kernel, so vet it before using it. */ +static bool __init +efi_poison_table_valid(const struct linux_efi_poisoned_memory *pm) +{ + if (pm->version !=3D 1) { + pr_warn("Ignoring poisoned-memory table with version %u\n", + pm->version); + return false; + } + + if (!efi_poison_geometry_valid(pm) || !efi_poison_range_valid(pm)) { + pr_warn("Ignoring malformed poisoned-memory table\n"); + return false; + } + + return true; +} + +static int __init efi_poison_init(void) +{ + struct linux_efi_poisoned_memory *pm; + u64 size; + + if (efi.poisoned_memory =3D=3D EFI_INVALID_TABLE_ADDR) + return 0; + + pm =3D memremap(efi.poisoned_memory, sizeof(*pm), MEMREMAP_WB); + if (WARN_ON_ONCE(!pm)) + return 0; + if (!efi_poison_table_valid(pm)) { + memunmap(pm); + return 0; + } + size =3D efi_poison_usable_size(pm); + memunmap(pm); + if (!size) + return 0; + + efi_poison =3D memremap(efi.poisoned_memory, sizeof(*pm) + size, + MEMREMAP_WB); + if (WARN_ON_ONCE(!efi_poison)) + return 0; + + efi_poison_nbits =3D size * BITS_PER_BYTE; + return 0; +} +early_initcall(efi_poison_init); + +static long efi_poison_unit(unsigned long pfn) +{ + u64 unit =3D PFN_PHYS(pfn) / efi_poison->unit_size; + + if (unit >=3D efi_poison_nbits) + return -1; + return unit; +} + +/* + * A bit is never cleared: it stands for a whole EFI_POISON_UNIT_SIZE, so = an + * unpoison cannot tell whether the unit as a whole is good again. + */ +void efi_hwpoison_record_pfn(unsigned long pfn) +{ + long unit; + + if (!efi_poison) + return; + + unit =3D efi_poison_unit(pfn); + if (unit < 0) + return; + + set_bit(unit, efi_poison->bitmap); +} diff --git a/include/linux/efi.h b/include/linux/efi.h index c7b4a37f760ec..d579d75372248 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1284,6 +1284,12 @@ struct linux_efi_poisoned_memory { =20 #define EFI_POISON_UNIT_SIZE SZ_2M =20 +#ifdef CONFIG_EFI_POISONED_MEMORY +void efi_hwpoison_record_pfn(unsigned long pfn); +#else +static inline void efi_hwpoison_record_pfn(unsigned long pfn) { } +#endif + void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size); =20 /* diff --git a/mm/memory-failure.c b/mm/memory-failure.c index aaf14608b30e2..357a72ffda625 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -1286,6 +1287,8 @@ static int action_result(unsigned long pfn, enum mf_a= ction_page_type type, if (type !=3D MF_MSG_ALREADY_POISONED && type !=3D MF_MSG_PFN_MAP) { num_poisoned_pages_inc(pfn); update_per_node_mf_stats(pfn, result); + /* Only hard offlines are carried over to the next kernel. */ + efi_hwpoison_record_pfn(pfn); } =20 pr_err("%#lx: recovery action for %s: %s\n", --=20 2.53.0-Meta From nobody Mon Sep 28 04:55:37 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 09C473FB7E7; Wed, 26 Aug 2026 12:04:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745898; cv=none; b=d2QptC+BDWYOuuF13h/Ypw1El1OPf2H3tiIx7WrLPURVs1830SY5MvBn/+/H9h5yXLJh8cc7kV2DV4768BbQ1OPf5bQJcLDS1GT1qUdc+j2Jxo8HNjcC606F4adz3g+c84Giz8uDVuj5hytAf7JGuzKMIGEgSb76zVPnd3ywXFQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745898; c=relaxed/simple; bh=o7M5nleNUlNmnqMGyVTK9Qj2BeNxjWI0gsdkMd+ovbg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tdf4HPrSShrD6FMR4YCI49Sn4xNHsMHG6A8CmbhNNmkpGTTIgYgFqjmvrgAT2uXLZRE7Ao6X2bgntlEbZzsHs6B5oxOVr+dTfuwsr9ZSUF8O4bZ2f0iivEv/B2Vd7cq/kXIgviDKs2troPlLGCp2u8hgL4djljJKXM19urCJY24= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=eJhsOs+d; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="eJhsOs+d" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=ua19ttiw+1gS6VkqPomk+aSAT0oJIssRXjVFuOoXcyQ=; b=eJhsOs+dcKxgZEjIawHbnCfoxR FJoj2CNhOb1rCtnrqH2Mm5aAlhy0LOqunuNfO1EA6x/ZK3OFyOSYpP6kp+gLpH6XoL4OQaK/xRKki pX7HzuixQR6+8AEy8YUjtcuSATq+wSmUzpUfLNfUkezzAAEiqfNVvC25+hWJu6NrfQoh1pvKkmP/N S8kFISupRIKNuvwk6E/mgWW/iuLDuynkVUMay1aPs8ymGFi/WIIcM1BMzocl5KnGPAp5rnMI7P4ez q0++WLO/Ya8vMFeuh9tmFVPZxYYtnFmnHOuFHmoFEGgXFvwMymeTAEoCAOg6C8UyC23eHL7dzUmir 7B44HBnA==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wzCNC-00FCXg-3C; Wed, 26 Aug 2026 12:04:55 +0000 From: Breno Leitao Date: Wed, 26 Aug 2026 05:03:55 -0700 Subject: [PATCH v3 4/5] mm/memory-failure: add a helper to poison a frame at boot Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260826-hwpoison-kho-v3-4-6f79c4b605bc@debian.org> References: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> In-Reply-To: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> To: Ard Biesheuvel , Ilias Apalodimas , Miaohe Lin , Naoya Horiguchi , Andrew Morton , kas@kernel.org, kexec@lists.infradead.org, David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko Cc: linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, rmikey@meta.com, riel@surriel.com, Breno Leitao , kernel-team@meta.com X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=2675; i=leitao@debian.org; h=from:subject:message-id; bh=o7M5nleNUlNmnqMGyVTK9Qj2BeNxjWI0gsdkMd+ovbg=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqjtZLFACyJ88fYHvmvDeKqRvOsw5p9Y+n1bm5D Mzx8D61g3WJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCao7WSwAKCRA1o5Of/Hh3 bVB+D/sH01X0pquQmQeP/oQwRNdbzSLw8hrBthdZ+iuZnJ7EHMihKbidDfHRr21dB7Wah97UeXu 5tqMyBCwmEUKMGWLphOO2rTVM3srwtYk/XGmp0yy635/DwcN0HvOEeWmabJFg699vfaZ/SCP2Vb JE8vJL2/GfuL5GwtRZmgsQUQ+S4phKctRMge8sk0Ggg+eOHf6QDZQafMOlZjP0kZYppluUTE5pE /bBRllA6j0Gh6MiXOpnapCz9b0ERKstaO8YWOUWlmMLXJEuBbsRx+HdUGUp4LPV7toFT+RiGjz6 ttsLGOS3n85dg9Q60645l3tsJOGdGMAhF5JiMLTw3PQ6wexEEpzwO5syX4+NOLlfeSFDW1/c1nX iiUBw1tymNwiDEUXh0GhvQq2CJukvcp8a+OuzWmIcg0IX9hw9M20qqY3ypRVJc2LvzvZFaP2Fa8 I3alDzhhkiphuwoMnicMqCg7cGjJqba2vNPmWin7iPqk96K/Mk1k/8jRDpE/QgJcziewYWRd8/2 0bRqDeDM2P3Q3PBpCzGU279PckPuqgGZ8duiElywmz1Q9Muik3O8uh54+r4e4zj958/GSrKZ++p qlVPg+pTp0I8JwfifiUB37YCoihHpNHZAr73OSS96JxHJMOE6kJ8jRYaWV0j1ge15pLYu2wRD5C CnO+jWKzf1QGruA== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao A kernel that inherits a record of hardware-poisoned frames from an earlier kernel needs a way to apply it. memory_failure() does not fit: it takes mf_mutex, prints a line per frame, and a 2M unit is 512 of them. Add hwpoison_boot_pfn(). A frame that is free takes the same route memory_failure() takes for a free page: take_page_off_buddy(), the flag, the refcount and the counter. A frame the kernel is already sitting on is only flagged, since it cannot be taken away from whoever reserved it. free_pages_prepare() drops such a frame if it is ever handed back, so it does not reach the allocator either way. The accounting cannot go through num_poisoned_pages_inc(). Its per memory block half divides by sections_per_block, which memory_dev_init() only sets up from driver_init(), so where this runs it is still zero and the boot dies on a divide by zero. Bump the global counter directly; the block counters stay short by these frames. Signed-off-by: Breno Leitao --- include/linux/mm.h | 1 + mm/memory-failure.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 32bb723ffbb92..52a00a0e090c7 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5104,6 +5104,7 @@ extern const struct attribute_group memory_failure_at= tr_group; extern void memory_failure_queue(unsigned long pfn, int flags); void num_poisoned_pages_inc(unsigned long pfn); void num_poisoned_pages_sub(unsigned long pfn, long i); +bool __init hwpoison_boot_pfn(unsigned long pfn); #else static inline void memory_failure_queue(unsigned long pfn, int flags) { diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 357a72ffda625..713fb2f8e0332 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -97,6 +97,30 @@ void num_poisoned_pages_sub(unsigned long pfn, long i) memblk_nr_poison_sub(pfn, i); } =20 +static void update_per_node_mf_stats(unsigned long pfn, enum mf_result res= ult); + +bool __init hwpoison_boot_pfn(unsigned long pfn) +{ + struct page *page =3D pfn_to_online_page(pfn); + + if (!page || PageHWPoison(page)) + return false; + + if (is_free_buddy_page(page)) { + if (!take_page_off_buddy(page)) + return false; + page_ref_inc(page); + } else if (!PageReserved(page)) { + return false; + } + + SetPageHWPoison(page); + update_per_node_mf_stats(pfn, MF_RECOVERED); + atomic_long_inc(&num_poisoned_pages); + + return true; +} + /** * MF_ATTR_RO - Create sysfs entry for each memory failure statistics. * @_name: name of the file in the per NUMA sysfs directory. --=20 2.53.0-Meta From nobody Mon Sep 28 04:55:37 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7594D3FA5F1; Wed, 26 Aug 2026 12:05:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745904; cv=none; b=Jyv4gaZjmiyMK4xj3aV37eYDZy63z8qnmdtS1/g1hoVPeZDMe0CuuS371iYlmz1CZpGjQ9o107Ak9cDy0umAs3HUvX9Kqm1G3BFB6C58npvEnRPIKWJmDWACnX3ApeLZgpHFJSlMRDFBTPNOaPMS0YlOrvRIstPwL+HmZ3i3AKE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787745904; c=relaxed/simple; bh=UoMzb64e2RlMzQ3LgVqhH1IIBLhMvJk/4Xuo1bCJkZY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=G93nDfcKwMjCITp/HcIA4i8bdv3cCmH4cF8p5AA/7X2R+LlVzfbS9Jgi5FRY6shGKxWCKFvzNv3d48rWFCLB9TYdDu1azBbKEw0yTo513GuG55yi4w+a6O/tHULAcZdXzfvqwDp5Lg8EHCtt9zZBCgFlZDFw2JE5n8JN7u5C5h4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=LkUh0EPQ; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="LkUh0EPQ" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=7xslyR078ZQa9uWsN28A/GTFrLCow8t/z01YQTIpb9Q=; b=LkUh0EPQg0YWEV+OVhhL9JVar1 +errUwJd89Jlfz9ZJ0e9fwczcl1eiXAOCjKm2dAVkwISXn/QCAbRUtMikpoexLaNG0LyOYkco7bxD gd46v+RNkPwwuVsi729M1C/LlE/5YMDSfW6zjA/+RbGyPuIYzS30Z4cS/RWBBj2GFzrWULotAXkgx E03T0r5DlUvwsqPviw8AQ+wKyxf/YtUE6VhFDflHxVxEiyU8xfWaMQ9bpVikanWkjfbxcgU/IFlJU dz9F3mtydsPnNG95Si5m6UR0jEludb/lwOWE34uWXrb+QAodQidP+/4NACd0caC6UmRv5JTOgeHUW P0bv+opA==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wzCNI-00FCY0-12; Wed, 26 Aug 2026 12:05:00 +0000 From: Breno Leitao Date: Wed, 26 Aug 2026 05:03:56 -0700 Subject: [PATCH v3 5/5] mm/memory-failure: efi: replay the poisioned page in the next kernel Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260826-hwpoison-kho-v3-5-6f79c4b605bc@debian.org> References: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> In-Reply-To: <20260826-hwpoison-kho-v3-0-6f79c4b605bc@debian.org> To: Ard Biesheuvel , Ilias Apalodimas , Miaohe Lin , Naoya Horiguchi , Andrew Morton , kas@kernel.org, kexec@lists.infradead.org, David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko Cc: linux-efi@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org, rmikey@meta.com, riel@surriel.com, Breno Leitao , kernel-team@meta.com X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=6726; i=leitao@debian.org; h=from:subject:message-id; bh=UoMzb64e2RlMzQ3LgVqhH1IIBLhMvJk/4Xuo1bCJkZY=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqjtZLCwO1oNCTElS8ix1B3HUNJcnZq769xR1lZ JL5PIiU4FSJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCao7WSwAKCRA1o5Of/Hh3 bbVRD/9Iw/LK/BjUim0dpaiQgKRZZ1f8xiftwNTEtyL3fOd0n8rJfhNNwI3sYHq4gVE284XOr0D HGbW/hOAUuLhRJmxMLt1Qil0zrEugzPTWrqr/cjzKoc9ga5RFlLqyWgh8m20KaN/vPRotaDgD7z lkFCuSvCi0Zz4XeMNR7NwjzbHxsvBKDUMUzHu2Z/+/Z0sHYuVy/Lpweceyw9VkmWvcAdtRQIEP3 zHv3ZzSeSWh57EEBZFbzvhgRcdsZK+5BCsafGwfZKZNrnyKq+UNWyoQL8IFc49096PwDyb8Vv7H h02yYRlRPK3SX7oy0qGdgUsbZnVwq/6cmVZRWYlo6awU5x0wnGxtBq4GFd0w2KgTmd90Tm1xb+x t9mJyuJx5cO2t9vbna97j/R/7WumGlKZ26f/PCpNoz3qdeItgOtm2T5p1jodiaHCs9pT3j5H43H oxqhKoT4dWSwszAA15pdvAfbCyBj/8hRGSnLgzY6/pMMCMdrDV17a98RuEVN1CmauiQM62VLNm+ y0mwHb+CUzJEBpngUcZl8El7meotwtiqRQSmfXv92H0N92WqAq/fHk9IRU4FxUfxQjdInJhBcc0 eiLHKoWhcTngSyZLgxzqcTLu8udSaoB9BmQ89+JfzcASGwJvZZSnmWNkWamlwIqRY+mmUXp2jgA SjD/FL4O6CUk1lQ== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao The bitmap an earlier kernel filled in rides the EFI system table into this one, but nothing reads it back until now. Add efi_offline_poisoned_memory(), which vets the table header, copies the geometry out of it, and hands every frame of every set unit to hwpoison_boot_pfn(). The bitmap runs to megabytes on a large machine, so it is mapped and walked a page at a time rather than in one go. Call it through hwpoison_init_boot() from mm_core_init() right after memblock_free_all(), the first point at which the recorded frames have struct pages. They end up in the state a frame poisoned by this kernel would be in, so the placement check that already understands PG_hwpoison covers them too. Suggested-by: Kiryl Shutsemau Signed-off-by: Breno Leitao --- drivers/firmware/efi/poison.c | 113 ++++++++++++++++++++++++++++++++++++++= +++- include/linux/efi.h | 2 + include/linux/mm.h | 5 ++ mm/memory-failure.c | 6 +++ mm/mm_init.c | 1 + 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c index d6855e712832c..179843277457f 100644 --- a/drivers/firmware/efi/poison.c +++ b/drivers/firmware/efi/poison.c @@ -9,14 +9,20 @@ =20 #define pr_fmt(fmt) "efi: " fmt =20 -#include #include #include #include #include +#include #include #include =20 +struct efi_poison_geometry { + u64 table; /* phys address of the table */ + u64 unit_size; + u64 bitmap_size; +}; + static struct linux_efi_poisoned_memory *efi_poison __ro_after_init; static u64 efi_poison_nbits __ro_after_init; =20 @@ -124,3 +130,108 @@ void efi_hwpoison_record_pfn(unsigned long pfn) =20 set_bit(unit, efi_poison->bitmap); } + +static bool __init efi_poison_read_geometry(u64 ppm, + struct efi_poison_geometry *g) +{ + struct linux_efi_poisoned_memory *pm; + bool valid; + + pm =3D early_memremap(ppm, sizeof(*pm)); + if (!pm) { + pr_warn("Could not map poisoned-memory table\n"); + return false; + } + + valid =3D efi_poison_table_valid(pm); + if (valid) { + g->table =3D ppm; + g->unit_size =3D pm->unit_size; + g->bitmap_size =3D efi_poison_usable_size(pm); + } else { + /* Keep the runtime side off a table this pass rejected. */ + efi.poisoned_memory =3D EFI_INVALID_TABLE_ADDR; + } + + early_memunmap(pm, sizeof(*pm)); + + return valid; +} + +static unsigned long __init +efi_poison_offline_unit(const struct efi_poison_geometry *g, u64 unit) +{ + unsigned long pfn =3D PHYS_PFN(unit * g->unit_size); + unsigned long i, nr_pages =3D 0; + + for (i =3D 0; i < g->unit_size >> PAGE_SHIFT; i++) + nr_pages +=3D hwpoison_boot_pfn(pfn + i); + + return nr_pages; +} + +static long __init efi_poison_walk_chunk(const struct efi_poison_geometry = *g, + u64 off, unsigned long *nr_units) +{ + u64 chunk =3D min_t(u64, PAGE_SIZE, g->bitmap_size - off); + unsigned long bit, nbits =3D chunk * BITS_PER_BYTE; + unsigned long *map, nr_pages =3D 0; + + map =3D early_memremap(g->table + offsetof(struct linux_efi_poisoned_memo= ry, + bitmap) + off, chunk); + if (!map) + return -1; + + for_each_set_bit(bit, map, nbits) { + nr_pages +=3D efi_poison_offline_unit(g, off * BITS_PER_BYTE + bit); + (*nr_units)++; + } + + early_memunmap(map, chunk); + + return nr_pages; +} + +static long __init efi_poison_walk(const struct efi_poison_geometry *g, + unsigned long *nr_units) +{ + unsigned long nr_pages =3D 0; + u64 off; + + for (off =3D 0; off < g->bitmap_size; off +=3D PAGE_SIZE) { + long nr =3D efi_poison_walk_chunk(g, off, nr_units); + + if (nr < 0) { + pr_warn("Could not map poisoned-memory bitmap\n"); + return -1; + } + nr_pages +=3D nr; + } + + return nr_pages; +} + +void __init efi_offline_poisoned_memory(void) +{ + struct efi_poison_geometry g; + unsigned long nr_units =3D 0; + long nr_pages, expected; + + if (efi.poisoned_memory =3D=3D EFI_INVALID_TABLE_ADDR) + return; + + if (!efi_poison_read_geometry(efi.poisoned_memory, &g)) + return; + + nr_pages =3D efi_poison_walk(&g, &nr_units); + if (nr_pages < 0) + return; + + if (nr_pages) + pr_info("poisoned %ld page(s) inherited across kexec\n", nr_pages); + + expected =3D nr_units * (g.unit_size >> PAGE_SHIFT); + if (nr_pages < expected) + pr_warn("%ld inherited poisoned page(s) could not be taken out of use\n", + expected - nr_pages); +} diff --git a/include/linux/efi.h b/include/linux/efi.h index d579d75372248..03ba40ff70e7e 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1285,8 +1285,10 @@ struct linux_efi_poisoned_memory { #define EFI_POISON_UNIT_SIZE SZ_2M =20 #ifdef CONFIG_EFI_POISONED_MEMORY +void efi_offline_poisoned_memory(void); void efi_hwpoison_record_pfn(unsigned long pfn); #else +static inline void efi_offline_poisoned_memory(void) { } static inline void efi_hwpoison_record_pfn(unsigned long pfn) { } #endif =20 diff --git a/include/linux/mm.h b/include/linux/mm.h index 52a00a0e090c7..092220943531e 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5104,12 +5104,17 @@ extern const struct attribute_group memory_failure_= attr_group; extern void memory_failure_queue(unsigned long pfn, int flags); void num_poisoned_pages_inc(unsigned long pfn); void num_poisoned_pages_sub(unsigned long pfn, long i); +void __init hwpoison_init_boot(void); bool __init hwpoison_boot_pfn(unsigned long pfn); #else static inline void memory_failure_queue(unsigned long pfn, int flags) { } =20 +static inline void hwpoison_init_boot(void) +{ +} + static inline void num_poisoned_pages_inc(unsigned long pfn) { } diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 713fb2f8e0332..1d6a472267bdb 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -121,6 +121,12 @@ bool __init hwpoison_boot_pfn(unsigned long pfn) return true; } =20 +/* The EFI table is the only source of inherited poison today. */ +void __init hwpoison_init_boot(void) +{ + efi_offline_poisoned_memory(); +} + /** * MF_ATTR_RO - Create sysfs entry for each memory failure statistics. * @_name: name of the file in the per NUMA sysfs directory. diff --git a/mm/mm_init.c b/mm/mm_init.c index ddda9d6837f32..f94fa221da0b8 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -2667,6 +2667,7 @@ void __init mm_core_init(void) kho_memory_init(); =20 memblock_free_all(); + hwpoison_init_boot(); mem_init(); kmem_cache_init(); /* --=20 2.53.0-Meta