* Re: [PATCH mm-unstable v18 04/14] mm/khugepaged: generalize __collapse_huge_page_* for mTHP support
From: David Hildenbrand (Arm) @ 2026-05-22 21:24 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <20260522150009.121603-5-npache@redhat.com>
On 5/22/26 16:59, Nico Pache wrote:
> generalize the order of the __collapse_huge_page_* and collapse_max_*
> functions to support future mTHP collapse.
>
> The current mechanism for determining collapse with the
> khugepaged_max_ptes_none value is not designed with mTHP in mind. This
> raises a key design issue: if we support user defined max_pte_none values
> (even those scaled by order), a collapse of a lower order can introduces
> an feedback loop, or "creep", when max_ptes_none is set to a value greater
> than HPAGE_PMD_NR / 2. [1]
>
> With this configuration, a successful collapse to order N will populate
> enough pages to satisfy the collapse condition on order N+1 on the next
> scan. This leads to unnecessary work and memory churn.
>
> To fix this issue introduce a helper function that will limit mTHP
> collapse support to two max_ptes_none values, 0 and HPAGE_PMD_NR - 1.
> This effectively supports two modes: [2]
>
> - max_ptes_none=0: never collapses if it encounters an empty PTE or a PTE
> that maps the shared zeropage. Consequently, no memory bloat.
> - max_ptes_none=511 (on 4k pagesz): Always collapse to the highest
> available mTHP order.
>
> This removes the possibility of "creep", and a warning will be emitted if
> any non-supported max_ptes_none value is configured with mTHP enabled.
> Any intermediate value will default mTHP collapse to max_ptes_none=0.
>
> mTHP collapse will not honor the khugepaged_max_ptes_shared or
> khugepaged_max_ptes_swap parameters, and will fail if it encounters a
> shared or swapped entry.
>
> No functional changes in this patch; however it defines future behavior
> for mTHP collapse.
>
> [1] - https://lore.kernel.org/all/e46ab3ab-a3d7-4fb7-9970-d0704bd5d05a@arm.com
> [2] - https://lore.kernel.org/all/37375ace-5601-4d6c-9dac-d1c8268698e9@redhat.com
>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Co-developed-by: Dev Jain <dev.jain@arm.com>
> Signed-off-by: Dev Jain <dev.jain@arm.com>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
> mm/khugepaged.c | 121 +++++++++++++++++++++++++++++++++++-------------
> 1 file changed, 88 insertions(+), 33 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 116f39518948..e98ba5b15163 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -353,30 +353,52 @@ static bool pte_none_or_zero(pte_t pte)
> * the shared zeropage for the given collapse operation.
> * @cc: The collapse control struct
> * @vma: The vma to check for userfaultfd
> + * @order: The folio order being collapsed to
> *
> * Return: Maximum number of empty/shared zeropage PTEs for the collapse operation
> */
> static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
> - struct vm_area_struct *vma)
> + struct vm_area_struct *vma, unsigned int order)
> {
> + unsigned int max_ptes_none = khugepaged_max_ptes_none;
Can be const, right?
> +
> if (vma && userfaultfd_armed(vma))
> return 0;
> /* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
> if (!cc->is_khugepaged)
> return HPAGE_PMD_NR;
> - /* For all other cases respect the user defined maximum */
> - return khugepaged_max_ptes_none;
> + /* for PMD collapse, respect the user defined maximum */
> + if (is_pmd_order(order))
> + return max_ptes_none;
> + /*
> + * for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
> + * scale the maximum number of PTEs to the order of the collapse.
> + */
> + if (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)
> + return (1 << order) - 1;
> + if (!max_ptes_none)
> + return 0;
> + /*
> + * For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,
> + * emit a warning and return 0.
> + */
> + pr_warn_once("mTHP collapse does not support max_ptes_none values"
> + " other than 0 or %u, defaulting to 0.\n",
> + KHUGEPAGED_MAX_PTES_LIMIT);
> + return 0;
This might read slightly clearer as
/*
* For mTHP ...
*/
if (max_ptes_none)
pr_warn_once(...)
return 0;
IOW, have a single "return 0" label here and only special-case when to warn.
Acked-by: David Hildenbrand (arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH] ARM: zte: clean up zx297520v3 doc. warnings
From: Randy Dunlap @ 2026-05-22 21:20 UTC (permalink / raw)
To: Stefan Dösinger, linux-kernel
Cc: Linus Walleij, Krzysztof Kozlowski, linux-arm-kernel,
Jonathan Corbet, Shuah Khan, linux-doc
In-Reply-To: <6270885.lOV4Wx5bFT@strix>
On 5/22/26 12:31 PM, Stefan Dösinger wrote:
> Hi Randy,
>
> Am Freitag, 22. Mai 2026, 20:44:24 Ostafrikanische Zeit schrieben Sie:
>> Does this mean that you will be merging this patch since you merged the
>> original patch?
>
> I am new to the kernel development process, so I don't know what's the
> preferred way. I guess for me it is easier if your patch gets merged as-is.
>
> I can certainly submit a pull request myself though since I made myself the
> maintainer for this thing. Does that go to linux-doc@vger.kernel.org or the
> soc list?
The same way that this commit was merged:
commit 220ae5d36dba
Author: Stefan Dösinger <stefandoesinger@gmail.com>
Date: Tue Jan 27 20:52:08 2026 +0300
ARM: zte: Add zx297520v3 platform support
I guess to the soc list.
--
~Randy
^ permalink raw reply
* Re: [PATCH mm-unstable v18 03/14] mm/khugepaged: rework max_ptes_* handling with helper functions
From: David Hildenbrand (Arm) @ 2026-05-22 21:16 UTC (permalink / raw)
To: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel
Cc: aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe, Usama Arif
In-Reply-To: <20260522150009.121603-4-npache@redhat.com>
> int hugepage_madvise(struct vm_area_struct *vma,
> vm_flags_t *vm_flags, int advice)
> {
> @@ -540,6 +598,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
> unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
> struct list_head *compound_pagelist)
> {
> + const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma);
> + const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc);
Yeah, it's good that these are all const now.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-hotfixes-unstable v18 00/14] khugepaged: add mTHP collapse support
From: David Hildenbrand (Arm) @ 2026-05-22 21:13 UTC (permalink / raw)
To: Nico Pache, Vlastimil Babka (SUSE)
Cc: linux-doc, akpm, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, lance.yang, liam, ljs, mathieu.desnoyers,
matthew.brost, mhiramat, mhocko, peterx, pfalcato, rakie.kim,
raquini, rdunlap, richard.weiyang, rientjes, rostedt, rppt,
ryan.roberts, shivankg, sunnanyong, surenb, thomas.hellstrom,
tiwai, usamaarif642, vbabka, vishal.moola, wangkefeng.wang, will,
willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <CAA1CXcD373fFfo9YPWRj8mJ_rsnzyCrpn1uk3=k7kU=QuaLOgg@mail.gmail.com>
On 5/22/26 18:11, Nico Pache wrote:
> On Fri, May 22, 2026 at 9:13 AM Vlastimil Babka (SUSE)
> <vbabka@kernel.org> wrote:
>>
>> On 5/22/26 17:07, Nico Pache wrote:
>>>
>>> Whoops I manually changed the coverletter subject to reflect that this
>>> in on mm-hotfixes-unstable but never updated the others...
>>
>> But why? That branch is for hotfixes that would go to the current 7.1-rcX
>> series. mm-unstable would be the correct one for this, AFAICT.
>
> Sorry this was a misunderstanding. The goal here was to base this off
> the closest base commit behind where my v17 already lies in the tree.
Ah, I guess this is a problem of "v17 is already in mm-unstable, so against what
to base v18".
Yeah, we touched on that problem in the LSF/MM process discussion ...
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-hotfixes-unstable v18 00/14] khugepaged: add mTHP collapse support
From: Andrew Morton @ 2026-05-22 20:47 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
lance.yang, liam, ljs, mathieu.desnoyers, matthew.brost, mhiramat,
mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <20260522150009.121603-1-npache@redhat.com>
On Fri, 22 May 2026 08:59:55 -0600 Nico Pache <npache@redhat.com> wrote:
> The following series provides khugepaged with the capability to collapse
> anonymous memory regions to mTHPs.
Thanks, I've update mm.git's mm-unstable branch to this version.
It sounds like I might be dropping it soon, haven't started looking at
that yet. But let's at least eyeball the latest version at this time.
Sashiko was able to apply this, so the base-it-on-hotfixes thing worked
well, thanks. The AI checking made a few allegations:
https://sashiko.dev/#/patchset/20260522150009.121603-1-npache@redhat.com
> V18 Changes:
> - Added RBs/Acks
> - [patch 02] Guard count_memcg_folio_events with is_pmd_order() to keep
> THP_COLLAPSE_ALLOC PMD-only (Usama, Lance)
> - [patch 03] Convert C++ comments to C-style; fix "none-page" terminology
> to "empty PTEs or PTEs mapping the shared zeropage"; drop unnecessary
> userfaultfd comment; add const to local max_ptes_* variables; fix
> "repect" typo (Lance, David)
> - [patch 04] collapse_max_ptes_none() now returns 0 instead of -EINVAL for
> unsupported values; remove SCAN_INVALID_PTES_NONE; change return type
> from int to unsigned int and propagate to all callers; add comment above
> __collapse_huge_page_swapin explaining mTHP swap bail-out (David,
> Lorenzo, Lance, Wei Yang, Usama)
> - [patch 05] Rewrite collapse_huge_page lock comment to David's suggested
> wording (David)
> - [patch 11] Propagate unsigned int return type for max_ptes_none; remove
> the now-unnecessary negative return check (consequence of patch 04);
> Add optimization to the next_order goto that will prevent unnecessary
> iterations if there are no lower orders enabled (Vernon); update locking
> comment; pass VMA to mthp_collapse to improve uffd-armed detection, and
> prevent unnecessary work. (Wei)
> - [patch 14] Update documentation to reflect fallback-to-0 behavior
>
Below is how v18 altered mm.git.
Quite a lot of it seems to be replacement of "//"-style comments. It's
unfortunate that this work isn't separated from the substantive
changes. We could have done that with a few followup fixes rather than
a wholesale replacement of the series.
Documentation/admin-guide/mm/transhuge.rst | 5
include/trace/events/huge_memory.h | 3
mm/khugepaged.c | 121 +++++++++----------
3 files changed, 66 insertions(+), 63 deletions(-)
--- a/Documentation/admin-guide/mm/transhuge.rst~b
+++ a/Documentation/admin-guide/mm/transhuge.rst
@@ -312,8 +312,9 @@ when collapsing a group of small pages i
For PMD-sized THP collapse, this directly limits the number of empty pages
allowed in the 2MB region.
-For mTHP collapse, only 0 or (HPAGE_PMD_NR - 1) are supported. Any other value
-will emit a warning and no mTHP collapse will be attempted.
+For mTHP collapse, only 0 or (HPAGE_PMD_NR - 1) are supported. At
+HPAGE_PMD_NR - 1, we collapse to the highest possible order. Any intermediate
+value will emit a warning and mTHP collapse will default to max_ptes_none=0.
A higher value allows more empty pages, potentially leading to more memory
usage but better THP performance. A lower value is more conservative and
--- a/include/trace/events/huge_memory.h~b
+++ a/include/trace/events/huge_memory.h
@@ -39,8 +39,7 @@
EM( SCAN_STORE_FAILED, "store_failed") \
EM( SCAN_COPY_MC, "copy_poisoned_page") \
EM( SCAN_PAGE_FILLED, "page_filled") \
- EM(SCAN_PAGE_DIRTY_OR_WRITEBACK, "page_dirty_or_writeback") \
- EMe(SCAN_INVALID_PTES_NONE, "invalid_ptes_none")
+ EMe(SCAN_PAGE_DIRTY_OR_WRITEBACK, "page_dirty_or_writeback")
#undef EM
#undef EMe
--- a/mm/khugepaged.c~b
+++ a/mm/khugepaged.c
@@ -61,7 +61,6 @@ enum scan_result {
SCAN_COPY_MC,
SCAN_PAGE_FILLED,
SCAN_PAGE_DIRTY_OR_WRITEBACK,
- SCAN_INVALID_PTES_NONE,
};
#define CREATE_TRACE_POINTS
@@ -380,41 +379,43 @@ static bool pte_none_or_zero(pte_t pte)
}
/**
- * collapse_max_ptes_none - Calculate maximum allowed none-page or zero-page
- * PTEs for the given collapse operation.
+ * collapse_max_ptes_none - Calculate maximum allowed empty PTEs or PTEs mapping
+ * the shared zeropage for the given collapse operation.
* @cc: The collapse control struct
* @vma: The vma to check for userfaultfd
* @order: The folio order being collapsed to
*
- * Return: Maximum number of none-page or zero-page PTEs allowed for the
- * collapse operation.
+ * Return: Maximum number of empty/shared zeropage PTEs for the collapse operation
*/
-static int collapse_max_ptes_none(struct collapse_control *cc,
+static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
struct vm_area_struct *vma, unsigned int order)
{
unsigned int max_ptes_none = khugepaged_max_ptes_none;
- // If the vma is userfaultfd-armed, allow no none-page or zero-page PTEs.
+
if (vma && userfaultfd_armed(vma))
return 0;
- // for MADV_COLLAPSE, allow any none-page or zero-page PTEs.
+ /* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
if (!cc->is_khugepaged)
return HPAGE_PMD_NR;
- // for PMD collapse, respect the user defined maximum.
+ /* for PMD collapse, respect the user defined maximum */
if (is_pmd_order(order))
return max_ptes_none;
- /* Zero/non-present collapse disabled. */
- if (!max_ptes_none)
- return 0;
- // for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
- // scale the maximum number of PTEs to the order of the collapse.
+ /*
+ * for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
+ * scale the maximum number of PTEs to the order of the collapse.
+ */
if (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)
return (1 << order) - 1;
-
- // We currently only support max_ptes_none values of 0 or KHUGEPAGED_MAX_PTES_LIMIT.
- // Emit a warning and return -EINVAL.
- pr_warn_once("mTHP collapse only supports max_ptes_none values of 0 or %u\n",
- KHUGEPAGED_MAX_PTES_LIMIT);
- return -EINVAL;
+ if (!max_ptes_none)
+ return 0;
+ /*
+ * For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,
+ * emit a warning and return 0.
+ */
+ pr_warn_once("mTHP collapse does not support max_ptes_none values"
+ " other than 0 or %u, defaulting to 0.\n",
+ KHUGEPAGED_MAX_PTES_LIMIT);
+ return 0;
}
/**
@@ -429,15 +430,19 @@ static int collapse_max_ptes_none(struct
static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,
unsigned int order)
{
- // for MADV_COLLAPSE, do not restrict the number of PTEs that map shared
- // anonymous pages.
+ /*
+ * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
+ * anonymous pages.
+ */
if (!cc->is_khugepaged)
return HPAGE_PMD_NR;
- // for mTHP collapse do not allow collapsing anonymous memory pages that
- // are shared between processes.
+ /*
+ * for mTHP collapse do not allow collapsing anonymous memory pages that
+ * are shared between processes.
+ */
if (!is_pmd_order(order))
return 0;
- // for PMD collapse, respect the user defined maximum.
+ /* for PMD collapse, respect the user defined maximum */
return khugepaged_max_ptes_shared;
}
@@ -453,14 +458,16 @@ static unsigned int collapse_max_ptes_sh
static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,
unsigned int order)
{
- // for MADV_COLLAPSE, do not restrict the number PTEs entries or
- // pagecache entries that are non-present.
+ /*
+ * For MADV_COLLAPSE, do not restrict the number PTEs entries or
+ * pagecache entries that are non-present.
+ */
if (!cc->is_khugepaged)
return HPAGE_PMD_NR;
- // for mTHP collapse do not allow any non-present PTEs or pagecache entries.
+ /* for mTHP collapse do not allow any non-present PTEs or pagecache entries */
if (!is_pmd_order(order))
return 0;
- // for PMD collapse, respect the user defined maximum.
+ /* for PMD collapse, respect the user defined maximum */
return khugepaged_max_ptes_swap;
}
@@ -593,9 +600,8 @@ static unsigned long collapse_allowable_
void khugepaged_enter_vma(struct vm_area_struct *vma,
vm_flags_t vm_flags)
{
- if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) &&
- collapse_allowable_orders(vma, vm_flags, TVA_KHUGEPAGED) &&
- hugepage_enabled())
+ if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled()
+ && collapse_allowable_orders(vma, vm_flags, TVA_KHUGEPAGED))
__khugepaged_enter(vma->vm_mm);
}
@@ -670,6 +676,8 @@ static enum scan_result __collapse_huge_
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
unsigned int order, struct list_head *compound_pagelist)
{
+ const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
+ const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
const unsigned long nr_pages = 1UL << order;
struct page *page = NULL;
struct folio *folio = NULL;
@@ -677,11 +685,6 @@ static enum scan_result __collapse_huge_
pte_t *_pte;
int none_or_zero = 0, shared = 0, referenced = 0;
enum scan_result result = SCAN_FAIL;
- int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
- unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
-
- if (max_ptes_none < 0)
- return SCAN_INVALID_PTES_NONE;
for (_pte = pte; _pte < pte + nr_pages;
_pte++, addr += PAGE_SIZE) {
@@ -1136,6 +1139,10 @@ static enum scan_result check_pmd_still_
* Bring missing pages in from swap, to complete THP collapse.
* Only done if khugepaged_scan_pmd believes it is worthwhile.
*
+ * For mTHP orders the function bails on the first swap entry, because
+ * faulting pages back in during collapse could re-populate PTEs that
+ * push a later scan over the threshold for a higher-order collapse.
+ *
* Called and returns without pte mapped or spinlocks held.
* Returns result: if not SCAN_SUCCEED, mmap_lock has been released.
*/
@@ -1257,19 +1264,18 @@ static enum scan_result alloc_charge_fol
return SCAN_CGROUP_CHARGE_FAIL;
}
- count_memcg_folio_events(folio, THP_COLLAPSE_ALLOC, 1);
+ if (is_pmd_order(order))
+ count_memcg_folio_events(folio, THP_COLLAPSE_ALLOC, 1);
*foliop = folio;
return SCAN_SUCCEED;
}
/*
- * collapse_huge_page expects the mmap_read_lock to be dropped before
- * entering this function. The function will also always return with the lock
- * dropped. The function starts by allocation a folio, which can potentially
- * take a long time if it involves sync compaction, and we do not need to hold
- * the mmap_lock during that. We must recheck the vma after taking it again in
- * write mode.
+ * collapse_huge_page expects the mmap_lock to be unlocked before entering and
+ * will always return with the lock unlocked, to avoid holding the mmap_lock
+ * while allocating a THP, as that could trigger direct reclaim/compaction.
+ * Note that the VMA must be rechecked after grabbing the mmap_lock again.
*/
static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,
int referenced, int unmapped, struct collapse_control *cc,
@@ -1500,12 +1506,12 @@ static unsigned int collapse_mthp_count_
* If a collapse is permitted, we attempt to collapse the PTE range into a
* mTHP.
*/
-static int mthp_collapse(struct mm_struct *mm, unsigned long address,
- int referenced, int unmapped, struct collapse_control *cc,
- unsigned long enabled_orders)
+static int mthp_collapse(struct mm_struct *mm, struct vm_area_struct *vma,
+ unsigned long address, int referenced, int unmapped,
+ struct collapse_control *cc, unsigned long enabled_orders)
{
- unsigned int nr_occupied_ptes, nr_ptes;
- int max_ptes_none, collapsed = 0, stack_size = 0;
+ unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
+ int collapsed = 0, stack_size = 0;
unsigned long collapse_address;
struct mthp_range range;
u16 offset;
@@ -1522,10 +1528,7 @@ static int mthp_collapse(struct mm_struc
if (!test_bit(order, &enabled_orders))
goto next_order;
- max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
-
- if (max_ptes_none < 0)
- return collapsed;
+ max_ptes_none = collapse_max_ptes_none(cc, vma, order);
nr_occupied_ptes = collapse_mthp_count_present(cc, offset,
nr_ptes);
@@ -1565,7 +1568,7 @@ static int mthp_collapse(struct mm_struc
}
next_order:
- if (order > KHUGEPAGED_MIN_MTHP_ORDER) {
+ if ((BIT(order) - 1) & enabled_orders) {
const u8 next_order = order - 1;
const u16 mid_offset = offset + (nr_ptes / 2);
@@ -1582,9 +1585,9 @@ static enum scan_result collapse_scan_pm
struct vm_area_struct *vma, unsigned long start_addr,
bool *lock_dropped, struct collapse_control *cc)
{
- int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
+ unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
@@ -1772,9 +1775,9 @@ out_unmap:
if (result == SCAN_SUCCEED) {
/* collapse_huge_page expects the lock to be dropped before calling */
mmap_read_unlock(mm);
- nr_collapsed = mthp_collapse(mm, start_addr, referenced, unmapped,
- cc, enabled_orders);
- /* collapse_huge_page will return with the mmap_lock released */
+ nr_collapsed = mthp_collapse(mm, vma, start_addr, referenced,
+ unmapped, cc, enabled_orders);
+ /* mmap_lock was released above, set lock_dropped */
*lock_dropped = true;
result = nr_collapsed ? SCAN_SUCCEED : SCAN_FAIL;
}
@@ -2665,7 +2668,7 @@ static enum scan_result collapse_scan_fi
unsigned long addr, struct file *file, pgoff_t start,
struct collapse_control *cc)
{
- const int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
+ const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
struct folio *folio = NULL;
struct address_space *mapping = file->f_mapping;
_
^ permalink raw reply
* [PATCH v6 12/12] Documentation: PCI: Add documentation for Live Update
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Add documentation files for the PCI subsystem's participation in Live
Update.
These documentation files are generated from the kernel-doc comments
in the PCI Live Update source code. They describe the File-Lifecycle
Bound (FLB) API, the device tracking API, and the specific policies
applied to preserved devices (such as bus number inheritance and bus
mastering preservation).
Signed-off-by: David Matlack <dmatlack@google.com>
---
Documentation/PCI/index.rst | 1 +
Documentation/PCI/liveupdate.rst | 29 +++++++++++++++++++++++++++
Documentation/core-api/liveupdate.rst | 1 +
MAINTAINERS | 1 +
4 files changed, 32 insertions(+)
create mode 100644 Documentation/PCI/liveupdate.rst
diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
index 5d720d2a415e..23fb737ac969 100644
--- a/Documentation/PCI/index.rst
+++ b/Documentation/PCI/index.rst
@@ -20,3 +20,4 @@ PCI Bus Subsystem
controller/index
boot-interrupts
tph
+ liveupdate
diff --git a/Documentation/PCI/liveupdate.rst b/Documentation/PCI/liveupdate.rst
new file mode 100644
index 000000000000..eba55f8a92ae
--- /dev/null
+++ b/Documentation/PCI/liveupdate.rst
@@ -0,0 +1,29 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+===========================
+PCI Support for Live Update
+===========================
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+ :doc: PCI Live Update
+
+Driver API
+==========
+
+.. kernel-doc:: drivers/pci/liveupdate.c
+ :export:
+
+Live Update ABI
+===============
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+ :doc: PCI File-Lifecycle Bound (FLB) Live Update ABI
+
+.. kernel-doc:: include/linux/kho/abi/pci.h
+ :internal:
+
+See Also
+========
+
+ * :doc:`/core-api/liveupdate`
+ * :doc:`/core-api/kho/index`
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 5a292d0f3706..d56a7760978a 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -70,3 +70,4 @@ See Also
- :doc:`Live Update uAPI </userspace-api/liveupdate>`
- :doc:`/core-api/kho/index`
+- :doc:`PCI </PCI/liveupdate>`
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e262c0ceb43..6f0b0ebf67cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20536,6 +20536,7 @@ L: kexec@lists.infradead.org
L: linux-pci@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F: Documentation/PCI/liveupdate.rst
F: drivers/pci/liveupdate.c
F: drivers/pci/liveupdate.h
F: include/linux/kho/abi/pci.h
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 11/12] PCI: liveupdate: Do not disable bus mastering on preserved devices during kexec
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Do not disable bus mastering on outgoing preserved devices during
pci_device_shutdown() for kexec.
Preserved devices must be allowed to perform memory transactions during
a Live Update to ensure continuous operation. Clearing the bus
mastering bit would prevent these devices from issuing any memory
requests while the new kernel boots.
Because bridges upstream of preserved endpoint devices are also
automatically preserved, this change also avoids clearing bus mastering
on them. This is critical because clearing bus mastering on an upstream
bridge prevents the bridge from forwarding memory requests upstream (i.e.
it would prevent the endpoint device from accessing system RAM and doing
peer-to-peer transactions with devices not downstream of the bridge).
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 11 +++++++++++
drivers/pci/liveupdate.h | 6 ++++++
drivers/pci/pci-driver.c | 7 +++++--
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index d404e64a4e55..a6f2790bc1bf 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -132,6 +132,10 @@
* * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
* preserved devices to ensure that all preserved devices on the bridge's
* secondary bus are addressable after the Live Update.
+ *
+ * * The PCI core does not disable bus mastering on outgoing preserved devices
+ * during kexec. This allows preserved devices to issue memory transactions
+ * throughout the Live Update.
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -790,6 +794,13 @@ int pci_liveupdate_configure_ari(struct pci_dev *dev)
return 0;
}
+bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+ guard(rwsem_read)(&pci_liveupdate.rwsem);
+ pci_WARN_ONCE(dev, !dev->liveupdate.frozen, "Preservation status is unstable!\n");
+ return dev->liveupdate.outgoing;
+}
+
/**
* pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
* @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index bcb0bc73d684..b266406aaac8 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -20,6 +20,7 @@ void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
void pci_liveupdate_init_acs(struct pci_dev *dev);
int pci_liveupdate_enable_acs(struct pci_dev *dev);
int pci_liveupdate_configure_ari(struct pci_dev *dev);
+bool pci_liveupdate_is_outgoing(struct pci_dev *dev);
#else
static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
{
@@ -57,6 +58,11 @@ static inline int pci_liveupdate_configure_ari(struct pci_dev *dev)
{
return -EINVAL;
}
+
+static inline bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
+{
+ return false;
+}
#endif
#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index f7a5e65a7c75..0b1f8d01d7a5 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -546,11 +546,14 @@ static void pci_device_shutdown(struct device *dev)
/*
* If this is a kexec reboot, turn off Bus Master bit on the
* device to tell it to not continue to do DMA. Don't touch
- * devices in D3cold or unknown states.
+ * devices being preserved for Live Update or in D3cold or
+ * unknown states.
+ *
* If it is not a kexec reboot, firmware will hit the PCI
* devices with big hammer and stop their DMA any way.
*/
- if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot))
+ if (kexec_in_progress && !pci_liveupdate_is_outgoing(pci_dev) &&
+ pci_dev->current_state <= PCI_D3hot)
pci_clear_master(pci_dev);
}
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 10/12] PCI: liveupdate: Freeze preservation status during shutdown
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Freeze a device's outgoing preservation status (preserved or not
preserved) during shutdown. This enables the PCI core and drivers to
safely make decisions based on the device's preservation status during
shutdown.
Note that pci_liveupdate_freeze() is triggered by the PCI core rather
than from drivers participating in Live Update so that all devices can
have their status frozen (i.e. prevent non-preserved devices from
getting preserved late).
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 16 ++++++++++++++++
drivers/pci/liveupdate.h | 5 +++++
drivers/pci/pci-driver.c | 2 ++
include/linux/pci_liveupdate.h | 3 +++
4 files changed, 26 insertions(+)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 701276ef6cfb..d404e64a4e55 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -297,6 +297,11 @@ static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev
{
struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
+ if (dev->liveupdate.frozen) {
+ pci_warn(dev, "Cannot unpreserve device after it is frozen!\n");
+ return -EINVAL;
+ }
+
if (!dev_ser) {
pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
return -EINVAL;
@@ -380,6 +385,11 @@ static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev
static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
{
+ if (dev->liveupdate.frozen) {
+ pci_warn(dev, "Cannot preserve device after it is frozen!\n");
+ return -EINVAL;
+ }
+
if (dev->liveupdate.outgoing)
return pci_liveupdate_preserve_device_again(dev);
@@ -653,6 +663,12 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
}
}
+void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+ dev->liveupdate.frozen = 1;
+}
+
static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
{
if (!dev->liveupdate.incoming) {
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 6f21ec50927b..bcb0bc73d684 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,7 @@
#ifdef CONFIG_PCI_LIVEUPDATE
void pci_liveupdate_setup_device(struct pci_dev *dev);
void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+void pci_liveupdate_freeze(struct pci_dev *dev);
bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
int pass);
void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
@@ -28,6 +29,10 @@ static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
{
}
+static inline void pci_liveupdate_freeze(struct pci_dev *dev)
+{
+}
+
static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
struct pci_dev *dev,
int pass)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index d10ece0889f0..f7a5e65a7c75 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -21,6 +21,7 @@
#include <linux/acpi.h>
#include <linux/dma-map-ops.h>
#include <linux/iommu.h>
+#include "liveupdate.h"
#include "pci.h"
#include "pcie/portdrv.h"
@@ -536,6 +537,7 @@ static void pci_device_shutdown(struct device *dev)
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = pci_dev->driver;
+ pci_liveupdate_freeze(pci_dev);
pm_runtime_resume(dev);
if (drv && drv->shutdown)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 2446c6d237ca..150993405754 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -24,6 +24,8 @@
* @was_preserved: True if this struct pci_dev was preserved by the previous
* kernel. Unlike @incoming, this field is not cleared after
* the device is finished participating in Live Update.
+ * @frozen: True if the outgoing preservation status of this device is frozen
+ * and thus cannot be changed.
*/
struct pci_liveupdate {
struct pci_dev_ser *outgoing;
@@ -31,6 +33,7 @@ struct pci_liveupdate {
u16 acs_ctrl;
bool inherit_buses;
bool was_preserved;
+ bool frozen;
};
struct pci_dev;
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 09/12] PCI: liveupdate: Inherit ARI Forwarding Enable on preserved bridges
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Inherit the ARI Forwarding Enable on preserved bridges and update
pci_dev->ari_enabled accordingly during a Live Update. This ensures that
the preserved devices on the bridge's secondary bus can be identified
with the same expanded 8-bit function number after a Live Update.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 18 ++++++++++++++++++
drivers/pci/liveupdate.h | 6 ++++++
drivers/pci/pci.c | 8 +++++++-
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index a93b7ef065f2..701276ef6cfb 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -128,6 +128,10 @@
* way after Live Update and ensures that IOMMU groups do not change. Note
* that a device will use its inherited ACS flags for the lifetime of its
* struct pci_dev (i.e. even after pci_liveupdate_finish()).
+ *
+ * * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
+ * preserved devices to ensure that all preserved devices on the bridge's
+ * secondary bus are addressable after the Live Update.
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -756,6 +760,20 @@ int pci_liveupdate_enable_acs(struct pci_dev *dev)
return 0;
}
+int pci_liveupdate_configure_ari(struct pci_dev *dev)
+{
+ u16 val;
+
+ guard(rwsem_read)(&pci_liveupdate.rwsem);
+
+ if (!dev->liveupdate.incoming)
+ return -EINVAL;
+
+ pcie_capability_read_word(dev, PCI_EXP_DEVCTL2, &val);
+ dev->ari_enabled = !!(val & PCI_EXP_DEVCTL2_ARI);
+ return 0;
+}
+
/**
* pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
* @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index 4e8a01bcb4bb..6f21ec50927b 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -18,6 +18,7 @@ bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
void pci_liveupdate_init_acs(struct pci_dev *dev);
int pci_liveupdate_enable_acs(struct pci_dev *dev);
+int pci_liveupdate_configure_ari(struct pci_dev *dev);
#else
static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
{
@@ -46,6 +47,11 @@ static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)
{
return -EINVAL;
}
+
+static inline int pci_liveupdate_configure_ari(struct pci_dev *dev)
+{
+ return -EINVAL;
+}
#endif
#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 211df4618164..271da55af270 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -3495,7 +3495,7 @@ void pci_configure_ari(struct pci_dev *dev)
u32 cap;
struct pci_dev *bridge;
- if (pcie_ari_disabled || !pci_is_pcie(dev) || dev->devfn)
+ if (!pci_is_pcie(dev) || dev->devfn)
return;
bridge = dev->bus->self;
@@ -3506,6 +3506,12 @@ void pci_configure_ari(struct pci_dev *dev)
if (!(cap & PCI_EXP_DEVCAP2_ARI))
return;
+ if (!pci_liveupdate_configure_ari(bridge))
+ return;
+
+ if (pcie_ari_disabled)
+ return;
+
if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ARI)) {
pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2,
PCI_EXP_DEVCTL2_ARI);
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 08/12] PCI: liveupdate: Inherit ACS flags in incoming preserved devices
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Inherit Access Control Services (ACS) flags on all incoming preserved
devices (endpoints and upstream bridges) during a Live Update.
Inheriting ACS flags avoids changing routing rules while memory
transactions are in flight from preserved devices. This is also strictly
necessary to ensure that IOMMU group assignments do not change across
a Live Update for preserved devices, as changing ACS configurations can
split or merge IOMMU groups.
Cache the inherited ACS controls established by the previous kernel in
struct pci_dev so that ACS controls do not change after a reset
(pci_restore_state() calls pci_enable_acs()).
To simplify ACS inheritance, reject preserving any devices that require
quirks to enable ACS as those quirks would also have to take Live Update
into account.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 68 ++++++++++++++++++++++++++++++++++
drivers/pci/liveupdate.h | 11 ++++++
drivers/pci/pci.c | 5 +++
drivers/pci/pci.h | 5 +++
drivers/pci/quirks.c | 7 ++++
include/linux/pci_liveupdate.h | 6 +++
6 files changed, 102 insertions(+)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 4c79e19b7f98..a93b7ef065f2 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -71,6 +71,9 @@
*
* * The device cannot be a Virtual Function (VF).
*
+ * * The device cannot require device-specific quirks to enable Access
+ * Control Services (ACS).
+ *
* Driver Binding
* ==============
*
@@ -113,6 +116,18 @@
* This enables the PCI core and any drivers bound to the bridge to participate
* in the Live Update so that preserved endpoints can continue issuing memory
* transactions during the Live Update.
+ *
+ * Handling Preserved Devices
+ * ==========================
+ *
+ * The PCI core treats preserved devices differently than non-preserved devices.
+ * This section enumerates those differences.
+ *
+ * * The PCI core inherits all ACS flags enabled on incoming preserved devices
+ * rather than assigning new ones. This ensures that TLPs are routed the same
+ * way after Live Update and ensures that IOMMU groups do not change. Note
+ * that a device will use its inherited ACS flags for the lifetime of its
+ * struct pci_dev (i.e. even after pci_liveupdate_finish()).
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -126,6 +141,7 @@
#include <linux/pci.h>
#include "liveupdate.h"
+#include "pci.h"
/**
* struct pci_liveupdate_global - Global state for PCI Live Update support
@@ -319,6 +335,16 @@ static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev
{
int i;
+ /*
+ * Do not preserve devices that rely on device-specific ACS equivalents
+ * (for now) since that would complicate keeping ACS constant across
+ * Live Update.
+ */
+ if (pci_need_dev_specific_enable_acs(dev)) {
+ pci_warn(dev, "Refusing to preserve device that relies on ACS quirks\n");
+ return -EINVAL;
+ }
+
if (ser->nr_devices == ser->max_nr_devices)
return -ENOSPC;
@@ -598,6 +624,7 @@ void pci_liveupdate_setup_device(struct pci_dev *dev)
pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
dev->liveupdate.incoming = dev_ser;
+ dev->liveupdate.was_preserved = true;
/*
* Hold the ref on the incoming FLB until pci_liveupdate_finish() so
@@ -688,6 +715,47 @@ void pci_liveupdate_finish(struct pci_dev *dev)
}
EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
+void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+ guard(rwsem_read)(&pci_liveupdate.rwsem);
+
+ if (!dev->acs_cap || !dev->liveupdate.incoming)
+ return;
+
+ pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, &dev->liveupdate.acs_ctrl);
+}
+
+int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+ u16 acs_ctrl = dev->liveupdate.acs_ctrl;
+ u16 acs_cap = dev->acs_cap;
+
+ /*
+ * Use liveupdate.was_preserved instead of liveupdate.incoming since the
+ * device's ACS controls should not change even after the device is
+ * finished participating in the Live Update.
+ */
+ if (!dev->liveupdate.was_preserved)
+ return -EINVAL;
+
+ /*
+ * The previous kernel should not have preserved any devices that
+ * require device-specific quirks to enable ACS, but if such a device is
+ * detected, log a big warning and fall back to the normal enable ACS
+ * path.
+ */
+ if (pci_need_dev_specific_enable_acs(dev)) {
+ pci_warn(dev, "Device-specific quirk required to enable ACS!\n");
+ WARN_ON_ONCE(true);
+ return -EINVAL;
+ }
+
+ if (acs_cap)
+ pci_write_config_word(dev, acs_cap + PCI_ACS_CTRL, acs_ctrl);
+
+ return 0;
+}
+
/**
* pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
* @dev: The PCI device to check
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index c763255a8de4..4e8a01bcb4bb 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -16,6 +16,8 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev);
bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
int pass);
void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
+void pci_liveupdate_init_acs(struct pci_dev *dev);
+int pci_liveupdate_enable_acs(struct pci_dev *dev);
#else
static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
{
@@ -35,6 +37,15 @@ static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
{
}
+
+static inline void pci_liveupdate_init_acs(struct pci_dev *dev)
+{
+}
+
+static inline int pci_liveupdate_enable_acs(struct pci_dev *dev)
+{
+ return -EINVAL;
+}
#endif
#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 8f7cfcc00090..211df4618164 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -33,6 +33,7 @@
#include <asm/dma.h>
#include <linux/aer.h>
#include <linux/bitfield.h>
+#include "liveupdate.h"
#include "pci.h"
DEFINE_MUTEX(pci_slot_mutex);
@@ -1017,6 +1018,9 @@ void pci_enable_acs(struct pci_dev *dev)
bool enable_acs = false;
int pos;
+ if (!pci_liveupdate_enable_acs(dev))
+ return;
+
/* If an iommu is present we start with kernel default caps */
if (pci_acs_enable) {
if (pci_dev_specific_enable_acs(dev))
@@ -3657,6 +3661,7 @@ void pci_acs_init(struct pci_dev *dev)
pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities);
pci_disable_broken_acs_cap(dev);
+ pci_liveupdate_init_acs(dev);
}
/**
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4a14f88e543a..b55f3deddd57 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1062,6 +1062,7 @@ void pci_acs_init(struct pci_dev *dev);
void pci_enable_acs(struct pci_dev *dev);
#ifdef CONFIG_PCI_QUIRKS
int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags);
+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev);
int pci_dev_specific_enable_acs(struct pci_dev *dev);
int pci_dev_specific_disable_acs_redir(struct pci_dev *dev);
void pci_disable_broken_acs_cap(struct pci_dev *pdev);
@@ -1072,6 +1073,10 @@ static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev,
{
return -ENOTTY;
}
+static inline bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)
+{
+ return false;
+}
static inline int pci_dev_specific_enable_acs(struct pci_dev *dev)
{
return -ENOTTY;
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 171caec2bc47..59b0b19c3783 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5482,6 +5482,13 @@ static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev)
return NULL;
}
+bool pci_need_dev_specific_enable_acs(struct pci_dev *dev)
+{
+ const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
+
+ return p && p->enable_acs;
+}
+
int pci_dev_specific_enable_acs(struct pci_dev *dev)
{
const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 2be98819e313..2446c6d237ca 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -17,14 +17,20 @@
* struct pci_liveupdate - PCI Live Update state for a struct pci_dev
* @outgoing: State preserved for the next kernel.
* @incoming: State preserved by the previous kernel.
+ * @acs_ctrl: ACS features established by the previous kernel.
* @inherit_buses: True if the PCI core should inherit the secondary and
* subordinate bus numbers assigned to this device due to
* an ongoing Live Update.
+ * @was_preserved: True if this struct pci_dev was preserved by the previous
+ * kernel. Unlike @incoming, this field is not cleared after
+ * the device is finished participating in Live Update.
*/
struct pci_liveupdate {
struct pci_dev_ser *outgoing;
struct pci_dev_ser *incoming;
+ u16 acs_ctrl;
bool inherit_buses;
+ bool was_preserved;
};
struct pci_dev;
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 07/12] PCI: Refactor matching logic for pci_dev_acs_ops
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Refactor the logic to match devices to pci_dev_acs_ops by factoring out
the loop and device matching into its own routine. This eliminates some
duplicate code between pci_dev_specific_enable_acs() and
pci_dev_specific_disable_acs_redir(), and will also be used in a
subsequent commit to check if a device requires device-specific
enable_acs() during a Live Update.
No functional change intended.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/quirks.c | 50 ++++++++++++++++++--------------------------
1 file changed, 20 insertions(+), 30 deletions(-)
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index caaed1a01dc0..171caec2bc47 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5384,9 +5384,6 @@ static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev)
*/
static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev)
{
- if (!pci_quirk_intel_pch_acs_match(dev))
- return -ENOTTY;
-
if (pci_quirk_enable_intel_lpc_acs(dev)) {
pci_warn(dev, "Failed to enable Intel PCH ACS quirk\n");
return 0;
@@ -5406,9 +5403,6 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev)
int pos;
u32 cap, ctrl;
- if (!pci_quirk_intel_spt_pch_acs_match(dev))
- return -ENOTTY;
-
pos = dev->acs_cap;
if (!pos)
return -ENOTTY;
@@ -5436,9 +5430,6 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)
int pos;
u32 cap, ctrl;
- if (!pci_quirk_intel_spt_pch_acs_match(dev))
- return -ENOTTY;
-
pos = dev->acs_cap;
if (!pos)
return -ENOTTY;
@@ -5458,22 +5449,25 @@ static int pci_quirk_disable_intel_spt_pch_acs_redir(struct pci_dev *dev)
static const struct pci_dev_acs_ops {
u16 vendor;
u16 device;
+ bool (*match)(struct pci_dev *dev);
int (*enable_acs)(struct pci_dev *dev);
int (*disable_acs_redir)(struct pci_dev *dev);
} pci_dev_acs_ops[] = {
{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
+ .match = pci_quirk_intel_pch_acs_match,
.enable_acs = pci_quirk_enable_intel_pch_acs,
},
{ PCI_VENDOR_ID_INTEL, PCI_ANY_ID,
+ .match = pci_quirk_intel_spt_pch_acs_match,
.enable_acs = pci_quirk_enable_intel_spt_pch_acs,
.disable_acs_redir = pci_quirk_disable_intel_spt_pch_acs_redir,
},
};
-int pci_dev_specific_enable_acs(struct pci_dev *dev)
+static const struct pci_dev_acs_ops *pci_dev_acs_ops_get(struct pci_dev *dev)
{
const struct pci_dev_acs_ops *p;
- int i, ret;
+ int i;
for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
p = &pci_dev_acs_ops[i];
@@ -5481,33 +5475,29 @@ int pci_dev_specific_enable_acs(struct pci_dev *dev)
p->vendor == (u16)PCI_ANY_ID) &&
(p->device == dev->device ||
p->device == (u16)PCI_ANY_ID) &&
- p->enable_acs) {
- ret = p->enable_acs(dev);
- if (ret >= 0)
- return ret;
- }
+ p->match(dev))
+ return p;
}
+ return NULL;
+}
+
+int pci_dev_specific_enable_acs(struct pci_dev *dev)
+{
+ const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
+
+ if (p && p->enable_acs)
+ return p->enable_acs(dev);
+
return -ENOTTY;
}
int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
{
- const struct pci_dev_acs_ops *p;
- int i, ret;
+ const struct pci_dev_acs_ops *p = pci_dev_acs_ops_get(dev);
- for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) {
- p = &pci_dev_acs_ops[i];
- if ((p->vendor == dev->vendor ||
- p->vendor == (u16)PCI_ANY_ID) &&
- (p->device == dev->device ||
- p->device == (u16)PCI_ANY_ID) &&
- p->disable_acs_redir) {
- ret = p->disable_acs_redir(dev);
- if (ret >= 0)
- return ret;
- }
- }
+ if (p && p->disable_acs_redir)
+ return p->disable_acs_redir(dev);
return -ENOTTY;
}
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 06/12] PCI: liveupdate: Auto-preserve upstream bridges across Live Update
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
When a PCI device is preserved across a Live Update, all of its upstream
bridges up to the root port must also be preserved. This enables the PCI
core and any drivers bound to the bridges to manage bridges correctly
across a Live Update.
Notably, this will be used in subsequent commits to ensure that
preserved devices can continue performing memory transactions without a
disruption or change in routing.
To preserve bridges, the PCI core tracks the number of downstream
devices preserved under each bridge using a reference count in struct
pci_dev_ser. This allows a bridge to remain preserved until all its
downstream preserved devices are unpreserved or finish their
participation in the Live Update.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 136 +++++++++++++++++++++++++++++++-----
include/linux/kho/abi/pci.h | 5 +-
2 files changed, 122 insertions(+), 19 deletions(-)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 2421bc218916..4c79e19b7f98 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -101,6 +101,18 @@
* If a misconfigured or unconfigured bridge is encountered during enumeration
* while there are preserved devices, its secondary and subordinate bus numbers
* will be cleared and devices below it will not be enumerated.
+ *
+ * PCI-to-PCI Bridges
+ * ==================
+ *
+ * Any PCI-to-PCI bridges upstream of a preserved device are automatically
+ * preserved when the device is preserved. The PCI core keeps track of the
+ * number of downstream devices that are preserved under a bridge so that the
+ * bridge is only unpreserved once all downstream devices are unpreserved.
+ *
+ * This enables the PCI core and any drivers bound to the bridge to participate
+ * in the Live Update so that preserved endpoints can continue issuing memory
+ * transactions during the Live Update.
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -261,28 +273,52 @@ static struct pci_ser *pci_liveupdate_flb_get_outgoing(void)
return ser;
}
-static void pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
{
struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
if (!dev_ser) {
pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
- return;
+ return -EINVAL;
+ }
+
+ if (!dev_ser->refcount) {
+ pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+ return -EINVAL;
}
+ if (--dev_ser->refcount)
+ return 0;
+
pci_info(dev, "Device will no longer be preserved across next Live Update\n");
ser->nr_devices--;
memset(dev_ser, 0, sizeof(*dev_ser));
dev->liveupdate.outgoing = NULL;
+ return 0;
}
-static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_preserve_device_again(struct pci_dev *dev)
{
- int i;
+ if (!dev->liveupdate.outgoing->refcount) {
+ pci_WARN(dev, 1, "Preserved device with 0 refcount!\n");
+ return -EINVAL;
+ }
- if (dev->liveupdate.outgoing)
+ /*
+ * Endpoint devices should not be preserved more than once. Bridges are
+ * preserved once for every downstream device that is preserved.
+ */
+ if (!dev->subordinate)
return -EBUSY;
+ dev->liveupdate.outgoing->refcount++;
+ return 0;
+}
+
+static int __pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+ int i;
+
if (ser->nr_devices == ser->max_nr_devices)
return -ENOSPC;
@@ -312,6 +348,52 @@ static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *d
return -ENOSPC;
}
+static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+ if (dev->liveupdate.outgoing)
+ return pci_liveupdate_preserve_device_again(dev);
+
+ return __pci_liveupdate_preserve_device(ser, dev);
+}
+
+#define for_each_pci_dev_in_path(_d, _start, _end) \
+ for ((_d) = (_start); (_d) != (_end); (_d) = (_d)->bus->self)
+
+static void __pci_liveupdate_unpreserve_path(struct pci_ser *ser,
+ struct pci_dev *start,
+ struct pci_dev *end)
+{
+ struct pci_dev *dev;
+
+ for_each_pci_dev_in_path(dev, start, end) {
+ if (pci_liveupdate_unpreserve_device(ser, dev))
+ return;
+ }
+}
+
+static void pci_liveupdate_unpreserve_path(struct pci_ser *ser,
+ struct pci_dev *start)
+{
+ __pci_liveupdate_unpreserve_path(ser, start, /*end=*/NULL);
+}
+
+static int pci_liveupdate_preserve_path(struct pci_ser *ser,
+ struct pci_dev *start)
+{
+ struct pci_dev *dev;
+ int ret;
+
+ for_each_pci_dev_in_path(dev, start, NULL) {
+ ret = pci_liveupdate_preserve_device(ser, dev);
+ if (ret) {
+ __pci_liveupdate_unpreserve_path(ser, start, dev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
/**
* pci_liveupdate_preserve() - Preserve a PCI device across Live Update
* @dev: The PCI device to preserve.
@@ -321,6 +403,9 @@ static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *d
* pci_liveupdate_preserve() from their struct liveupdate_file_handler
* preserve() callback to ensure the outgoing struct pci_ser is already set up.
*
+ * pci_liveupdate_preserve() automatically preserves all bridges upstream of
+ * @dev.
+ *
* Returns: 0 on success, <0 on failure.
*/
int pci_liveupdate_preserve(struct pci_dev *dev)
@@ -336,7 +421,7 @@ int pci_liveupdate_preserve(struct pci_dev *dev)
if (IS_ERR(ser))
return PTR_ERR(ser);
- return pci_liveupdate_preserve_device(ser, dev);
+ return pci_liveupdate_preserve_path(ser, dev);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
@@ -349,6 +434,9 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
* pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
* unpreserve() callback to ensure the outgoing struct pci_ser is already set
* up.
+ *
+ * pci_liveupdate_unpreserve() automatically unpreserves all bridges upstream of
+ * @dev.
*/
void pci_liveupdate_unpreserve(struct pci_dev *dev)
{
@@ -362,7 +450,7 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
return;
}
- pci_liveupdate_unpreserve_device(ser, dev);
+ pci_liveupdate_unpreserve_path(ser, dev);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
@@ -534,29 +622,41 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
}
}
-static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
+static int pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
{
if (!dev->liveupdate.incoming) {
pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
- return;
+ return -EINVAL;
}
- if (dev->liveupdate.incoming->refcount != 1) {
- pci_WARN(dev, 1, "Preserved device has a corrupted refcount!\n");
- return;
+ if (!dev->liveupdate.incoming->refcount) {
+ pci_WARN(dev, 1, "Preserved device has a 0 refcount!\n");
+ return -EINVAL;
}
/*
- * Drop the refcount so this device does not get treated as an incoming
- * device again, e.g. in case pci_liveupdate_setup_device() gets called
- * again because the device is hot-plugged.
+ * Decrement the refcount so this device does not get treated as an
+ * incoming device again, e.g. in case pci_liveupdate_setup_device()
+ * gets called again because the device is hot-plugged.
*/
- dev->liveupdate.incoming->refcount = 0;
+ if (--dev->liveupdate.incoming->refcount)
+ return 0;
pci_info(dev, "Device is finished participating in Live Update\n");
dev->liveupdate.incoming = NULL;
ser->nr_devices--;
pci_liveupdate_flb_put_incoming();
+ return 0;
+}
+
+static void pci_liveupdate_finish_path(struct pci_ser *ser, struct pci_dev *start)
+{
+ struct pci_dev *dev;
+
+ for_each_pci_dev_in_path(dev, start, NULL) {
+ if (pci_liveupdate_finish_device(ser, dev))
+ return;
+ }
}
/**
@@ -568,6 +668,8 @@ static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *de
* Update. Drivers must call pci_liveupdate_finish() from their struct
* liveupdate_file_handler finish() callback to ensure the incoming struct
* pci_ser is allocated.
+ *
+ * pci_liveupdate_finish() automatically finishes all bridges upstream of @dev.
*/
void pci_liveupdate_finish(struct pci_dev *dev)
{
@@ -581,7 +683,7 @@ void pci_liveupdate_finish(struct pci_dev *dev)
return;
}
- pci_liveupdate_finish_device(incoming->ser, dev);
+ pci_liveupdate_finish_path(incoming->ser, dev);
pci_liveupdate_flb_put_incoming();
}
EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 85def616703d..c86518be4ce7 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,7 +23,7 @@
* incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
*/
-#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v3"
/**
* struct pci_dev_ser - Serialized state about a single PCI device.
@@ -32,7 +32,8 @@
* @bdf: The device's PCI bus, device, and function number.
* @refcount: Reference count used by the PCI core to keep track of whether it
* is done using a device's struct pci_dev_ser. The value of the
- * refcount is equal to 1 when the struct pci_dev_ser is in use, and
+ * refcount is equal to the number of preserved devices at or below
+ * it in the PCI hierarchy when the struct pci_dev_ser is in use, and
* 0 otherwise.
*/
struct pci_dev_ser {
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 05/12] PCI: liveupdate: Keep bus numbers constant during Live Update
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
During a Live Update, preserved devices must be allowed to continue
performing memory transactions so the kernel cannot change the fabric
topology, including bus numbers, since that would require disabling
and flushing any memory transactions first.
To keep bus numbers constant, always inherit the secondary and
subordinate bus numbers assigned to bridges during scanning, instead of
assigning new ones, if any PCI devices are being preserved. Note that
the kernel inherits bus numbers even on bridges without any downstream
endpoints that were preserved. This avoids accidentally assigning a
bridge a new window that overlaps with a preserved device that is
downstream of a different bridge.
If a bridge is scanned with a broken topology or has no bus numbers
set during a Live Update, refuse to assign it new bus numbers and refuse
to enumerate devices below it until the Live Update is finished. This is
a safety measure to prevent topology conflicts.
Require that CONFIG_CARDBUS is not enabled to enable
CONFIG_PCI_LIVEUPDATE since inheriting bus numbers on PCI-to-CardBus
bridges requires additional work but is not a priority at the moment.
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../admin-guide/kernel-parameters.txt | 6 +-
drivers/pci/Kconfig | 2 +-
drivers/pci/liveupdate.c | 83 ++++++++++++++++++-
drivers/pci/liveupdate.h | 14 ++++
drivers/pci/probe.c | 17 +++-
include/linux/pci_liveupdate.h | 4 +
6 files changed, 119 insertions(+), 7 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 4d0f545fb3ec..a64af71c2705 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -5138,7 +5138,11 @@ Kernel parameters
explicitly which ones they are.
assign-busses [X86] Always assign all PCI bus
numbers ourselves, overriding
- whatever the firmware may have done.
+ whatever the firmware may have done. Ignored
+ during a Live Update, where the kernel must
+ inherit the PCI topology (including bus numbers)
+ to avoid interrupting ongoing memory
+ transactions of preserved devices.
usepirqmask [X86] Honor the possible IRQ mask stored
in the BIOS $PIR table. This is needed on
some systems with broken BIOSes, notably
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index e68ae5c172d4..a597fede1b3b 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
config PCI_LIVEUPDATE
bool "PCI Live Update Support"
- depends on PCI && LIVEUPDATE && 64BIT
+ depends on PCI && LIVEUPDATE && 64BIT && !CARDBUS
help
Enable PCI core support for preserving PCI devices across Live
Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 4f2ec6ffdd16..2421bc218916 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -86,6 +86,21 @@
* bound to the correct driver. i.e. The PCI core does not protect against a
* device getting preserved by driver A in the outgoing kernel and then getting
* bound to driver B in the incoming kernel.
+ *
+ * BDF Stability
+ * =============
+ *
+ * The PCI core guarantees that preserved devices can be identified by the same
+ * bus, device, and function numbers for as long as they are preserved
+ * (including across kexec). To accomplish this, the PCI core always inherits
+ * the secondary and subordinate bus numbers assigned to bridges during scanning
+ * if any device is preserved. This is true even on architectures that always
+ * assign new bus numbers during scanning. The kernel assumes the previous
+ * kernel established a sane bus topology across kexec.
+ *
+ * If a misconfigured or unconfigured bridge is encountered during enumeration
+ * while there are preserved devices, its secondary and subordinate bus numbers
+ * will be cleared and devices below it will not be enumerated.
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -103,7 +118,7 @@
/**
* struct pci_liveupdate_global - Global state for PCI Live Update support
* @rwsem: Reader/writer semaphore used to protect the incoming and outgoing
- * FLBs, and the references to them in struct pci_dev.
+ * FLBs and references to them in struct pci_dev.
*/
struct pci_liveupdate_global {
struct rw_semaphore rwsem;
@@ -396,6 +411,72 @@ static void pci_liveupdate_flb_put_incoming(void)
liveupdate_flb_put_incoming(&pci_liveupdate_flb);
}
+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+ int pass)
+{
+ struct pci_dev *parent = bus->self;
+
+ /*
+ * On the second pass, reuse the value that was set on the first pass
+ * so that the passes are consistent with one another.
+ */
+ if (pass)
+ return dev->liveupdate.inherit_buses;
+
+ /*
+ * If the parent bridge is being forced to inherit its bus numbers
+ * during this scan then this bridge must as well, otherwise the PCI
+ * core could expand this bridge's reservation beyond its parent (which
+ * cannot expand).
+ */
+ if (parent && parent->liveupdate.inherit_buses) {
+ dev->liveupdate.inherit_buses = true;
+ goto out;
+ }
+
+ /*
+ * Otherwise, if there are any incoming preserved devices, force the
+ * bus numbers to be inherited to avoid changing the bus numbers
+ * assigned to those devices during enumeration.
+ *
+ * To keep things simple, inherit bus numbers on all bridges if any PCI
+ * devices are incoming, to ensure that no bridge's reservation is
+ * expanded to overlap with a preserved device downstream of a different
+ * bridge.
+ */
+ scoped_guard(rwsem_read, &pci_liveupdate.rwsem) {
+ struct pci_flb_incoming *incoming;
+
+ incoming = pci_liveupdate_flb_get_incoming();
+ if (!incoming) {
+ dev->liveupdate.inherit_buses = false;
+ goto out;
+ }
+
+ /*
+ * It is safe to sample incoming->ser->nr_devices and then
+ * drop the rwsem since nr_devices will only decrease. Thus the
+ * only "race" is that the current scan will be overly
+ * conservative and force bus inheritance.
+ */
+ dev->liveupdate.inherit_buses = incoming->ser->nr_devices;
+ pci_liveupdate_flb_put_incoming();
+ }
+
+out:
+ return dev->liveupdate.inherit_buses;
+}
+
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+ /*
+ * Clear inherit_buses after the second pass so it can be re-evaluated
+ * on future scans.
+ */
+ if (pass)
+ dev->liveupdate.inherit_buses = false;
+}
+
void pci_liveupdate_setup_device(struct pci_dev *dev)
{
struct pci_flb_incoming *incoming;
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index eaaa3559fd77..c763255a8de4 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -13,6 +13,9 @@
#ifdef CONFIG_PCI_LIVEUPDATE
void pci_liveupdate_setup_device(struct pci_dev *dev);
void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus, struct pci_dev *dev,
+ int pass);
+void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass);
#else
static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
{
@@ -21,6 +24,17 @@ static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
{
}
+
+static inline bool pci_liveupdate_scan_bridge_begin(struct pci_bus *bus,
+ struct pci_dev *dev,
+ int pass)
+{
+ return false;
+}
+
+static inline void pci_liveupdate_scan_bridge_end(struct pci_dev *dev, int pass)
+{
+}
#endif
#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 2e2be8af6976..19965bfd347d 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1402,6 +1402,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
int max, unsigned int available_buses,
int pass)
{
+ bool liveupdate, assign_new_buses = pcibios_assign_all_busses();
struct pci_bus *child;
u32 buses;
u16 bctl;
@@ -1411,6 +1412,10 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
u8 fixed_sec, fixed_sub;
int next_busnr;
+ liveupdate = pci_liveupdate_scan_bridge_begin(bus, dev, pass);
+ if (liveupdate)
+ assign_new_buses = false;
+
/*
* Make sure the bridge is powered on to be able to access config
* space of devices below it.
@@ -1454,8 +1459,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
goto out;
}
- if ((secondary || subordinate) &&
- !pcibios_assign_all_busses() && !broken) {
+ if ((secondary || subordinate) && !assign_new_buses && !broken) {
unsigned int cmax, buses;
/*
@@ -1497,8 +1501,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
* do in the second pass.
*/
if (!pass) {
- if (pcibios_assign_all_busses() || broken)
-
+ if (assign_new_buses || broken)
/*
* Temporarily disable forwarding of the
* configuration cycles on all bridges in
@@ -1512,6 +1515,11 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
goto out;
}
+ if (liveupdate) {
+ pci_err(dev, "Cannot reconfigure bridge during Live Update, skipping\n");
+ goto out;
+ }
+
/* Clear errors */
pci_write_config_word(dev, PCI_STATUS, 0xffff);
@@ -1572,6 +1580,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);
pm_runtime_put(&dev->dev);
+ pci_liveupdate_scan_bridge_end(dev, pass);
return max;
}
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index cfdc3d62ec02..2be98819e313 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -17,10 +17,14 @@
* struct pci_liveupdate - PCI Live Update state for a struct pci_dev
* @outgoing: State preserved for the next kernel.
* @incoming: State preserved by the previous kernel.
+ * @inherit_buses: True if the PCI core should inherit the secondary and
+ * subordinate bus numbers assigned to this device due to
+ * an ongoing Live Update.
*/
struct pci_liveupdate {
struct pci_dev_ser *outgoing;
struct pci_dev_ser *incoming;
+ bool inherit_buses;
};
struct pci_dev;
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 04/12] PCI: liveupdate: Document driver binding responsibilities
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Document how driver binding works during a Live Update and what the PCI
core expects of drivers and users. Note that this is only a description
of the current division of responsibilities. These can change in the
future if we decide.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 96c43b84532c..4f2ec6ffdd16 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -70,6 +70,22 @@
* preserved. These may be relaxed in the future:
*
* * The device cannot be a Virtual Function (VF).
+ *
+ * Driver Binding
+ * ==============
+ *
+ * In the outgoing kernel, it is the driver's responsibility to ensure that it
+ * does not release a device between pci_liveupdate_preserve() and
+ * pci_liveupdate_unpreserve().
+ *
+ * In the incoming kernel, it is the driver's responsibility to ensure that it
+ * does not release a preserved device between probe() and
+ * pci_liveupdate_finish().
+ *
+ * It is the user's responsibility to ensure that incoming preserved devices are
+ * bound to the correct driver. i.e. The PCI core does not protect against a
+ * device getting preserved by driver A in the outgoing kernel and then getting
+ * bound to driver B in the incoming kernel.
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 03/12] PCI: liveupdate: Track incoming preserved PCI devices
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
During PCI enumeration, the previous kernel might have passed state about
devices that were preserved across kexec. The PCI core needs to fetch
this state to identify which devices are "incoming" and require special
handling.
Add pci_liveupdate_setup_device() which is called during device setup
to fetch the serialized state (struct pci_ser) from the Live Update
Orchestrator. The first time this happens, pci_flb_retrieve() will run
and convert the array of pci_dev_ser structs into an xarray so that it
can be looked up efficiently.
If a device is found in the xarray, the PCI core stores a pointer to its
state in dev->liveupdate_incoming and holds a reference to the incoming
FLB until pci_liveupdate_finish() is called by the driver.
This ensures proper lifecycle management for incoming preserved devices
and allows the PCI core and drivers to apply specific Live Update
logic to them in subsequent commits.
Drivers can check if a device is an incoming preserved device (e.g.
during probe) by calling pci_liveupdate_is_incoming().
CONFIG_64BIT is now required to enable CONFIG_PCI_LIVEUPDATE so that the
domain and bdf can be guaranteed to fit in an unsigned long and be used
as the xarray key.
Signed-off-by: David Matlack <dmatlack@google.com>
---
MAINTAINERS | 1 +
drivers/pci/Kconfig | 2 +-
drivers/pci/liveupdate.c | 230 ++++++++++++++++++++++++++++++++-
drivers/pci/liveupdate.h | 5 +
drivers/pci/probe.c | 3 +
include/linux/pci_liveupdate.h | 13 ++
6 files changed, 251 insertions(+), 3 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6c618830cf61..0e262c0ceb43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20537,6 +20537,7 @@ L: linux-pci@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
F: drivers/pci/liveupdate.c
+F: drivers/pci/liveupdate.h
F: include/linux/kho/abi/pci.h
F: include/linux/pci_liveupdate.h
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 10c9b65aa242..e68ae5c172d4 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -330,7 +330,7 @@ config VGA_ARB_MAX_GPUS
config PCI_LIVEUPDATE
bool "PCI Live Update Support"
- depends on PCI && LIVEUPDATE
+ depends on PCI && LIVEUPDATE && 64BIT
help
Enable PCI core support for preserving PCI devices across Live
Update. This, in combination with support in a device's driver,
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 065d5af822f7..96c43b84532c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -49,6 +49,20 @@
* This allows the PCI core to keep its FLB data (struct pci_ser) up to date
* with the list of **outgoing** preserved devices for the next kernel.
*
+ * After kexec, whenever a device is enumerated, the PCI core will check if it
+ * is an **incoming** preserved device (i.e. preserved by the previous kernel)
+ * by checking the incoming FLB data (struct pci_ser).
+ *
+ * Drivers must notify the PCI core when an **incoming** device is done
+ * participating in the incoming Live Update with the following API:
+ *
+ * * ``pci_liveupdate_finish(pci_dev)``
+ *
+ * The PCI core does not enforce any ordering of ``pci_liveupdate_finish()`` and
+ * ``pci_liveupdate_preserve()``. i.e. A PCI device can be **outgoing**
+ * (preserved for next kernel) and **incoming** (preserved by previous kernel)
+ * at the same time.
+ *
* Restrictions
* ============
*
@@ -83,6 +97,21 @@ static struct pci_liveupdate_global pci_liveupdate = {
.rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem),
};
+/**
+ * struct pci_flb_incoming - Incoming PCI FLB object
+ * @ser: The incoming struct pci_ser from the previous kernel.
+ * @xa: Xarray used to quickly lookup devices in @ser.
+ */
+struct pci_flb_incoming {
+ struct pci_ser *ser;
+ struct xarray xa;
+};
+
+static unsigned long pci_ser_xa_key(u32 domain, u16 bdf)
+{
+ return (unsigned long)domain << 16 | bdf;
+}
+
static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
{
struct pci_dev *dev = NULL;
@@ -128,13 +157,49 @@ static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
{
- args->obj = phys_to_virt(args->data);
+ struct pci_ser *ser = phys_to_virt(args->data);
+ struct pci_flb_incoming *incoming;
+ int ret = -ENOMEM;
+ u32 i;
+
+ incoming = kmalloc_obj(*incoming);
+ if (!incoming)
+ goto err_restore_free;
+
+ incoming->ser = ser;
+ xa_init(&incoming->xa);
+
+ for (i = 0; i < incoming->ser->max_nr_devices; i++) {
+ struct pci_dev_ser *dev_ser = &incoming->ser->devices[i];
+ unsigned long key;
+
+ if (!dev_ser->refcount)
+ continue;
+
+ key = pci_ser_xa_key(dev_ser->domain, dev_ser->bdf);
+ ret = xa_insert(&incoming->xa, key, dev_ser, GFP_KERNEL);
+ if (ret)
+ goto err_xa_destroy;
+ }
+
+ args->obj = incoming;
return 0;
+
+err_xa_destroy:
+ xa_destroy(&incoming->xa);
+ kfree(incoming);
+err_restore_free:
+ kho_restore_free(ser);
+ return ret;
}
static void pci_flb_finish(struct liveupdate_flb_op_args *args)
{
- kho_restore_free(args->obj);
+ struct pci_flb_incoming *incoming = args->obj;
+
+ xa_destroy(&incoming->xa);
+ kho_restore_free(incoming->ser);
+ kfree(incoming);
}
static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
@@ -270,6 +335,91 @@ void pci_liveupdate_unpreserve(struct pci_dev *dev)
}
EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
+static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
+{
+ struct pci_flb_incoming *incoming = NULL;
+ int ret;
+
+ ret = liveupdate_flb_get_incoming(&pci_liveupdate_flb, (void **)&incoming);
+
+ /* Live Update is not enabled. */
+ if (ret == -EOPNOTSUPP)
+ return NULL;
+
+ /* Live Update is enabled, but there is no incoming FLB data. */
+ if (ret == -ENODATA)
+ return NULL;
+
+ /*
+ * Live Update is enabled and there is incoming FLB data, but none of it
+ * matches pci_liveupdate_flb.compatible.
+ *
+ * This could mean that no PCI FLB data was passed by the previous
+ * kernel, but it could also mean the previous kernel used a different
+ * compatibility string (i.e. a different ABI).
+ */
+ if (ret == -ENOENT) {
+ pr_info_once("No incoming FLB matched %s\n", pci_liveupdate_flb.compatible);
+ return NULL;
+ }
+
+ /*
+ * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+ * but it cannot be retrieved.
+ */
+ if (ret) {
+ WARN_ONCE(ret, "Failed to retrieve incoming FLB data\n");
+ return NULL;
+ }
+
+ return incoming;
+}
+
+static void pci_liveupdate_flb_put_incoming(void)
+{
+ liveupdate_flb_put_incoming(&pci_liveupdate_flb);
+}
+
+void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+ struct pci_flb_incoming *incoming;
+ struct pci_dev_ser *dev_ser;
+ unsigned long key;
+
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+ incoming = pci_liveupdate_flb_get_incoming();
+ if (!incoming)
+ return;
+
+ key = pci_ser_xa_key(pci_domain_nr(dev->bus), pci_dev_id(dev));
+ dev_ser = xa_load(&incoming->xa, key);
+
+ /* This device was not preserved across Live Update */
+ if (!dev_ser) {
+ pci_liveupdate_flb_put_incoming();
+ return;
+ }
+
+ /*
+ * This device was preserved, but has already been probed and gone
+ * through pci_liveupdate_finish(). This can happen if PCI core probes
+ * the same device multiple times, e.g. due to hotplug.
+ */
+ if (!dev_ser->refcount) {
+ pci_liveupdate_flb_put_incoming();
+ return;
+ }
+
+ pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
+ dev->liveupdate.incoming = dev_ser;
+
+ /*
+ * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
+ * that dev->liveupdate.incoming does not get freed while it is in use.
+ */
+}
+
void pci_liveupdate_cleanup_device(struct pci_dev *dev)
{
/*
@@ -280,7 +430,83 @@ void pci_liveupdate_cleanup_device(struct pci_dev *dev)
pci_WARN(dev, 1, "Destroying outgoing-preserved device!\n");
pci_liveupdate_unpreserve(dev);
}
+
+ if (READ_ONCE(dev->liveupdate.incoming)) {
+ pci_WARN(dev, 1, "Destroying incoming-preserved device!\n");
+ pci_liveupdate_finish(dev);
+ }
+}
+
+static void pci_liveupdate_finish_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+ if (!dev->liveupdate.incoming) {
+ pci_warn(dev, "Cannot finish preserving an unpreserved device\n");
+ return;
+ }
+
+ if (dev->liveupdate.incoming->refcount != 1) {
+ pci_WARN(dev, 1, "Preserved device has a corrupted refcount!\n");
+ return;
+ }
+
+ /*
+ * Drop the refcount so this device does not get treated as an incoming
+ * device again, e.g. in case pci_liveupdate_setup_device() gets called
+ * again because the device is hot-plugged.
+ */
+ dev->liveupdate.incoming->refcount = 0;
+
+ pci_info(dev, "Device is finished participating in Live Update\n");
+ dev->liveupdate.incoming = NULL;
+ ser->nr_devices--;
+ pci_liveupdate_flb_put_incoming();
+}
+
+/**
+ * pci_liveupdate_finish() - Finish the preservation of a PCI device
+ * @dev: The PCI device
+ *
+ * pci_liveupdate_finish() notifies the PCI core that a PCI device that was
+ * preserved across the previous Live Update has finished participating in Live
+ * Update. Drivers must call pci_liveupdate_finish() from their struct
+ * liveupdate_file_handler finish() callback to ensure the incoming struct
+ * pci_ser is allocated.
+ */
+void pci_liveupdate_finish(struct pci_dev *dev)
+{
+ struct pci_flb_incoming *incoming;
+
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+ incoming = pci_liveupdate_flb_get_incoming();
+ if (!incoming) {
+ pci_warn(dev, "Cannot finish preserving device without incoming FLB\n");
+ return;
+ }
+
+ pci_liveupdate_finish_device(incoming->ser, dev);
+ pci_liveupdate_flb_put_incoming();
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_finish);
+
+/**
+ * pci_liveupdate_is_incoming() - Check if a device is incoming-preserved
+ * @dev: The PCI device to check
+ *
+ * Check if a device was preserved across Live Update by the previous kernel,
+ * i.e. the device is incoming-preserved. Note that a device is only considered
+ * incoming-preserved prior to pci_liveupdate_finish(). It is up to drivers to
+ * synchronize usage of pci_liveupdate_is_incoming() with their own call to
+ * pci_liveupdate_finish() to avoid acting on stale data.
+ *
+ * Returns: True if the device is incoming-preserved, false otherwise.
+ */
+bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+ guard(rwsem_read)(&pci_liveupdate.rwsem);
+ return dev->liveupdate.incoming;
}
+EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
/**
* pci_liveupdate_register_flb() - Register a file handler with the PCI core
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
index b2335581f8d0..eaaa3559fd77 100644
--- a/drivers/pci/liveupdate.h
+++ b/drivers/pci/liveupdate.h
@@ -11,8 +11,13 @@
#include <linux/pci.h>
#ifdef CONFIG_PCI_LIVEUPDATE
+void pci_liveupdate_setup_device(struct pci_dev *dev);
void pci_liveupdate_cleanup_device(struct pci_dev *dev);
#else
+static inline void pci_liveupdate_setup_device(struct pci_dev *dev)
+{
+}
+
static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
{
}
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b88109a8dfe4..2e2be8af6976 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2070,6 +2070,8 @@ int pci_setup_device(struct pci_dev *dev)
if (pci_early_dump)
early_dump_pci_device(dev);
+ pci_liveupdate_setup_device(dev);
+
/* Need to have dev->class ready */
dev->cfg_size = pci_cfg_space_size(dev);
@@ -2193,6 +2195,7 @@ int pci_setup_device(struct pci_dev *dev)
default: /* unknown header */
pci_err(dev, "unknown header type %02x, ignoring device\n",
dev->hdr_type);
+ pci_liveupdate_cleanup_device(dev);
pci_release_of_node(dev);
return -EIO;
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index cfcfbfa73af7..cfdc3d62ec02 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -16,9 +16,11 @@
/**
* struct pci_liveupdate - PCI Live Update state for a struct pci_dev
* @outgoing: State preserved for the next kernel.
+ * @incoming: State preserved by the previous kernel.
*/
struct pci_liveupdate {
struct pci_dev_ser *outgoing;
+ struct pci_dev_ser *incoming;
};
struct pci_dev;
@@ -28,6 +30,8 @@ int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
int pci_liveupdate_preserve(struct pci_dev *dev);
void pci_liveupdate_unpreserve(struct pci_dev *dev);
+void pci_liveupdate_finish(struct pci_dev *dev);
+bool pci_liveupdate_is_incoming(struct pci_dev *dev);
#else
static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
{
@@ -46,6 +50,15 @@ static inline int pci_liveupdate_preserve(struct pci_dev *dev)
static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
{
}
+
+static inline void pci_liveupdate_finish(struct pci_dev *dev)
+{
+}
+
+static inline bool pci_liveupdate_is_incoming(struct pci_dev *dev)
+{
+ return false;
+}
#endif
#endif /* LINUX_PCI_LIVEUPDATE_H */
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 02/12] PCI: liveupdate: Track outgoing preserved PCI devices
From: David Matlack @ 2026-05-22 20:24 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Add APIs to allow drivers to notify the PCI core of which devices are
being preserved across a Live Update for the next kernel, i.e.
"outgoing" devices.
Drivers must notify the PCI core when devices are preserved so that the
PCI core can update its FLB data (struct pci_ser) and track the list of
outgoing devices. pci_liveupdate_preserve() notifies the PCI core that a
device must be preserved across Live Update. pci_liveupdate_unpreserve()
reverses this (cancels the preservation of the device).
This tracking ensures the PCI core is fully aware of which devices may
need special handling during shutdown and kexec, and so that it can be
handed off to the next kernel.
Signed-off-by: David Matlack <dmatlack@google.com>
---
drivers/pci/liveupdate.c | 167 +++++++++++++++++++++++++++++++++
drivers/pci/liveupdate.h | 21 +++++
drivers/pci/probe.c | 2 +
include/linux/kho/abi/pci.h | 9 +-
include/linux/pci.h | 3 +
include/linux/pci_liveupdate.h | 21 +++++
6 files changed, 220 insertions(+), 3 deletions(-)
create mode 100644 drivers/pci/liveupdate.h
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 737e7b9366db..065d5af822f7 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -36,6 +36,26 @@
*
* * ``pci_liveupdate_register_flb(driver_file_handler)``
* * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ *
+ * Device Tracking
+ * ===============
+ *
+ * Drivers must notify the PCI core when specific devices are preserved or
+ * unpreserved with the following APIs:
+ *
+ * * ``pci_liveupdate_preserve(pci_dev)``
+ * * ``pci_liveupdate_unpreserve(pci_dev)``
+ *
+ * This allows the PCI core to keep its FLB data (struct pci_ser) up to date
+ * with the list of **outgoing** preserved devices for the next kernel.
+ *
+ * Restrictions
+ * ============
+ *
+ * The PCI core enforces the following restrictions on which devices can be
+ * preserved. These may be relaxed in the future:
+ *
+ * * The device cannot be a Virtual Function (VF).
*/
#define pr_fmt(fmt) "PCI: liveupdate: " fmt
@@ -48,6 +68,21 @@
#include <linux/mm.h>
#include <linux/pci.h>
+#include "liveupdate.h"
+
+/**
+ * struct pci_liveupdate_global - Global state for PCI Live Update support
+ * @rwsem: Reader/writer semaphore used to protect the incoming and outgoing
+ * FLBs, and the references to them in struct pci_dev.
+ */
+struct pci_liveupdate_global {
+ struct rw_semaphore rwsem;
+};
+
+static struct pci_liveupdate_global pci_liveupdate = {
+ .rwsem = __RWSEM_INITIALIZER(pci_liveupdate.rwsem),
+};
+
static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
{
struct pci_dev *dev = NULL;
@@ -115,6 +150,138 @@ static struct liveupdate_flb pci_liveupdate_flb = {
.compatible = PCI_LUO_FLB_COMPATIBLE,
};
+static struct pci_ser *pci_liveupdate_flb_get_outgoing(void)
+{
+ struct pci_ser *ser = NULL;
+ int ret;
+
+ ret = liveupdate_flb_get_outgoing(&pci_liveupdate_flb, (void **)&ser);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (!ser)
+ return ERR_PTR(-ENOENT);
+
+ return ser;
+}
+
+static void pci_liveupdate_unpreserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+ struct pci_dev_ser *dev_ser = dev->liveupdate.outgoing;
+
+ if (!dev_ser) {
+ pci_warn(dev, "Cannot unpreserve device that is not preserved\n");
+ return;
+ }
+
+ pci_info(dev, "Device will no longer be preserved across next Live Update\n");
+ ser->nr_devices--;
+ memset(dev_ser, 0, sizeof(*dev_ser));
+ dev->liveupdate.outgoing = NULL;
+}
+
+static int pci_liveupdate_preserve_device(struct pci_ser *ser, struct pci_dev *dev)
+{
+ int i;
+
+ if (dev->liveupdate.outgoing)
+ return -EBUSY;
+
+ if (ser->nr_devices == ser->max_nr_devices)
+ return -ENOSPC;
+
+ for (i = 0; i < ser->max_nr_devices; i++) {
+ /*
+ * Start searching at index ser->nr_devices. This should result
+ * in a constant time search under expected conditions (devices
+ * are not getting unpreserved).
+ */
+ int index = (ser->nr_devices + i) % ser->max_nr_devices;
+ struct pci_dev_ser *dev_ser = &ser->devices[index];
+
+ if (dev_ser->refcount)
+ continue;
+
+ pci_info(dev, "Device will be preserved across next Live Update\n");
+ ser->nr_devices++;
+
+ dev_ser->domain = pci_domain_nr(dev->bus);
+ dev_ser->bdf = pci_dev_id(dev);
+ dev_ser->refcount = 1;
+
+ dev->liveupdate.outgoing = dev_ser;
+ return 0;
+ }
+
+ return -ENOSPC;
+}
+
+/**
+ * pci_liveupdate_preserve() - Preserve a PCI device across Live Update
+ * @dev: The PCI device to preserve.
+ *
+ * pci_liveupdate_preserve() notifies the PCI core that a PCI device should be
+ * preserved across the next Live Update. Drivers are expected to call
+ * pci_liveupdate_preserve() from their struct liveupdate_file_handler
+ * preserve() callback to ensure the outgoing struct pci_ser is already set up.
+ *
+ * Returns: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+ struct pci_ser *ser = NULL;
+
+ if (dev->is_virtfn)
+ return -EINVAL;
+
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+ ser = pci_liveupdate_flb_get_outgoing();
+ if (IS_ERR(ser))
+ return PTR_ERR(ser);
+
+ return pci_liveupdate_preserve_device(ser, dev);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_preserve);
+
+/**
+ * pci_liveupdate_unpreserve() - Cancel preservation of a PCI device
+ * @dev: The PCI device to unpreserve.
+ *
+ * pci_liveupdate_unpreserve() notifies the PCI core that a PCI device should no
+ * longer be preserved across the next Live Update. Drivers are expected to call
+ * pci_liveupdate_unpreserve() from their struct liveupdate_file_handler
+ * unpreserve() callback to ensure the outgoing struct pci_ser is already set
+ * up.
+ */
+void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+ struct pci_ser *ser = NULL;
+
+ guard(rwsem_write)(&pci_liveupdate.rwsem);
+
+ ser = pci_liveupdate_flb_get_outgoing();
+ if (IS_ERR(ser)) {
+ pci_warn(dev, "Cannot unpreserve device without outgoing Live Update state\n");
+ return;
+ }
+
+ pci_liveupdate_unpreserve_device(ser, dev);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unpreserve);
+
+void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+ /*
+ * It should be safe to READ_ONCE() outside of the rwsem during cleanup
+ * since there should no longer be any references to @dev on the system.
+ */
+ if (READ_ONCE(dev->liveupdate.outgoing)) {
+ pci_WARN(dev, 1, "Destroying outgoing-preserved device!\n");
+ pci_liveupdate_unpreserve(dev);
+ }
+}
+
/**
* pci_liveupdate_register_flb() - Register a file handler with the PCI core
* @fh: The file handler to register.
diff --git a/drivers/pci/liveupdate.h b/drivers/pci/liveupdate.h
new file mode 100644
index 000000000000..b2335581f8d0
--- /dev/null
+++ b/drivers/pci/liveupdate.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (core API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack@google.com>
+ */
+#ifndef DRIVERS_PCI_LIVEUPDATE_H
+#define DRIVERS_PCI_LIVEUPDATE_H
+
+#include <linux/pci.h>
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+void pci_liveupdate_cleanup_device(struct pci_dev *dev);
+#else
+static inline void pci_liveupdate_cleanup_device(struct pci_dev *dev)
+{
+}
+#endif
+
+#endif /* DRIVERS_PCI_LIVEUPDATE_H */
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..b88109a8dfe4 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -24,6 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/bitfield.h>
#include <trace/events/pci.h>
+#include "liveupdate.h"
#include "pci.h"
static struct resource busn_resource = {
@@ -2490,6 +2491,7 @@ static void pci_release_dev(struct device *dev)
pci_dev = to_pci_dev(dev);
pci_release_capabilities(pci_dev);
+ pci_liveupdate_cleanup_device(pci_dev);
pci_release_of_node(pci_dev);
pcibios_release_device(pci_dev);
pci_bus_put(pci_dev->bus);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 6ebcf817fff4..85def616703d 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,19 +23,22 @@
* incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
*/
-#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+#define PCI_LUO_FLB_COMPATIBLE "pci-v2"
/**
* struct pci_dev_ser - Serialized state about a single PCI device.
*
* @domain: The device's PCI domain number (segment).
* @bdf: The device's PCI bus, device, and function number.
- * @padding: Padding to naturally align struct pci_dev_ser.
+ * @refcount: Reference count used by the PCI core to keep track of whether it
+ * is done using a device's struct pci_dev_ser. The value of the
+ * refcount is equal to 1 when the struct pci_dev_ser is in use, and
+ * 0 otherwise.
*/
struct pci_dev_ser {
u32 domain;
u16 bdf;
- u16 padding;
+ u16 refcount;
} __packed;
/**
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 8cadeeab86fd..a7c3722b1e77 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -594,6 +594,9 @@ struct pci_dev {
u8 tph_mode; /* TPH mode */
u8 tph_req_type; /* TPH requester type */
#endif
+#ifdef CONFIG_PCI_LIVEUPDATE
+ struct pci_liveupdate liveupdate;
+#endif
};
static inline struct pci_dev *pci_physfn(struct pci_dev *dev)
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
index 8ec98beefcb4..cfcfbfa73af7 100644
--- a/include/linux/pci_liveupdate.h
+++ b/include/linux/pci_liveupdate.h
@@ -8,14 +8,26 @@
#ifndef LINUX_PCI_LIVEUPDATE_H
#define LINUX_PCI_LIVEUPDATE_H
+#include <linux/kho/abi/pci.h>
#include <linux/liveupdate.h>
#include <linux/types.h>
+#include <linux/spinlock_types.h>
+
+/**
+ * struct pci_liveupdate - PCI Live Update state for a struct pci_dev
+ * @outgoing: State preserved for the next kernel.
+ */
+struct pci_liveupdate {
+ struct pci_dev_ser *outgoing;
+};
struct pci_dev;
#ifdef CONFIG_PCI_LIVEUPDATE
int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+int pci_liveupdate_preserve(struct pci_dev *dev);
+void pci_liveupdate_unpreserve(struct pci_dev *dev);
#else
static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
{
@@ -25,6 +37,15 @@ static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh
static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
{
}
+
+static inline int pci_liveupdate_preserve(struct pci_dev *dev)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unpreserve(struct pci_dev *dev)
+{
+}
#endif
#endif /* LINUX_PCI_LIVEUPDATE_H */
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 01/12] PCI: liveupdate: Set up FLB handler for the PCI core
From: David Matlack @ 2026-05-22 20:23 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-1-dmatlack@google.com>
Set up a File-Lifecycle-Bound (FLB) handler for the PCI core to enable
it to participate in the preservation of PCI devices across Live Update.
Essentially, this commit enables the PCI core to allocate a struct
(struct pci_ser) and preserve it across a Live Update whenever at least
one device is preserved.
Preserving PCI devices across Live Update is built on top of the Live
Update Orchestrator's (LUO) support for file preservation. Drivers are
expected to expose a file to userspace to represent a single PCI device
and support preservation of that file. This is intended primarily to
support preservation of PCI devices bound to VFIO drivers.
This commit enables drivers to register their liveupdate_file_handler
with the PCI core so that the PCI core can do its own tracking and
enforcement of which devices are preserved.
pci_liveupdate_register_flb(driver_file_handler);
pci_liveupdate_unregister_flb(driver_file_handler);
When the first file (with a handler registered with the PCI core) is
preserved, the PCI core will be notified to allocate its tracking struct
(pci_ser). When the last file is unpreserved (i.e. preservation
cancelled) the PCI core will be notified to free struct pci_ser.
This struct is preserved across a Live Update using KHO and can be
fetched by the PCI core during early boot (e.g. during device
enumeration) so that it knows which devices were preserved.
Note: This commit only allocates struct pci_ser and preserves it across
Live Update. A subsequent commit will add an API for drivers to tell the
PCI core exactly which devices are being preserved.
Note: There is no reason to check for kho_is_enabled() since it can be
assumed to return true. If KHO was not enabled then Live Update would
not be enabled and these routines would never run.
Signed-off-by: David Matlack <dmatlack@google.com>
---
MAINTAINERS | 10 +++
drivers/pci/Kconfig | 15 ++++
drivers/pci/Makefile | 1 +
drivers/pci/liveupdate.c | 145 +++++++++++++++++++++++++++++++++
include/linux/kho/abi/pci.h | 61 ++++++++++++++
include/linux/pci.h | 1 +
include/linux/pci_liveupdate.h | 30 +++++++
7 files changed, 263 insertions(+)
create mode 100644 drivers/pci/liveupdate.c
create mode 100644 include/linux/kho/abi/pci.h
create mode 100644 include/linux/pci_liveupdate.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..6c618830cf61 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20530,6 +20530,16 @@ L: linux-pci@vger.kernel.org
S: Supported
F: Documentation/PCI/pci-error-recovery.rst
+PCI LIVE UPDATE
+M: David Matlack <dmatlack@google.com>
+L: kexec@lists.infradead.org
+L: linux-pci@vger.kernel.org
+S: Maintained
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
+F: drivers/pci/liveupdate.c
+F: include/linux/kho/abi/pci.h
+F: include/linux/pci_liveupdate.h
+
PCI MSI DRIVER FOR ALTERA MSI IP
L: linux-pci@vger.kernel.org
S: Orphan
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 33c88432b728..10c9b65aa242 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -328,6 +328,21 @@ config VGA_ARB_MAX_GPUS
Reserves space in the kernel to maintain resource locking for
multiple GPUS. The overhead for each GPU is very small.
+config PCI_LIVEUPDATE
+ bool "PCI Live Update Support"
+ depends on PCI && LIVEUPDATE
+ help
+ Enable PCI core support for preserving PCI devices across Live
+ Update. This, in combination with support in a device's driver,
+ enables PCI devices to run and perform memory transactions
+ uninterrupted during a kexec for Live Update.
+
+ This option should only be enabled by users who plan to use Live
+ Update for kernel upgrades and require preserving PCI devices during
+ those upgrades.
+
+ If unsure, say N.
+
source "drivers/pci/hotplug/Kconfig"
source "drivers/pci/controller/Kconfig"
source "drivers/pci/endpoint/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..e8d003cb6757 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_PROC_FS) += proc.o
obj-$(CONFIG_SYSFS) += pci-sysfs.o slot.o
obj-$(CONFIG_ACPI) += pci-acpi.o
obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o
+obj-$(CONFIG_PCI_LIVEUPDATE) += liveupdate.o
endif
obj-$(CONFIG_OF) += of.o
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
new file mode 100644
index 000000000000..737e7b9366db
--- /dev/null
+++ b/drivers/pci/liveupdate.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack@google.com>
+ */
+
+/**
+ * DOC: PCI Live Update
+ *
+ * The PCI subsystem participates in the Live Update process to enable drivers
+ * to preserve their PCI devices across kexec.
+ *
+ * File-Lifecycle-Bound (FLB) Data
+ * ===============================
+ *
+ * PCI device preservation across Live Update is built on top of the Live Update
+ * Orchestrator's (LUO) support for file preservation across kexec. Drivers
+ * are expected to expose a file to represent a single PCI device and support
+ * preservation of that file with ``ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)``.
+ * This allows userspace to control the preservation of devices and ensure
+ * proper lifecycle management while a device is preserved. The first intended
+ * use-case is preserving vfio-pci device files.
+ *
+ * The PCI core maintains its own state about what devices are being preserved
+ * across Live Update using a feature called File-Lifecycle-Bound (FLB) data in
+ * LUO. Essentially, this allows the PCI core to allocate struct pci_ser when
+ * the first device (file) is preserved and free it when the last device (file)
+ * is unpreserved. After kexec, the PCI core can fetch the struct pci_ser (which
+ * was constructed by the previous kernel) from LUO at any time (e.g. during
+ * enumeration) so that it knows which devices were preserved.
+ *
+ * To enable the PCI core to be notified whenever a file representing a device
+ * is preserved, drivers must register their struct liveupdate_file_handler with
+ * the PCI core by using the following APIs:
+ *
+ * * ``pci_liveupdate_register_flb(driver_file_handler)``
+ * * ``pci_liveupdate_unregister_flb(driver_file_handler)``
+ */
+
+#define pr_fmt(fmt) "PCI: liveupdate: " fmt
+
+#include <linux/io.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/pci.h>
+#include <linux/liveupdate.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+
+static int pci_flb_preserve(struct liveupdate_flb_op_args *args)
+{
+ struct pci_dev *dev = NULL;
+ u32 max_nr_devices = 0;
+ struct pci_ser *ser;
+ unsigned long size;
+
+ /*
+ * Allocate enough space to preserve all devices that are currently
+ * present on the system. Extra padding can be added to this in the
+ * future to increase the chances that there is enough room to preserve
+ * devices that are not yet present on the system (e.g. VFs, hot-plugged
+ * devices).
+ */
+ for_each_pci_dev(dev)
+ max_nr_devices++;
+
+ size = struct_size_t(struct pci_ser, devices, max_nr_devices);
+
+ ser = kho_alloc_preserve(size);
+ if (IS_ERR(ser))
+ return PTR_ERR(ser);
+
+ pr_debug("Preserved struct pci_ser with room for %u devices\n",
+ max_nr_devices);
+
+ ser->max_nr_devices = max_nr_devices;
+ ser->nr_devices = 0;
+
+ args->obj = ser;
+ args->data = virt_to_phys(ser);
+ return 0;
+}
+
+static void pci_flb_unpreserve(struct liveupdate_flb_op_args *args)
+{
+ struct pci_ser *ser = args->obj;
+
+ WARN_ON(ser->nr_devices);
+ kho_unpreserve_free(ser);
+ pr_debug("Unpreserved struct pci_ser\n");
+}
+
+static int pci_flb_retrieve(struct liveupdate_flb_op_args *args)
+{
+ args->obj = phys_to_virt(args->data);
+ return 0;
+}
+
+static void pci_flb_finish(struct liveupdate_flb_op_args *args)
+{
+ kho_restore_free(args->obj);
+}
+
+static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
+ .preserve = pci_flb_preserve,
+ .unpreserve = pci_flb_unpreserve,
+ .retrieve = pci_flb_retrieve,
+ .finish = pci_flb_finish,
+ .owner = THIS_MODULE,
+};
+
+static struct liveupdate_flb pci_liveupdate_flb = {
+ .ops = &pci_liveupdate_flb_ops,
+ .compatible = PCI_LUO_FLB_COMPATIBLE,
+};
+
+/**
+ * pci_liveupdate_register_flb() - Register a file handler with the PCI core
+ * @fh: The file handler to register.
+ *
+ * Drivers should call pci_liveupdate_register_flb() to register their
+ * struct liveupdate_file_handler with the PCI core. This enables the PCI core
+ * to allocate its outgoing struct pci_ser whenever the first device is
+ * preserved, and free it when the last device is unpreserved.
+ *
+ * Return: 0 on success, <0 on failure.
+ */
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+ pr_debug("Registering file handler \"%s\"\n", fh->compatible);
+ return liveupdate_register_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
+
+/**
+ * pci_liveupdate_unregister_flb() - Unregister a file handler with the PCI core
+ * @fh: The file handler to unregister.
+ */
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+ pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
+ liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
+}
+EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
new file mode 100644
index 000000000000..6ebcf817fff4
--- /dev/null
+++ b/include/linux/kho/abi/pci.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack@google.com>
+ */
+
+#ifndef _LINUX_KHO_ABI_PCI_H
+#define _LINUX_KHO_ABI_PCI_H
+
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+
+/**
+ * DOC: PCI File-Lifecycle Bound (FLB) Live Update ABI
+ *
+ * This header defines the ABI for preserving core PCI state across kexec using
+ * Live Update File-Lifecycle Bound (FLB) data.
+ *
+ * This interface is a contract. Any modification to any of the serialization
+ * structs defined here constitutes a breaking change. Such changes require
+ * incrementing the version number in the PCI_LUO_FLB_COMPATIBLE string.
+ */
+
+#define PCI_LUO_FLB_COMPATIBLE "pci-v1"
+
+/**
+ * struct pci_dev_ser - Serialized state about a single PCI device.
+ *
+ * @domain: The device's PCI domain number (segment).
+ * @bdf: The device's PCI bus, device, and function number.
+ * @padding: Padding to naturally align struct pci_dev_ser.
+ */
+struct pci_dev_ser {
+ u32 domain;
+ u16 bdf;
+ u16 padding;
+} __packed;
+
+/**
+ * struct pci_ser - PCI Subsystem Live Update State
+ *
+ * This struct tracks state about all devices that are being preserved across
+ * a Live Update for the next kernel.
+ *
+ * @max_nr_devices: The length of the devices[] flexible array.
+ * @nr_devices: The number of devices that were preserved.
+ * @devices: Flexible array of pci_dev_ser structs for each device.
+ */
+struct pci_ser {
+ u32 max_nr_devices;
+ u32 nr_devices;
+ struct pci_dev_ser devices[];
+} __packed;
+
+/* Ensure all elements of devices[] are naturally aligned. */
+static_assert(offsetof(struct pci_ser, devices) % sizeof(unsigned long) == 0);
+static_assert(sizeof(struct pci_dev_ser) % sizeof(unsigned long) == 0);
+
+#endif /* _LINUX_KHO_ABI_PCI_H */
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 2c4454583c11..8cadeeab86fd 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -42,6 +42,7 @@
#include <uapi/linux/pci.h>
#include <linux/pci_ids.h>
+#include <linux/pci_liveupdate.h>
#define PCI_STATUS_ERROR_BITS (PCI_STATUS_DETECTED_PARITY | \
PCI_STATUS_SIG_SYSTEM_ERROR | \
diff --git a/include/linux/pci_liveupdate.h b/include/linux/pci_liveupdate.h
new file mode 100644
index 000000000000..8ec98beefcb4
--- /dev/null
+++ b/include/linux/pci_liveupdate.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PCI Live Update support (Public/Driver API)
+ *
+ * Copyright (c) 2026, Google LLC.
+ * David Matlack <dmatlack@google.com>
+ */
+#ifndef LINUX_PCI_LIVEUPDATE_H
+#define LINUX_PCI_LIVEUPDATE_H
+
+#include <linux/liveupdate.h>
+#include <linux/types.h>
+
+struct pci_dev;
+
+#ifdef CONFIG_PCI_LIVEUPDATE
+int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh);
+void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh);
+#else
+static inline int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
+{
+}
+#endif
+
+#endif /* LINUX_PCI_LIVEUPDATE_H */
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* [PATCH v6 00/12] PCI: liveupdate: PCI core support for Live Update
From: David Matlack @ 2026-05-22 20:23 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-mm, linux-pci
Cc: Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Matlack, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pranjal Shrivastava, Pratyush Yadav, Saeed Mahameed,
Samiullah Khawaja, Shuah Khan, Vipin Sharma, William Tu, Yi Liu
This series can be found on GitHub:
https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v6
This series introduces initial support in the PCI core for Live Update,
enabling drivers to preserve PCI devices across a kexec-based kernel
update without interrupting the device. This functionality is critical
for minimizing downtime in environments where PCI devices (e.g., those
assigned to VMs via VFIO) must continue operating or maintain state
across a host kernel upgrade.
Specifically, this series allows preserved PCI devices to perform
uninterrupted memory transactions (DMA) to/from system memory across a
Live Update. These devices can be behind a bridge but must not be VFs.
Support for P2P and VF preservation will be addressed in future series.
Series Overview
---------------
This series implements the following to support PCI device preservation
across Live Update:
1. Set up a File-Lifecycle-Bound (FLB) handler to track and preserve
PCI-specific state (struct pci_ser) across Live Update using Kexec
Handover (KHO).
2. Add APIs for drivers to register outgoing-preserved devices for
preservation and for the PCI core to identify incoming-preserved
devices during enumeration.
3. Automatically preserve all upstream bridges for any preserved
endpoint. Use reference counting to ensure bridges remain preserved
as long as any downstream device is preserved.
4. Guarantee that preserved devices retain the same RequesterID (bus,
device, function) for the duration of their preservation by
inheriting secondary bus numbers, subordinate bus numbers, and ARI
Forwarding Enable on preserved bridges.
5. Guarantee that memory transactions to/from preserved devices are
routed consistently by inheriting Access Control Services (ACS)
flags across a Live Update, from the endpoint up to the root port.
6. Modify the PCI shutdown path to avoid disabling bus mastering on
preserved devices, thereby allowing preserved devices to perform
uninterrupted during kexec for Live Update.
7. Provide comprehensive documentation for the FLB API, device
tracking mechanisms, and the division of responsibilities between
the PCI core, drivers, and userspace.
Dependencies
------------
This series is built on top of the next branch of the liveupdate.git
tree, which includes two commits to enable refcounting the incoming FLB:
https://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git/log/?h=next
Testing
-------
This series was tested in conjunction with v4 of the VFIO PCI driver
series:
https://lore.kernel.org/kvm/20260511234802.2280368-1-vipinsh@google.com/
The full set of patches used for testing can be found on GitHub:
https://github.com/dmatlack/linux/tree/liveupdate/pci/base/v6-with-vfio
Testing was performed using the new VFIO selftests:
- vfio_pci_liveupdate_uapi_test
- vfio_pci_liveupdate_kexec_test
Both tests were run in a QEMU-based VM environment (using a single
virtio-net PCIe device connected to a root port to exercise bridge
support) and on bare metal using an Intel EMR server with 8x Intel DSA
PCIe devices and 1x NVMe device.
Future Work
-----------
Following this series, we expect to make further improvements to the PCI
core support for Live Update:
- Allow P2P across Live Update by avoiding resizing or moving
preserved device BARs and preserving all upstream bridge windows.
- Support preserving Virtual Functions by preserving SR-IOV
configuration on PFs and enumerating VFs after Live Update.
Changelog
---------
v6:
- Fix truncated domain during bit shift in pci_ser_xa_key() (Sashiko)
- Replace complex and buggy per-device locks and outgoing mutex with a
single rwsem (me)
- Use a loop instead of recursion to walk upstream bridges (Sashiko)
- Correctly account for all devices that need quirks to enable ACS
instead of relying on PCI_DEV_FLAGS_ACS_ENABLED_QUIRK. This required
adding a patch to refactor pci_dev_acs_ops in quirks.c (Sashiko)
- Fix circular locking dependency between pci_rescan_remove_lock and
pci_liveupdate_flb.private->incoming.lock (me)
- Convert several functions to return 0/error instead of true/false.
- Bump PCI_LUO_FLB_COMPATIBLE in the patch that changes the semantics
of pci_dev_ser.refcount (me)
- Convert was_prepared and frozen from bitfields to bools to avoid
KCSAN warnings (Sashiko)
- Drop experimental verbiage from Kconfig and Documentation (me)
- Ensure that bridges inherit bus numbers during scanning if their
parent bridge also inherits bus numbers. Otherwise, a race between a
scan and pci_liveupdate_finish() could result in a bridge assigning a
bus window larger than its parent while its parent is forced to
inherit bus numbers (i.e., cannot expand) (me)
- Free struct pci_ser if an error occurs during pci_flb_retrieve()
(Sashiko)
- Detect duplicate entries in incoming FLB when constructing xarray and
return an error instead of silently ignoring (me)
- Add a wrapper function for fetching from the outgoing FLB (me)
- Grammatical and spelling fixes (Bjorn)
- Don't bother forcing bus inheritance when only outgoing devices are
preserved. If a scan causes a bus number conflict, that is a general
PCI core bug; it is not specific to Live Update (me)
- Convert bitfields in struct pci_liveupdate to bools to avoid data
races (me)
v5: https://lore.kernel.org/linux-pci/20260512184846.119396-1-dmatlack@google.com/
v4: https://lore.kernel.org/linux-pci/20260423212316.3431746-1-dmatlack@google.com/
v3: https://lore.kernel.org/kvm/20260323235817.1960573-1-dmatlack@google.com/
v2: https://lore.kernel.org/kvm/20260129212510.967611-1-dmatlack@google.com/
v1: https://lore.kernel.org/kvm/20251126193608.2678510-1-dmatlack@google.com/
rfc: https://lore.kernel.org/kvm/20251018000713.677779-1-vipinsh@google.com/
David Matlack (12):
PCI: liveupdate: Set up FLB handler for the PCI core
PCI: liveupdate: Track outgoing preserved PCI devices
PCI: liveupdate: Track incoming preserved PCI devices
PCI: liveupdate: Document driver binding responsibilities
PCI: liveupdate: Keep bus numbers constant during Live Update
PCI: liveupdate: Auto-preserve upstream bridges across Live Update
PCI: Refactor matching logic for pci_dev_acs_ops
PCI: liveupdate: Inherit ACS flags in incoming preserved devices
PCI: liveupdate: Inherit ARI Forwarding Enable on preserved bridges
PCI: liveupdate: Freeze preservation status during shutdown
PCI: liveupdate: Do not disable bus mastering on preserved devices
during kexec
Documentation: PCI: Add documentation for Live Update
Documentation/PCI/index.rst | 1 +
Documentation/PCI/liveupdate.rst | 29 +
.../admin-guide/kernel-parameters.txt | 6 +-
Documentation/core-api/liveupdate.rst | 1 +
MAINTAINERS | 12 +
drivers/pci/Kconfig | 15 +
drivers/pci/Makefile | 1 +
drivers/pci/liveupdate.c | 850 ++++++++++++++++++
drivers/pci/liveupdate.h | 68 ++
drivers/pci/pci-driver.c | 9 +-
drivers/pci/pci.c | 13 +-
drivers/pci/pci.h | 5 +
drivers/pci/probe.c | 22 +-
drivers/pci/quirks.c | 57 +-
include/linux/kho/abi/pci.h | 65 ++
include/linux/pci.h | 4 +
include/linux/pci_liveupdate.h | 77 ++
17 files changed, 1197 insertions(+), 38 deletions(-)
create mode 100644 Documentation/PCI/liveupdate.rst
create mode 100644 drivers/pci/liveupdate.c
create mode 100644 drivers/pci/liveupdate.h
create mode 100644 include/linux/kho/abi/pci.h
create mode 100644 include/linux/pci_liveupdate.h
base-commit: 34e8f02817e31826e76bb2ded48bf28fe921f20b
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply
* Re: [PATCH v2 1/6] alloc_tag: add ioctl to /proc/allocinfo
From: Andrew Morton @ 2026-05-22 20:11 UTC (permalink / raw)
To: Abhishek Bapat
Cc: Suren Baghdasaryan, Kent Overstreet, Hao Ge, Shuah Khan,
Jonathan Corbet, linux-doc, linux-kernel, linux-mm, Sourav Panda
In-Reply-To: <8ffa0cef49b10026f2171d41b963c39201c9bd5b.1779471082.git.abhishekbapat@google.com>
On Fri, 22 May 2026 17:45:33 +0000 Abhishek Bapat <abhishekbapat@google.com> wrote:
> From: Suren Baghdasaryan <surenb@google.com>
>
> Add the following ioctl commands for /proc/allocinfo file:
>
> ALLOCINFO_IOC_CONTENT_ID - gets content identifier which can be used
> to check whether the file content has changed specifically due to module
> load/unload. Every time a module is loaded / unloaded, the returned
> value will be different. By comparing the identifier value at the
> beginning and at the end of the content retrieval operation, users can
> validate retrieved information for consistency.
>
> ALLOCINFO_IOC_GET_AT - gets the record at the specified position. This
> is the position of a record in /proc/allocinfo.
>
> ALLOCINFO_IOC_GET_NEXT - gets the record next to the last retrieved
> one. If no records were previously retrieved, returns the first
> record.
>
> index 000000000000..e9a5b55fcc7a
> --- /dev/null
> +++ b/include/uapi/linux/alloc_tag.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * include/linux/alloc_tag.h
> + */
> +
> +#ifndef _UAPI_ALLOC_TAG_H
> +#define _UAPI_ALLOC_TAG_H
> +
> +#include <linux/types.h>
> +
> +#define ALLOCINFO_STR_SIZE 64
> +
> +struct allocinfo_content_id {
> + __u64 id;
> +};
> +
> +struct allocinfo_tag {
> + /* Longer names are trimmed */
> + char modname[ALLOCINFO_STR_SIZE];
> + char function[ALLOCINFO_STR_SIZE];
> + char filename[ALLOCINFO_STR_SIZE];
> + __u64 lineno;
> +};
> +
> +struct allocinfo_counter {
> + __u64 bytes;
> + __u64 calls;
> + __u8 accurate;
> + __u8 pad[7]; /* Add alignment to not break the 32-bit compatible interface */
This seems rather fragile, and makes assumptions about compiler layout?
Can't we use __attribute__((aligned)) in some fashion?
> +};
> +
> +struct allocinfo_tag_data {
> + struct allocinfo_tag tag;
> + struct allocinfo_counter counter;
> +};
> +
> +struct allocinfo_get_at {
> + __u64 pos; /* input */
> + struct allocinfo_tag_data data;
> +};
> +
> +#define _ALLOCINFO_IOC_CONTENT_ID 0
> +#define _ALLOCINFO_IOC_GET_AT 1
> +#define _ALLOCINFO_IOC_GET_NEXT 2
> +
> +#define ALLOCINFO_IOC_BASE 0xA6
> +#define ALLOCINFO_IOC_CONTENT_ID _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_CONTENT_ID, \
> + struct allocinfo_content_id)
> +#define ALLOCINFO_IOC_GET_AT _IOWR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_AT, \
> + struct allocinfo_get_at)
> +#define ALLOCINFO_IOC_GET_NEXT _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_NEXT, \
> + struct allocinfo_tag_data)
> +
> +#endif /* _UAPI_ALLOC_TAG_H */
> diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
> index b9ca95d1f506..3598735b6c93 100644
> --- a/lib/alloc_tag.c
> +++ b/lib/alloc_tag.c
> @@ -5,6 +5,7 @@
> #include <linux/gfp.h>
> #include <linux/kallsyms.h>
> #include <linux/module.h>
> +#include <linux/mutex.h>
> #include <linux/page_ext.h>
> #include <linux/pgalloc_tag.h>
> #include <linux/proc_fs.h>
> @@ -14,6 +15,7 @@
> #include <linux/string_choices.h>
> #include <linux/vmalloc.h>
> #include <linux/kmemleak.h>
> +#include <uapi/linux/alloc_tag.h>
>
> #define ALLOCINFO_FILE_NAME "allocinfo"
> #define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag))
> @@ -46,6 +48,10 @@ int alloc_tag_ref_offs;
> struct allocinfo_private {
> struct codetag_iterator iter;
> bool print_header;
> + /* ioctl uses a separate iterator not to interfere with reads */
> + struct codetag_iterator ioctl_iter;
> + bool positioned; /* seq_open_private() sets to 0 */
> + struct mutex ioctl_lock;
> };
>
> static void *allocinfo_start(struct seq_file *m, loff_t *pos)
> @@ -125,6 +131,190 @@ static const struct seq_operations allocinfo_seq_op = {
> .show = allocinfo_show,
> };
>
> +static int allocinfo_open(struct inode *inode, struct file *file)
> +{
> + int ret;
> +
> + ret = seq_open_private(file, &allocinfo_seq_op,
> + sizeof(struct allocinfo_private));
> + if (!ret) {
> + struct seq_file *m = file->private_data;
> + struct allocinfo_private *priv = m->private;
> +
> + mutex_init(&priv->ioctl_lock);
> + }
> + return ret;
> +}
Generally, the commenting in here is very thin. Add some explanations
of what the various functions do and, especially, why they do it?
> +static int allocinfo_release(struct inode *inode, struct file *file)
> +{
> + return seq_release_private(inode, file);
> +}
> +
> +static const char *allocinfo_str(const char *str)
> +{
> + size_t len = strlen(str);
> +
> + /* Keep an extra space for the trailing NULL. */
> + if (len >= ALLOCINFO_STR_SIZE)
> + str += (len - ALLOCINFO_STR_SIZE) + 1;
> + return str;
> +}
> +
> +/* Copy a string and trim from the beginning if it's too long */
> +static void allocinfo_copy_str(char *dest, const char *src)
> +{
> + strscpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
> +}
See, even these two little functions are unnecessarily difficult to
review when one doesn"t know what they are setting out to do. One has
to first reverse engineer their intent from the implementation, then
check that the implementation implements that intent.
> +static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
> +{
> + struct allocinfo_private *priv;
> + struct codetag *ct;
> + __u64 pos;
> + struct allocinfo_get_at params = {0};
> +
> + if (copy_from_user(¶ms, arg, sizeof(params)))
> + return -EFAULT;
> +
> + priv = (struct allocinfo_private *)m->private;
Unneeded cast.
> + pos = params.pos;
> +
> + mutex_lock(&priv->ioctl_lock);
> + codetag_lock_module_list(alloc_tag_cttype, true);
> +
> + /* Find the codetag */
> + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
> + ct = codetag_next_ct(&priv->ioctl_iter);
> + while (ct && pos--)
> + ct = codetag_next_ct(&priv->ioctl_iter);
> + if (ct) {
> + allocinfo_to_params(ct, ¶ms.data);
> + priv->positioned = true;
> + }
> +
> + codetag_lock_module_list(alloc_tag_cttype, false);
> + mutex_unlock(&priv->ioctl_lock);
> +
> + if (!ct)
> + return -ENOENT;
> +
> + if (copy_to_user(arg, ¶ms, sizeof(params)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
> +{
> + struct allocinfo_private *priv;
> + struct codetag *ct;
> + struct allocinfo_tag_data params = {0};
> + int ret = 0;
> +
> + priv = (struct allocinfo_private *)m->private;
Ditto.
> + mutex_lock(&priv->ioctl_lock);
> + codetag_lock_module_list(alloc_tag_cttype, true);
> +
> + if (!priv->positioned) {
> + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
> + priv->positioned = true;
> + }
> +
> + ct = codetag_next_ct(&priv->ioctl_iter);
> + if (ct)
> + allocinfo_to_params(ct, ¶ms);
> +
> + if (!ct) {
> + priv->positioned = false;
> + ret = -ENOENT;
> + }
> + codetag_lock_module_list(alloc_tag_cttype, false);
> + mutex_unlock(&priv->ioctl_lock);
> +
> + if (ret == 0) {
> + if (copy_to_user(arg, ¶ms, sizeof(params)))
> + return -EFAULT;
> + }
> + return ret;
> +}
>
> ...
>
^ permalink raw reply
* Re: [PATCH v2 0/6] alloc_tag: introduce IOCTL-based filtering for MAP
From: Andrew Morton @ 2026-05-22 20:11 UTC (permalink / raw)
To: Abhishek Bapat
Cc: Suren Baghdasaryan, Kent Overstreet, Hao Ge, Shuah Khan,
Jonathan Corbet, linux-doc, linux-kernel, linux-mm, Sourav Panda
In-Reply-To: <cover.1779471082.git.abhishekbapat@google.com>
On Fri, 22 May 2026 17:45:32 +0000 Abhishek Bapat <abhishekbapat@google.com> wrote:
> Currently, memory allocation profiling data is primarily exposed through
> /proc/allocinfo. While useful for manual inspection, this text-based
> interface poses challenges for production monitoring and large-scale
> analysis:
>
> 1. Userspace must parse large amounts of text to extract specific
> fields.
> 2. To find specific tags, userspace must read the entire dataset,
> requiring many context switches and high data copying.
> 3. The kernel currently aggregates per-CPU counters for every allocation
> size, even those the user intends to filter out immediately.
>
> This series introduces a new IOCTL-based binary interface for allocinfo
> that supports kernel-side filtering. By allowing the user to specify a
> filter mask, we significantly reduce the work performed in-kernel and
> the amount of data transferred to userspace.
>
> Performance measurements were conducted on an Intel Xeon Platinum 8481C
> (224 CPUs) with caches dropped before each run.
>
> The IOCTL mechanism shows a ~20x performance improvement for
> filtered queries. The kernel avoids the expensive per-CPU counter
> aggregation (alloc_tag_read) for any tags that fail the initial string
> or location filters.
>
> Scenario 1: Specific File Filtering (arch/x86/events/rapl.c)
> 1. Traditional (cat /proc/allocinfo | grep): 22ms (sys)
> 2. IOCTL Interface: 1ms (sys)
>
> Scenario 2: Compound Filtering (Filename + Size)
> 1. Traditional: (cat ... | grep | awk): 21ms (sys)
> 2. IOCTL Interface: 1ms (sys)
>
> Scenario 3: Size-Based Filtering (min_size = 1MB)
> 1. Traditional: (cat ... | awk): 21ms (sys)
> 2. IOCTL Interface: 14ms (sys)
Yup, textual interfaces aren't fast.
And ioctl-baed interfaces aren't popular. One would prefer to see an
interface which uses read()/lseek(), pread(), etc. It would be
appropriate for this [0/N] to have a discussion of why that approach
was not chosen.
> .../userspace-api/ioctl/ioctl-number.rst | 2 +
> MAINTAINERS | 2 +
> include/linux/codetag.h | 1 +
> include/uapi/linux/alloc_tag.h | 87 +++
> lib/alloc_tag.c | 303 ++++++++++-
> lib/codetag.c | 11 +
> tools/testing/selftests/alloc_tag/Makefile | 9 +
> .../alloc_tag/allocinfo_ioctl_test.c | 505 ++++++++++++++++++
> 8 files changed, 918 insertions(+), 2 deletions(-)
> create mode 100644 include/uapi/linux/alloc_tag.h
> create mode 100644 tools/testing/selftests/alloc_tag/Makefile
> create mode 100644 tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
At some point this should grow user-facing documentation, please.
And the right time for that is now, because such documentation is
useful for code review - it makes that review both easier and more
useful.
Sashiko had a few things to say:
https://sashiko.dev/#/patchset/cover.1779471082.git.abhishekbapat@google.com
^ permalink raw reply
* [PATCH v5 21/21] nfsd: add support to CB_NOTIFY for dir attribute changes
From: Jeff Layton @ 2026-05-22 19:42 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Jonathan Corbet, Shuah Khan
Cc: Steven Rostedt, Alexander Aring, Amir Goldstein, Jan Kara,
Alexander Viro, Christian Brauner, Calum Mackay, linux-kernel,
linux-doc, linux-nfs, Jeff Layton
In-Reply-To: <20260522-dir-deleg-v5-0-542cddfad576@kernel.org>
If the client requested dir attribute change notifications, send those
alongside any set of add/remove/rename events. Note that the server will
still recall the delegation on a SETATTR, so these are only sent for
changes to child dirents.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfs4state.c | 25 ++++++++++++++++++++--
fs/nfsd/nfs4xdr.c | 61 +++++++++++++++++++++++++++++++++++++++++++++--------
fs/nfsd/xdr4.h | 2 ++
3 files changed, 77 insertions(+), 11 deletions(-)
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 6c808149f251..acddd55a99d8 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -3491,10 +3491,15 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
struct nfsd_notify_event *events[NOTIFY4_EVENT_QUEUE_SIZE];
struct xdr_buf xdr = { .buflen = PAGE_SIZE * NOTIFY4_PAGE_ARRAY_SIZE,
.pages = ncn->ncn_pages };
+ int limit = NOTIFY4_EVENT_QUEUE_SIZE;
struct xdr_stream stream;
struct nfsd_file *nf;
- int count, i;
bool error = false;
+ int count, i;
+
+ /* Save a slot for dir attr update if requested */
+ if (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS))
+ --limit;
xdr_init_encode_pages(&stream, &xdr);
@@ -3508,7 +3513,7 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
}
/* we can't keep up! */
- if (count > NOTIFY4_EVENT_QUEUE_SIZE) {
+ if (count > limit) {
spin_unlock(&ncn->ncn_lock);
goto out_recall;
}
@@ -3555,6 +3560,22 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb)
nfsd_notify_event_put(nne);
}
if (!error) {
+ if (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS)) {
+ u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp));
+
+ if (maskp) {
+ u8 *p = nfsd4_encode_dir_attr_change(&stream, dp, nf);
+
+ if (p) {
+ *maskp = BIT(NOTIFY4_CHANGE_DIR_ATTRS);
+ ncn->ncn_nf[count].notify_mask.count = 1;
+ ncn->ncn_nf[count].notify_mask.element = maskp;
+ ncn->ncn_nf[count].notify_vals.data = p;
+ ncn->ncn_nf[count].notify_vals.len = (u8 *)stream.p - p;
+ ++count;
+ }
+ }
+ }
ncn->ncn_nf_cnt = count;
nfsd_file_put(nf);
return true;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 4fe697bf34e7..2143fb6d5e3f 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4227,11 +4227,11 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
struct nfsd_file *nf, char *name, u32 namelen)
{
struct nfs4_file *fi = dp->dl_stid.sc_file;
- struct path path = { .mnt = nf->nf_file->f_path.mnt,
- .dentry = dentry };
+ struct path path = nf->nf_file->f_path;
struct nfsd4_fattr_args args = { };
uint32_t *attrmask;
__be32 status;
+ bool parent;
int ret;
/* Reserve space for attrmask */
@@ -4243,6 +4243,9 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
ne->ne_file.len = namelen;
ne->ne_attrs.attrmask.element = attrmask;
+ parent = (dentry == path.dentry);
+ path.dentry = dentry;
+
/* FIXME: d_find_alias for inode ? */
if (!path.dentry || !d_inode(path.dentry))
goto noattrs;
@@ -4258,15 +4261,20 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
args.change_attr = nfsd4_change_attribute(&args.stat);
- attrmask[0] = dp->dl_child_attrs[0];
- attrmask[1] = dp->dl_child_attrs[1];
- attrmask[2] = 0;
+ if (parent) {
+ attrmask[0] = dp->dl_dir_attrs[0];
+ attrmask[1] = dp->dl_dir_attrs[1];
+ } else {
+ attrmask[0] = dp->dl_child_attrs[0];
+ attrmask[1] = dp->dl_child_attrs[1];
- if (!setup_notify_fhandle(dentry, fi, nf, &args))
- attrmask[0] &= ~FATTR4_WORD0_FILEHANDLE;
+ if (!setup_notify_fhandle(dentry, fi, nf, &args))
+ attrmask[0] &= ~FATTR4_WORD0_FILEHANDLE;
- if (!(args.stat.result_mask & STATX_BTIME))
- attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
+ if (!(args.stat.result_mask & STATX_BTIME))
+ attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
+ }
+ attrmask[2] = 0;
ne->ne_attrs.attrmask.count = 2;
ne->ne_attrs.attr_vals.data = (u8 *)xdr->p;
@@ -4383,6 +4391,41 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *
return NULL;
}
+/**
+ * nfsd4_encode_dir_attr_change
+ * @xdr: stream to which to encode the fattr4
+ * @dp: delegation where the event occurred
+ * @nf: nfsd_file opened on the directory
+ *
+ * Encode a dir attr change event.
+ */
+u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation *dp,
+ struct nfsd_file *nf)
+{
+ struct dentry *dentry = nf->nf_file->f_path.dentry;
+ struct notify_attr4 na = { };
+ struct name_snapshot n;
+ bool ret;
+ u8 *p = NULL;
+
+ if (!(dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS)))
+ return NULL;
+
+ take_dentry_name_snapshot(&n, dentry);
+ ret = nfsd4_setup_notify_entry4(&na.na_changed_entry, xdr,
+ dentry, dp, nf, (char *)n.name.name,
+ n.name.len);
+
+ /* Don't bother with the event if we're not encoding attrs */
+ if (ret && na.na_changed_entry.ne_attrs.attr_vals.len) {
+ p = (u8 *)xdr->p;
+ if (!xdrgen_encode_notify_attr4(xdr, &na))
+ p = NULL;
+ }
+ release_dentry_name_snapshot(&n);
+ return p;
+}
+
static void svcxdr_init_encode_from_buffer(struct xdr_stream *xdr,
struct xdr_buf *buf, __be32 *p, int bytes)
{
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index 62ac790428be..805c7122eb93 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -973,6 +973,8 @@ __be32 nfsd4_encode_fattr_to_buf(__be32 **p, int words,
u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event *nne,
struct nfs4_delegation *dd, struct nfsd_file *nf,
u32 *notify_mask);
+u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation *dp,
+ struct nfsd_file *nf);
extern __be32 nfsd4_setclientid(struct svc_rqst *rqstp,
struct nfsd4_compound_state *, union nfsd4_op_u *u);
extern __be32 nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
--
2.54.0
^ permalink raw reply related
* [PATCH v5 20/21] nfsd: track requested dir attributes
From: Jeff Layton @ 2026-05-22 19:42 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Jonathan Corbet, Shuah Khan
Cc: Steven Rostedt, Alexander Aring, Amir Goldstein, Jan Kara,
Alexander Viro, Christian Brauner, Calum Mackay, linux-kernel,
linux-doc, linux-nfs, Jeff Layton
In-Reply-To: <20260522-dir-deleg-v5-0-542cddfad576@kernel.org>
Track the union of requested and supported dir attributes in the
delegation. In a later patch this will be used to ensure that we
only encode the attributes in that union when sending
add/remove/rename updates.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfs4proc.c | 9 ++++++---
fs/nfsd/nfs4state.c | 14 +++++++++++++-
fs/nfsd/state.h | 2 ++
3 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 1eed8f23551d..43da73a537ad 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -2521,9 +2521,10 @@ nfsd4_verify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
return status == nfserr_same ? nfs_ok : status;
}
-#define SUPPORTED_NOTIFY_MASK (BIT(NOTIFY4_REMOVE_ENTRY) | \
- BIT(NOTIFY4_ADD_ENTRY) | \
- BIT(NOTIFY4_RENAME_ENTRY) | \
+#define SUPPORTED_NOTIFY_MASK (BIT(NOTIFY4_CHANGE_DIR_ATTRS) | \
+ BIT(NOTIFY4_REMOVE_ENTRY) | \
+ BIT(NOTIFY4_ADD_ENTRY) | \
+ BIT(NOTIFY4_RENAME_ENTRY) | \
BIT(NOTIFY4_GFLAG_EXTEND))
static __be32
@@ -2570,6 +2571,8 @@ nfsd4_get_dir_delegation(struct svc_rqst *rqstp,
memcpy(&gdd->gddr_stateid, &dd->dl_stid.sc_stateid, sizeof(gdd->gddr_stateid));
gdd->gddr_child_attributes[0] = dd->dl_child_attrs[0];
gdd->gddr_child_attributes[1] = dd->dl_child_attrs[1];
+ gdd->gddr_dir_attributes[0] = dd->dl_dir_attrs[0];
+ gdd->gddr_dir_attributes[1] = dd->dl_dir_attrs[1];
nfs4_put_stid(&dd->dl_stid);
return nfs_ok;
}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index ce740a94a634..6c808149f251 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -9835,6 +9835,15 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
FATTR4_WORD1_TIME_MODIFY | \
FATTR4_WORD1_TIME_CREATE)
+#define GDD_WORD0_DIR_ATTRS (FATTR4_WORD0_CHANGE | \
+ FATTR4_WORD0_SIZE)
+
+#define GDD_WORD1_DIR_ATTRS (FATTR4_WORD1_NUMLINKS | \
+ FATTR4_WORD1_SPACE_USED | \
+ FATTR4_WORD1_TIME_ACCESS | \
+ FATTR4_WORD1_TIME_METADATA | \
+ FATTR4_WORD1_TIME_MODIFY)
+
/**
* nfsd_get_dir_deleg - attempt to get a directory delegation
* @cstate: compound state
@@ -9904,10 +9913,13 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
dp->dl_stid.sc_export =
exp_get(cstate->current_fh.fh_export);
+ dp->dl_notify_mask = gdd->gddr_notification[0];
dp->dl_child_attrs[0] = gdd->gdda_child_attributes[0] & GDD_WORD0_CHILD_ATTRS;
dp->dl_child_attrs[1] = gdd->gdda_child_attributes[1] & GDD_WORD1_CHILD_ATTRS;
+ dp->dl_dir_attrs[0] = gdd->gdda_dir_attributes[0] & GDD_WORD0_DIR_ATTRS;
+ dp->dl_dir_attrs[1] = gdd->gdda_dir_attributes[1] & GDD_WORD1_DIR_ATTRS;
- fl = nfs4_alloc_init_lease(dp, gdd->gddr_notification[0]);
+ fl = nfs4_alloc_init_lease(dp, dp->dl_notify_mask);
if (!fl)
goto out_put_stid;
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 461abcee9e6c..62a5fe3f6cc0 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -286,7 +286,9 @@ struct nfs4_delegation {
struct timespec64 dl_ctime;
/* For dir delegations */
+ uint32_t dl_notify_mask;
uint32_t dl_child_attrs[2];
+ uint32_t dl_dir_attrs[2];
};
static inline bool deleg_is_read(u32 dl_type)
--
2.54.0
^ permalink raw reply related
* [PATCH v5 19/21] nfsd: properly track requested child attributes
From: Jeff Layton @ 2026-05-22 19:42 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Jonathan Corbet, Shuah Khan
Cc: Steven Rostedt, Alexander Aring, Amir Goldstein, Jan Kara,
Alexander Viro, Christian Brauner, Calum Mackay, linux-kernel,
linux-doc, linux-nfs, Jeff Layton
In-Reply-To: <20260522-dir-deleg-v5-0-542cddfad576@kernel.org>
Track the union of requested and supported child attributes in the
delegation, and only encode the attributes in that union when sending
add/remove/rename updates.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfs4proc.c | 2 ++
fs/nfsd/nfs4state.c | 18 ++++++++++++++++++
fs/nfsd/nfs4xdr.c | 15 ++++++---------
fs/nfsd/state.h | 3 +++
4 files changed, 29 insertions(+), 9 deletions(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 30f338f90acd..1eed8f23551d 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -2568,6 +2568,8 @@ nfsd4_get_dir_delegation(struct svc_rqst *rqstp,
gdd->gddrnf_status = GDD4_OK;
memcpy(&gdd->gddr_stateid, &dd->dl_stid.sc_stateid, sizeof(gdd->gddr_stateid));
+ gdd->gddr_child_attributes[0] = dd->dl_child_attrs[0];
+ gdd->gddr_child_attributes[1] = dd->dl_child_attrs[1];
nfs4_put_stid(&dd->dl_stid);
return nfs_ok;
}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index eed997d1c88f..ce740a94a634 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -9820,6 +9820,21 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
return status;
}
+#define GDD_WORD0_CHILD_ATTRS (FATTR4_WORD0_TYPE | \
+ FATTR4_WORD0_CHANGE | \
+ FATTR4_WORD0_SIZE | \
+ FATTR4_WORD0_FILEID | \
+ FATTR4_WORD0_FILEHANDLE)
+
+#define GDD_WORD1_CHILD_ATTRS (FATTR4_WORD1_MODE | \
+ FATTR4_WORD1_NUMLINKS | \
+ FATTR4_WORD1_RAWDEV | \
+ FATTR4_WORD1_SPACE_USED | \
+ FATTR4_WORD1_TIME_ACCESS | \
+ FATTR4_WORD1_TIME_METADATA | \
+ FATTR4_WORD1_TIME_MODIFY | \
+ FATTR4_WORD1_TIME_CREATE)
+
/**
* nfsd_get_dir_deleg - attempt to get a directory delegation
* @cstate: compound state
@@ -9889,6 +9904,9 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
dp->dl_stid.sc_export =
exp_get(cstate->current_fh.fh_export);
+ dp->dl_child_attrs[0] = gdd->gdda_child_attributes[0] & GDD_WORD0_CHILD_ATTRS;
+ dp->dl_child_attrs[1] = gdd->gdda_child_attributes[1] & GDD_WORD1_CHILD_ATTRS;
+
fl = nfs4_alloc_init_lease(dp, gdd->gddr_notification[0]);
if (!fl)
goto out_put_stid;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index f8534288b2fc..4fe697bf34e7 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4258,18 +4258,15 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
args.change_attr = nfsd4_change_attribute(&args.stat);
- attrmask[0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
- FATTR4_WORD0_SIZE | FATTR4_WORD0_FILEID;
- attrmask[1] = FATTR4_WORD1_MODE | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_RAWDEV |
- FATTR4_WORD1_SPACE_USED | FATTR4_WORD1_TIME_ACCESS |
- FATTR4_WORD1_TIME_METADATA | FATTR4_WORD1_TIME_MODIFY;
+ attrmask[0] = dp->dl_child_attrs[0];
+ attrmask[1] = dp->dl_child_attrs[1];
attrmask[2] = 0;
- if (setup_notify_fhandle(dentry, fi, nf, &args))
- attrmask[0] |= FATTR4_WORD0_FILEHANDLE;
+ if (!setup_notify_fhandle(dentry, fi, nf, &args))
+ attrmask[0] &= ~FATTR4_WORD0_FILEHANDLE;
- if (args.stat.result_mask & STATX_BTIME)
- attrmask[1] |= FATTR4_WORD1_TIME_CREATE;
+ if (!(args.stat.result_mask & STATX_BTIME))
+ attrmask[1] &= ~FATTR4_WORD1_TIME_CREATE;
ne->ne_attrs.attrmask.count = 2;
ne->ne_attrs.attr_vals.data = (u8 *)xdr->p;
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 56008234b700..461abcee9e6c 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -284,6 +284,9 @@ struct nfs4_delegation {
struct timespec64 dl_atime;
struct timespec64 dl_mtime;
struct timespec64 dl_ctime;
+
+ /* For dir delegations */
+ uint32_t dl_child_attrs[2];
};
static inline bool deleg_is_read(u32 dl_type)
--
2.54.0
^ permalink raw reply related
* [PATCH v5 18/21] nfsd: add the filehandle to returned attributes in CB_NOTIFY
From: Jeff Layton @ 2026-05-22 19:42 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Jonathan Corbet, Shuah Khan
Cc: Steven Rostedt, Alexander Aring, Amir Goldstein, Jan Kara,
Alexander Viro, Christian Brauner, Calum Mackay, linux-kernel,
linux-doc, linux-nfs, Jeff Layton
In-Reply-To: <20260522-dir-deleg-v5-0-542cddfad576@kernel.org>
nfsd's usual fh_compose routine requires a svc_export and fills out a
svc_fh. In the context of a CB_NOTIFY there is no such export to
consult.
Add a new routine that composes a filehandle with only a parent
filehandle and nfs4_file. Use that to fill out the fhandle field in the
nfsd4_fattr_args.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfs4xdr.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 33b6cd492e56..f8534288b2fc 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4184,6 +4184,39 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
goto out;
}
+static bool
+setup_notify_fhandle(struct dentry *dentry, struct nfs4_file *fi,
+ struct nfsd_file *nf, struct nfsd4_fattr_args *args)
+{
+ int fileid_type, fsid_len, maxsize, flags = 0;
+ struct knfsd_fh *fhp = &args->fhandle;
+ struct inode *inode = d_inode(dentry);
+ struct inode *parent = NULL;
+ struct fid *fid;
+
+ fsid_len = key_len(fi->fi_fhandle.fh_fsid_type);
+ fhp->fh_size = 4 + fsid_len;
+
+ /* Copy first 4 bytes + fsid */
+ memcpy(&fhp->fh_raw, &fi->fi_fhandle.fh_raw, fhp->fh_size);
+
+ fid = (struct fid *)(fh_fsid(fhp) + fsid_len/4);
+ maxsize = (NFS4_FHSIZE - fhp->fh_size)/4;
+
+ if (fi->fi_connectable && !S_ISDIR(inode->i_mode)) {
+ parent = d_inode(nf->nf_file->f_path.dentry);
+ flags = EXPORT_FH_CONNECTABLE;
+ }
+
+ fileid_type = exportfs_encode_inode_fh(inode, fid, &maxsize, parent, flags);
+ if (fileid_type < 0)
+ return false;
+
+ fhp->fh_fileid_type = fileid_type;
+ fhp->fh_size += maxsize * 4;
+ return true;
+}
+
#define CB_NOTIFY_STATX_REQUEST_MASK (STATX_BASIC_STATS | \
STATX_BTIME | \
STATX_CHANGE_COOKIE)
@@ -4193,6 +4226,7 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
struct dentry *dentry, struct nfs4_delegation *dp,
struct nfsd_file *nf, char *name, u32 namelen)
{
+ struct nfs4_file *fi = dp->dl_stid.sc_file;
struct path path = { .mnt = nf->nf_file->f_path.mnt,
.dentry = dentry };
struct nfsd4_fattr_args args = { };
@@ -4231,6 +4265,9 @@ nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr,
FATTR4_WORD1_TIME_METADATA | FATTR4_WORD1_TIME_MODIFY;
attrmask[2] = 0;
+ if (setup_notify_fhandle(dentry, fi, nf, &args))
+ attrmask[0] |= FATTR4_WORD0_FILEHANDLE;
+
if (args.stat.result_mask & STATX_BTIME)
attrmask[1] |= FATTR4_WORD1_TIME_CREATE;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 17/21] nfsd: add a fi_connectable flag to struct nfs4_file
From: Jeff Layton @ 2026-05-22 19:42 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Jonathan Corbet, Shuah Khan
Cc: Steven Rostedt, Alexander Aring, Amir Goldstein, Jan Kara,
Alexander Viro, Christian Brauner, Calum Mackay, linux-kernel,
linux-doc, linux-nfs, Jeff Layton
In-Reply-To: <20260522-dir-deleg-v5-0-542cddfad576@kernel.org>
When encoding a filehandle for a CB_NOTIFY, there is no svc_export
available, but the server needs to know whether to encode a connectable
filehandle. Add a flag to the nfs4_file that tells whether the
svc_export under which a directory delegation was acquired has subtree
checking enabled, in which case it needs connectable filehandles.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfsd/nfs4state.c | 1 +
fs/nfsd/state.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index e00b4463c89d..eed997d1c88f 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -5179,6 +5179,7 @@ static void nfsd4_file_init(const struct svc_fh *fh, struct nfs4_file *fp)
memset(fp->fi_access, 0, sizeof(fp->fi_access));
fp->fi_aliased = false;
fp->fi_inode = d_inode(fh->fh_dentry);
+ fp->fi_connectable = !(fh->fh_export->ex_flags & NFSEXP_NOSUBTREECHECK);
#ifdef CONFIG_NFSD_PNFS
INIT_LIST_HEAD(&fp->fi_lo_states);
atomic_set(&fp->fi_lo_recalls, 0);
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index e85cce4f8bc5..56008234b700 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -747,6 +747,7 @@ struct nfs4_file {
int fi_delegees;
struct knfsd_fh fi_fhandle;
bool fi_had_conflict;
+ bool fi_connectable;
#ifdef CONFIG_NFSD_PNFS
struct list_head fi_lo_states;
atomic_t fi_lo_recalls;
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox