* Re: [PATCH mm-unstable v18 11/14] mm/khugepaged: Introduce mTHP collapse support
From: David Hildenbrand (Arm) @ 2026-06-01 13:15 UTC (permalink / raw)
To: Nico Pache, Usama Arif, usamaarif642
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, 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, vbabka, vishal.moola,
wangkefeng.wang, will, willy, yang, ying.huang, ziy, zokeefe
In-Reply-To: <CAA1CXcBg1su-bk3i_H+TW4-nTgvGSGqRNeC9MpQo7sGeH8ejnA@mail.gmail.com>
>>
>> Reading this, it is unclear why exactly do we need the stack.
>
> So I looked into your items below. It seems logical, and I think it
> works the same way; however, your method seems slightly harder to
> understand due to all the edge cases and more error-prone to future
> changes (the stack holds implicit knowledge of the offset/order that
> must now be tracked in the edge cases).
>
> Given the stack is 24 bytes, I'm not sure if the extra complexity is
> worth saving that small amount of memory. Although we would also be
> getting rid of (3?) functions, so both approaches have pros and cons.
I consider a simple forward loop over the offset ... less complexity compared to
a stack structure :)
>
> I will implement a patch comparing your solution against mine and send
> it here, then we can decide which approach is better.
Right, throw it over the fence and I'll see how to improve it further.
[...]
>>> + bitmap_zero(cc->mthp_bitmap, MAX_PTRS_PER_PTE);
>>> memset(cc->node_load, 0, sizeof(cc->node_load));
>>> nodes_clear(cc->alloc_nmask);
>>> +
>>> + enabled_orders = collapse_allowable_orders(vma, vma->vm_flags, tva_flags);
>>> +
>>> + /*
>>> + * If PMD is the only enabled order, enforce max_ptes_none, otherwise
>>> + * scan all pages to populate the bitmap for mTHP collapse.
>>> + */
>>
>> You should note here, that we re-verify in mthp_collapse().
>>
>> But the question is, whether we should relocate the check completely into
>> mthp_collapse(), instead of conditionally duplicating it.
>>
>> What speaks against always populating the bitmap and making the decision in
>> mthp_collapse()?
>>
>> Sure, we might scan a page table a bit longer, but the code gets clearer ... and
>> I am not sure if scanning some more page table entries is really that critical here.
>
> Someone asked me to preserve the legacy behavior (PMD only). Although
> rather trivial, if you set max_ptes_none=0 for example, we'd still
> have to do 511 iterations for no reason if PMD collapse is the only
> enabled order rather than bailing immediately.
>
> I'm ok with dropping it, but I think its the correct approach (despite
> the extra complexity). @Usama Arif brought up this point here
> https://lore.kernel.org/all/f8f7bb71-ca31-46ee-a62d-7ddfd83e0ead@gmail.com/
We talk about regressions, but I am not sure if we care about scanning speed
within a page table that much?
After all, we locked it and already read some entries.
Having the same check at two places to optimize for PMD order might right now
feel like a good optimization, but likely an irrelevant one in a near future?
Anyhow, won't push back, as long as we document why we are special casing things
here.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH v8 2/6] mm/memory-failure: surface unhandlable kernel pages as -ENOTRECOVERABLE
From: David Hildenbrand (Arm) @ 2026-06-01 13:22 UTC (permalink / raw)
To: Miaohe Lin, Breno Leitao
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest,
linux-trace-kernel, kernel-team, Lance Yang, Andrew Morton,
Lorenzo Stoakes, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Shuah Khan, Naoya Horiguchi,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Jonathan Corbet, Shuah Khan, Liam R. Howlett
In-Reply-To: <19f968f5-1289-f573-4406-e5c91dcd8923@huawei.com>
On 6/1/26 14:28, Miaohe Lin wrote:
> On 2026/5/27 22:06, Breno Leitao wrote:
>> get_any_page() collapses every HWPoisonHandlable() rejection into a
>> single -EIO via the __get_hwpoison_page() -> -EBUSY -> shake_page()
>> -> retry path. That is correct for the transient case (a userspace
>> folio briefly off LRU during migration or compaction, which a later
>> shake can drag back), but wrong for stable kernel-owned pages: slab,
>> page-table, large-kmalloc and PG_reserved pages will never become
>> HWPoisonHandlable(), so the retry loop is wasted work and the final
>> -EIO loses the "this is structurally unrecoverable" information.
>> memory_failure() then maps -EIO into MF_MSG_GET_HWPOISON, which the
>> panic-on-unrecoverable sysctl deliberately does not act on.
>>
>> Introduce HWPoisonKernelOwned(), a small predicate that positively
>> identifies pages the hwpoison handler cannot recover from:
>>
>> HWPoisonKernelOwned(p, flags) :=
>> !(MF_SOFT_OFFLINE && page_has_movable_ops(p)) &&
>> (PageReserved(p) || PageSlab(p) ||
>> PageTable(p) || PageLargeKmalloc(p))
>>
>> The MF_SOFT_OFFLINE / page_has_movable_ops() opt-out mirrors the
>> same exception in HWPoisonHandlable(): soft-offline is allowed to
>> migrate movable_ops pages even though they are not on the LRU, and
>> we must not pre-empt that with an unrecoverable verdict.
>>
>> The list is intentionally not exhaustive. vmalloc and kernel-stack
>> pages, for example, do not carry a page_type bit and would need a
>> different oracle; they keep going through the existing retry path
>> unchanged. This is the smallest set we can identify with certainty
>> by page type.
>>
>> Wire the helper into the top of get_any_page() to short-circuit
>> those pages before the retry loop runs. On a hit, drop the caller's
>> MF_COUNT_INCREASED reference (if any) and return -ENOTRECOVERABLE
>> straight away. Pages outside the helper's positive list still take
>> the existing retry path and return -EIO, leaving operator-visible
>> behaviour for those cases unchanged.
>>
>> Extend the unhandlable-page pr_err() to fire for either errno and
>> update the get_hwpoison_page() kerneldoc to document the new return.
>>
>> memory_failure() still folds every negative return into
>> MF_MSG_GET_HWPOISON via its existing "else if (res < 0)" branch, so
>> this patch on its own only changes the errno that soft_offline_page()
>> can propagate to its callers. A follow-up wires -ENOTRECOVERABLE
>> through memory_failure() and reports MF_MSG_KERNEL for the
>> unrecoverable cases, which is what the
>> panic_on_unrecoverable_memory_failure sysctl observes.
>
> Thanks for your patch.
>
>>
>> Suggested-by: David Hildenbrand <david@kernel.org>
>> Suggested-by: Lance Yang <lance.yang@linux.dev>
>> Signed-off-by: Breno Leitao <leitao@debian.org>
>> ---
>> mm/memory-failure.c | 42 ++++++++++++++++++++++++++++++++++++++++--
>> 1 file changed, 40 insertions(+), 2 deletions(-)
>>
>> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
>> index f4d3e6e20e13..8f63bdfeff8f 100644
>> --- a/mm/memory-failure.c
>> +++ b/mm/memory-failure.c
>> @@ -1325,6 +1325,28 @@ static inline bool HWPoisonHandlable(struct page *page, unsigned long flags)
>> return PageLRU(page) || is_free_buddy_page(page);
>> }
>>
>> +/*
>> + * Positive identification of pages the hwpoison handler cannot recover.
>> + * These page types are owned by kernel internals (no userspace mapping
>> + * to unmap, no file mapping to invalidate, no migration target), so the
>> + * shake_page() / retry loop in get_any_page() can never turn them into
>> + * something HWPoisonHandlable() will accept. Short-circuit them to
>> + * -ENOTRECOVERABLE so callers can panic on operator request instead of
>> + * spinning through retries that exit as a transient-looking -EIO.
>> + *
>> + * The MF_SOFT_OFFLINE / page_has_movable_ops() opt-out mirrors
>> + * HWPoisonHandlable(): soft-offline is allowed to migrate movable_ops
>> + * pages even though they are not on the LRU.
>> + */
>> +static inline bool HWPoisonKernelOwned(struct page *page, unsigned long flags)
>> +{
>> + if ((flags & MF_SOFT_OFFLINE) && page_has_movable_ops(page))
>> + return false;
>> +
>> + return PageReserved(page) || PageSlab(page) ||
>
> Once shake_page finds a lightweight range-based way to shrink slab, slab pages could be freed
> into buddy and above PageSlab test should be removed then. Maybe add a TODO or XXX here?
>
>> + PageTable(page) || PageLargeKmalloc(page);
>
> I'm not sure but is it safe or a common way to test PageReserved, PageSlab,
> PageTable and PageLargeKmalloc without extra page refcnt?
Checking typed pages in a racy fashion is fine (PageSlab, PageTable,
PageLargeKmalloc).
Checking PageReserved in a racy fashion is fine as well. TESTPAGEFLAG() will
allow checking it on compound pages.
For PageLargeKmalloc, we would want to check the head page, though. The page
type is only stored for the head page.
So maybe we want to lookup the compound head (if any) and perform the type
checks against that?
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH v8 3/6] mm/memory-failure: report MF_MSG_KERNEL for unrecoverable kernel pages
From: David Hildenbrand (Arm) @ 2026-06-01 13:24 UTC (permalink / raw)
To: Breno Leitao, Miaohe Lin, Andrew Morton, Lorenzo Stoakes,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Shuah Khan, Naoya Horiguchi, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Liam R. Howlett
Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest,
linux-trace-kernel, kernel-team
In-Reply-To: <20260527-ecc_panic-v8-3-9ea0cfa16bb0@debian.org>
On 5/27/26 16:06, Breno Leitao wrote:
> The previous patch teaches get_any_page() to return -ENOTRECOVERABLE
> for stable unhandlable kernel pages (PG_reserved, slab, page tables,
> large-kmalloc). memory_failure() still folds every negative return
> into MF_MSG_GET_HWPOISON, so callers that want to react to the
> unrecoverable cases (a panic option, smarter logging) cannot tell
> them apart from transient page-allocator races.
>
> Turn the post-call branch into a switch over the get_hwpoison_page()
> return code: map -ENOTRECOVERABLE to MF_MSG_KERNEL and any other
> negative return to MF_MSG_GET_HWPOISON. case 0 keeps the existing
> free-buddy / kernel-high-order handling and case 1 falls through to
> the rest of memory_failure() unchanged.
>
> The MF_MSG_KERNEL label and tracepoint string are kept as
> "reserved kernel page" to avoid breaking userspace tools that match
> on those literals; the enum value still adequately tags the failure
> even though it now also covers slab, page tables and large-kmalloc
> pages.
>
> Suggested-by: David Hildenbrand <david@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> mm/memory-failure.c | 17 +++++++++++++++--
> 1 file changed, 15 insertions(+), 2 deletions(-)
>
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 8f63bdfeff8f..14c0a958638c 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -2426,7 +2426,8 @@ int memory_failure(unsigned long pfn, int flags)
> * that may make page_ref_freeze()/page_ref_unfreeze() mismatch.
> */
> res = get_hwpoison_page(p, flags);
> - if (!res) {
> + switch (res) {
> + case 0:
> if (is_free_buddy_page(p)) {
> if (take_page_off_buddy(p)) {
> page_ref_inc(p);
> @@ -2445,7 +2446,19 @@ int memory_failure(unsigned long pfn, int flags)
> res = action_result(pfn, MF_MSG_KERNEL_HIGH_ORDER, MF_IGNORED);
> }
> goto unlock_mutex;
> - } else if (res < 0) {
> + case 1:
> + /* Got a refcount on a handlable page. */
> + break;
> + case -ENOTRECOVERABLE:
> + /*
> + * Stable unhandlable kernel-owned page (PG_reserved,
> + * slab, page tables, large-kmalloc).
> + * No recovery possible.
> + */
> + res = action_result(pfn, MF_MSG_KERNEL, MF_IGNORED);
> + goto unlock_mutex;
> + default:
> + /* Transient lifecycle race with the page allocator. */
> res = action_result(pfn, MF_MSG_GET_HWPOISON, MF_IGNORED);
> goto unlock_mutex;
> }
>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH mm-unstable v18 03/14] mm/khugepaged: rework max_ptes_* handling with helper functions
From: Lorenzo Stoakes @ 2026-06-01 13:26 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, 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, 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>
On Fri, May 22, 2026 at 08:59:58AM -0600, Nico Pache wrote:
> The following cleanup reworks all the max_ptes_* handling into helper
> functions. This increases the code readability and will later be used to
> implement the mTHP handling of these variables.
>
> With these changes we abstract all the madvise_collapse() special casing
> (do not respect the sysctls) away from the functions that utilize them.
> And will be used later in this series to cleanly restrict the mTHP
> collapse behavior.
>
> No functional change is intended; however, we are now only reading the
> sysfs variables once per scan, whereas before these variables were being
> read on each loop iteration.
>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Suggested-by: David Hildenbrand <david@kernel.org>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Acked-by: Usama Arif <usama.arif@linux.dev>
> Signed-off-by: Nico Pache <npache@redhat.com>
Had a read through, this is nice, all LGTM, so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> mm/khugepaged.c | 120 +++++++++++++++++++++++++++++++++---------------
> 1 file changed, 84 insertions(+), 36 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 13d82993755f..116f39518948 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -348,6 +348,64 @@ static bool pte_none_or_zero(pte_t pte)
> return pte_present(pte) && is_zero_pfn(pte_pfn(pte));
> }
>
> +/**
> + * 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
> + *
> + * 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)
> +{
> + 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;
> +}
> +
> +/**
> + * collapse_max_ptes_shared - Calculate maximum allowed PTEs that map shared
> + * anonymous pages for the given collapse operation.
> + * @cc: The collapse control struct
> + *
> + * Return: Maximum number of PTEs that map shared anonymous pages for the
> + * collapse operation
> + */
> +static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
> +{
> + /*
> + * For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
> + * anonymous pages.
> + */
> + if (!cc->is_khugepaged)
> + return HPAGE_PMD_NR;
> + return khugepaged_max_ptes_shared;
> +}
> +
> +/**
> + * collapse_max_ptes_swap - Calculate the maximum allowed non-present PTEs or the
> + * maximum allowed non-present pagecache entries for the given collapse operation.
> + * @cc: The collapse control struct
> + *
> + * Return: Maximum number of non-present PTEs or the maximum allowed non-present
> + * pagecache entries for the collapse operation.
> + */
> +static unsigned int collapse_max_ptes_swap(struct collapse_control *cc)
> +{
> + /*
> + * 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;
> + return khugepaged_max_ptes_swap;
> +}
> +
> 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);
> struct page *page = NULL;
> struct folio *folio = NULL;
> unsigned long addr = start_addr;
> @@ -551,16 +611,12 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
> _pte++, addr += PAGE_SIZE) {
> pte_t pteval = ptep_get(_pte);
> if (pte_none_or_zero(pteval)) {
> - ++none_or_zero;
> - if (!userfaultfd_armed(vma) &&
> - (!cc->is_khugepaged ||
> - none_or_zero <= khugepaged_max_ptes_none)) {
> - continue;
> - } else {
> + if (++none_or_zero > max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> goto out;
> }
> + continue;
> }
> if (!pte_present(pteval)) {
> result = SCAN_PTE_NON_PRESENT;
> @@ -591,9 +647,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
>
> /* See collapse_scan_pmd(). */
> if (folio_maybe_mapped_shared(folio)) {
> - ++shared;
> - if (cc->is_khugepaged &&
> - shared > khugepaged_max_ptes_shared) {
> + if (++shared > max_ptes_shared) {
> result = SCAN_EXCEED_SHARED_PTE;
> count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
> goto out;
> @@ -1262,6 +1316,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> struct vm_area_struct *vma, unsigned long start_addr,
> bool *lock_dropped, struct collapse_control *cc)
> {
> + const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma);
> + const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc);
> + const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
> pmd_t *pmd;
> pte_t *pte, *_pte;
> int none_or_zero = 0, shared = 0, referenced = 0;
> @@ -1295,36 +1352,29 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
>
> pte_t pteval = ptep_get(_pte);
> if (pte_none_or_zero(pteval)) {
> - ++none_or_zero;
> - if (!userfaultfd_armed(vma) &&
> - (!cc->is_khugepaged ||
> - none_or_zero <= khugepaged_max_ptes_none)) {
> - continue;
> - } else {
> + if (++none_or_zero > max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> goto out_unmap;
> }
> + continue;
> }
> if (!pte_present(pteval)) {
> - ++unmapped;
> - if (!cc->is_khugepaged ||
> - unmapped <= khugepaged_max_ptes_swap) {
> - /*
> - * Always be strict with uffd-wp
> - * enabled swap entries. Please see
> - * comment below for pte_uffd_wp().
> - */
> - if (pte_swp_uffd_wp_any(pteval)) {
> - result = SCAN_PTE_UFFD_WP;
> - goto out_unmap;
> - }
> - continue;
> - } else {
> + if (++unmapped > max_ptes_swap) {
> result = SCAN_EXCEED_SWAP_PTE;
> count_vm_event(THP_SCAN_EXCEED_SWAP_PTE);
> goto out_unmap;
> }
> + /*
> + * Always be strict with uffd-wp
> + * enabled swap entries. Please see
> + * comment below for pte_uffd_wp().
> + */
> + if (pte_swp_uffd_wp_any(pteval)) {
> + result = SCAN_PTE_UFFD_WP;
> + goto out_unmap;
> + }
> + continue;
> }
> if (pte_uffd_wp(pteval)) {
> /*
> @@ -1367,9 +1417,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> * is shared.
> */
> if (folio_maybe_mapped_shared(folio)) {
> - ++shared;
> - if (cc->is_khugepaged &&
> - shared > khugepaged_max_ptes_shared) {
> + if (++shared > max_ptes_shared) {
> result = SCAN_EXCEED_SHARED_PTE;
> count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
> goto out_unmap;
> @@ -2324,6 +2372,8 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
> unsigned long addr, struct file *file, pgoff_t start,
> struct collapse_control *cc)
> {
> + const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL);
> + const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
> struct folio *folio = NULL;
> struct address_space *mapping = file->f_mapping;
> XA_STATE(xas, &mapping->i_pages, start);
> @@ -2342,8 +2392,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
>
> if (xa_is_value(folio)) {
> swap += 1 << xas_get_order(&xas);
> - if (cc->is_khugepaged &&
> - swap > khugepaged_max_ptes_swap) {
> + if (swap > max_ptes_swap) {
> result = SCAN_EXCEED_SWAP_PTE;
> count_vm_event(THP_SCAN_EXCEED_SWAP_PTE);
> break;
> @@ -2414,8 +2463,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
> cc->progress += HPAGE_PMD_NR;
>
> if (result == SCAN_SUCCEED) {
> - if (cc->is_khugepaged &&
> - present < HPAGE_PMD_NR - khugepaged_max_ptes_none) {
> + if (present < HPAGE_PMD_NR - max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> } else {
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH v4 07/13] kho: add support for linked-block serialization
From: Pratyush Yadav @ 2026-06-01 13:38 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-8-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> Introduce a linked-block serialization mechanism for state handover.
>
> Previously, LUO used contiguous memory blocks for serializing sessions
> and files, which imposed limits on the total number of items that could
> be preserved across a live update.
>
> This commit adds the infrastructure for a more flexible, block-based
> approach where serialized data is stored in a chain of linked blocks.
> This is a generic KHO serialization block infrastructure that can be
> used by multiple subsystems.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
> Documentation/core-api/kho/abi.rst | 5 +
> Documentation/core-api/kho/index.rst | 11 +
> MAINTAINERS | 1 +
> include/linux/kho/abi/block.h | 56 ++++
> include/linux/kho_block.h | 79 ++++++
> kernel/liveupdate/Makefile | 1 +
> kernel/liveupdate/kho_block.c | 384 +++++++++++++++++++++++++++
> 7 files changed, 537 insertions(+)
> create mode 100644 include/linux/kho/abi/block.h
> create mode 100644 include/linux/kho_block.h
> create mode 100644 kernel/liveupdate/kho_block.c
>
> diff --git a/Documentation/core-api/kho/abi.rst b/Documentation/core-api/kho/abi.rst
> index 799d743105a6..edeb5b311963 100644
> --- a/Documentation/core-api/kho/abi.rst
> +++ b/Documentation/core-api/kho/abi.rst
> @@ -28,6 +28,11 @@ KHO persistent memory tracker ABI
> .. kernel-doc:: include/linux/kho/abi/kexec_handover.h
> :doc: KHO persistent memory tracker
>
> +KHO serialization block ABI
> +===========================
> +
> +.. kernel-doc:: include/linux/kho/abi/block.h
> +
> See Also
> ========
>
> diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
> index 0a2dee4f8e7d..320914a42178 100644
> --- a/Documentation/core-api/kho/index.rst
> +++ b/Documentation/core-api/kho/index.rst
> @@ -83,6 +83,17 @@ Public API
> .. kernel-doc:: kernel/liveupdate/kexec_handover.c
> :export:
>
> +KHO Serialization Blocks API
> +============================
> +
> +.. kernel-doc:: kernel/liveupdate/kho_block.c
> + :doc: KHO Serialization Blocks
> +
> +.. kernel-doc:: include/linux/kho_block.h
> +
> +.. kernel-doc:: kernel/liveupdate/kho_block.c
> + :internal:
> +
> See Also
> ========
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2fb1c75afd16..fd119b343e99 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14194,6 +14194,7 @@ F: Documentation/admin-guide/mm/kho.rst
> F: Documentation/core-api/kho/*
> F: include/linux/kexec_handover.h
> F: include/linux/kho/
> +F: include/linux/kho_block.h
> F: kernel/liveupdate/kexec_handover*
> F: lib/test_kho.c
> F: tools/testing/selftests/kho/
> diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
> new file mode 100644
> index 000000000000..8641c20b379b
> --- /dev/null
> +++ b/include/linux/kho/abi/block.h
> @@ -0,0 +1,56 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2026, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@soleen.com>
> + */
> +
> +/**
> + * DOC: KHO Serialization Blocks ABI
> + *
> + * Subsystems using the KHO Serialization Blocks framework rely on the stable
> + * Application Binary Interface defined below to pass serialized state from a
> + * pre-update kernel to a post-update kernel.
> + *
> + * This interface is a contract. Any modification to the structure fields,
> + * compatible strings, or the layout of the `__packed` serialization
> + * structures defined here constitutes a breaking change. Such changes require
> + * incrementing the version number in the `KHO_BLOCK_ABI_COMPATIBLE` string to
> + * prevent a new kernel from misinterpreting data from an old kernel.
> + *
> + * Changes are allowed provided the compatibility version is incremented;
> + * however, backward/forward compatibility is only guaranteed for kernels
> + * supporting the same ABI version.
> + */
> +
> +#ifndef _LINUX_KHO_ABI_BLOCK_H
> +#define _LINUX_KHO_ABI_BLOCK_H
> +
> +#include <asm/page.h>
> +#include <linux/types.h>
> +
> +#define KHO_BLOCK_ABI_COMPATIBLE "kho-block-v1"
During KHO radix development, I argued for a separate compatible for the
radix tree, but at that time, we tied the radix tree to core KHO ABI.
The argument being that all core KHO data structures belong to the KHO
ABI set. I imagine this will be used by kho_vmalloc, so it will also be
end up being used by a core KHO API.
So, do we want separate ABI? I don't much have a preference myself, but
I do think the compatible management will be a bit easier if this relied
on KHO compatible, especially once kho_vmalloc starts using it.
> +
> +/**
> + * KHO_BLOCK_SIZE - The size of each serialization block.
> + *
> + * This is defined as PAGE_SIZE. PAGE_SIZE is ABI compliant because live
> + * update between kernels with different page sizes is not supported by KHO.
> + */
> +#define KHO_BLOCK_SIZE PAGE_SIZE
> +
> +/**
> + * struct kho_block_header_ser - Header for the serialized data block.
> + * @next: Physical address of the next struct kho_block_header_ser.
> + * @count: The number of entries that immediately follow this header in the
> + * memory block.
> + *
> + * This structure is located at the beginning of a block of physical memory
> + * preserved across a kexec. It provides the necessary metadata to interpret
> + * the array of entries that follow.
> + */
> +struct kho_block_header_ser {
> + u64 next;
> + u64 count;
> +} __packed;
> +
> +#endif /* _LINUX_KHO_ABI_BLOCK_H */
> diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
> new file mode 100644
> index 000000000000..5e6b87b1befa
> --- /dev/null
> +++ b/include/linux/kho_block.h
> @@ -0,0 +1,79 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2026, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@soleen.com>
> + */
> +
> +#ifndef _LINUX_KHO_BLOCK_H
> +#define _LINUX_KHO_BLOCK_H
> +
> +#include <linux/list.h>
> +#include <linux/types.h>
> +#include <linux/kho/abi/block.h>
> +
> +/**
> + * struct kho_block - Internal representation of a serialization block.
> + * @list: List head for linking blocks in memory.
> + * @ser: Pointer to the serialized header in preserved memory.
> + */
> +struct kho_block {
> + struct list_head list;
> + struct kho_block_header_ser *ser;
> +};
> +
> +/**
> + * struct kho_block_set - A set of blocks that belong to the same object.
> + * @blocks: The list of serialization blocks (struct kho_block).
> + * @nblocks: The number of allocated serialization blocks.
> + * @head_pa: Physical address of the first block header.
> + * @entry_size: The size of each entry in the blocks.
> + * @count_per_block: The maximum number of entries each block can hold.
> + * @incoming: True if this block set was restored from the previous kernel.
> + */
> +struct kho_block_set {
> + struct list_head blocks;
> + long nblocks;
> + u64 head_pa;
> + size_t entry_size;
I think we should add the entry_size to kho_block_header_ser? I think it
is a part of the ABI of the block set. If this changes, we cannot parse
a block set with a different size. If a subsystem wants to change entry
size, they create a new block set with different entry size, and then
they bump their compatible version.
> + u64 count_per_block;
> + bool incoming;
> +};
> +
> +/**
> + * struct kho_block_it - Iterator for serializing entries into blocks.
> + * @bs: The block set being iterated.
> + * @block: The current block.
> + * @i: The current entry index within @block.
> + */
> +struct kho_block_it {
> + struct kho_block_set *bs;
> + struct kho_block *block;
> + u64 i;
> +};
> +
> +/**
> + * KHO_BLOCK_SET_INIT - Initialize a static kho_block_set.
> + * @_name: Name of the kho_block_set variable.
> + * @_entry_size: The size of each entry in the block set.
> + */
> +#define KHO_BLOCK_SET_INIT(_name, _entry_size) { \
> + .blocks = LIST_HEAD_INIT((_name).blocks), \
> + .entry_size = _entry_size, \
> +}
> +
> +void kho_block_set_init(struct kho_block_set *bs, size_t entry_size);
> +
> +int kho_block_grow(struct kho_block_set *bs, u64 count);
> +void kho_block_shrink(struct kho_block_set *bs, u64 count);
These block management functions seem like internal details of the block
set API. Do we need to export them? I think users should not have to
worry about block management. They should read, set, or clear entries
using the iterators, and internally the block management should take of
allocation or freeing. So here for example, I th
> +
> +int kho_block_restore(struct kho_block_set *bs, u64 head_pa);
> +void kho_block_destroy(struct kho_block_set *bs);
Nit: kho_block_set_{restore,destroy}()? At first glance I thought they
manipulated a single block.
> +void kho_block_set_clear(struct kho_block_set *bs);
> +
> +void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs);
> +void *kho_block_it_next(struct kho_block_it *it);
> +void *kho_block_it_read(struct kho_block_it *it);
> +void *kho_block_it_prev(struct kho_block_it *it);
> +void kho_block_it_finalize(struct kho_block_it *it);
> +
> +#endif /* _LINUX_KHO_BLOCK_H */
> diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
> index d2f779cbe279..eec9d3ae07eb 100644
> --- a/kernel/liveupdate/Makefile
> +++ b/kernel/liveupdate/Makefile
> @@ -1,6 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
>
> luo-y := \
> + kho_block.o \
> luo_core.o \
> luo_file.o \
> luo_flb.o \
> diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
> new file mode 100644
> index 000000000000..a4e650af946f
> --- /dev/null
> +++ b/kernel/liveupdate/kho_block.c
> @@ -0,0 +1,384 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/*
> + * Copyright (c) 2026, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@soleen.com>
> + */
> +
> +/**
> + * DOC: KHO Serialization Blocks
> + *
> + * KHO provides a mechanism to preserve stateful data across a kexec handover
> + * by serializing it into memory blocks. This file provides the common
> + * infrastructure for managing these blocks.
> + *
> + * Each block consists of a header (struct kho_block_header_ser) followed by an
> + * array of serialized entries. Multiple blocks are linked together via a
> + * physical pointer in the header, forming a linked list that can be easily
> + * traversed in both the current and the next kernel.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/io.h>
> +#include <linux/kexec_handover.h>
> +#include <linux/kho/abi/block.h>
> +#include <linux/kho_block.h>
> +#include <linux/slab.h>
> +
> +/*
> + * Safeguard limit for the number of serialization blocks. This is used to
> + * prevent infinite loops and excessive memory allocation in case of memory
> + * corruption in the preserved state.
> + */
> +#define KHO_MAX_BLOCKS 10000
> +
> +/**
> + * kho_block_set_init - Initialize a block set.
> + * @bs: The block set to initialize.
> + * @entry_size: The size of each entry in the blocks.
> + */
> +void kho_block_set_init(struct kho_block_set *bs, size_t entry_size)
> +{
> + *bs = (struct kho_block_set)KHO_BLOCK_SET_INIT(*bs, entry_size);
> +}
> +
> +static inline u64 kho_block_count_per_block(struct kho_block_set *bs)
> +{
> + if (unlikely(!bs->count_per_block)) {
> + bs->count_per_block = (KHO_BLOCK_SIZE -
> + sizeof(struct kho_block_header_ser)) /
> + bs->entry_size;
> + WARN_ON(!bs->count_per_block);
> + }
> + return bs->count_per_block;
> +}
This looks odd. I don't see a reason to calculate this lazily. Why not
just do it when initializing the block set, in kho_block_set_init() or
kho_block_restore()? And then use bs->count_per_block directly.
> +
> +/* Free serialized data */
> +static void kho_block_free_ser(struct kho_block_set *bs,
> + struct kho_block_header_ser *ser)
> +{
> + if (bs->incoming)
> + kho_restore_free(ser);
> + else
> + kho_unpreserve_free(ser);
> +}
> +
> +static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs)
> +{
> + WARN_ON(bs->incoming);
WARN_ON_ONCE?
> + return kho_alloc_preserve(KHO_BLOCK_SIZE);
> +}
> +
> +static int kho_block_add(struct kho_block_set *bs,
> + struct kho_block_header_ser *ser)
> +{
> + struct kho_block *block, *last;
> +
> + if (bs->nblocks >= KHO_MAX_BLOCKS)
> + return -ENOSPC;
> +
> + block = kzalloc_obj(*block);
> + if (!block)
> + return -ENOMEM;
> +
> + block->ser = ser;
> + last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
> + list_add_tail(&block->list, &bs->blocks);
> + bs->nblocks++;
> +
> + if (last)
> + last->ser->next = virt_to_phys(ser);
> + else
> + bs->head_pa = virt_to_phys(ser);
> +
> + return 0;
> +}
> +
> +/**
> + * kho_block_grow - Create a new block if the current capacity is reached.
> + * @bs: The block set.
> + * @count: The current number of entries.
> + *
> + * This function handles the dynamic expansion of a block set. It allocates
> + * and links a new serialization block if the provided entry count matches
> + * the current total capacity of the set.
> + *
> + * Return: 0 on success, or a negative errno on failure.
> + */
> +int kho_block_grow(struct kho_block_set *bs, u64 count)
> +{
> + struct kho_block_header_ser *ser;
> + int err;
> +
> + if (WARN_ON(bs->incoming))
WARN_ON_ONCE here too?
> + return -EINVAL;
> +
> + if (count != bs->nblocks * kho_block_count_per_block(bs))
> + return 0;
> +
> + ser = kho_block_alloc_ser(bs);
> + if (IS_ERR(ser))
> + return PTR_ERR(ser);
> +
> + err = kho_block_add(bs, ser);
> + if (err) {
> + kho_block_free_ser(bs, ser);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * kho_block_shrink - Conditionally destroy the last block in a block set.
> + * @bs: The block set.
> + * @count: The current number of entries across all blocks.
> + *
> + * This function checks if the last block in the set is redundant based on the
> + * total entry count and the capacity of the preceding blocks. If the entry
> + * count can be accommodated by the blocks that come before the last one, the
> + * last block is destroyed and removed from the set.
> + */
> +void kho_block_shrink(struct kho_block_set *bs, u64 count)
> +{
> + struct kho_block *last, *new_last;
> +
> + if (count > (bs->nblocks - 1) * kho_block_count_per_block(bs))
> + return;
> +
> + if (list_empty(&bs->blocks))
> + return;
> +
> + last = list_last_entry(&bs->blocks, struct kho_block, list);
> + list_del(&last->list);
> + bs->nblocks--;
> + kho_block_free_ser(bs, last->ser);
> + kfree(last);
> +
> + new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
> + if (new_last)
> + new_last->ser->next = 0;
> + else
> + bs->head_pa = 0;
> +}
> +
> +/*
> + * kho_cyclic_blocks_check - Check for cycles in a linked list of blocks.
> + * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
> + */
> +static bool kho_cyclic_blocks_check(struct kho_block_set *bs)
> +{
> + struct kho_block_header_ser *fast;
> + struct kho_block_header_ser *slow;
> + int count = 0;
> +
> + fast = phys_to_virt(bs->head_pa);
> + slow = fast;
> +
> + while (fast) {
> + if (count++ >= KHO_MAX_BLOCKS) {
> + pr_err("Linked list too long\n");
> + return false;
> + }
> +
> + if (!fast->next)
> + break;
> +
> + fast = phys_to_virt(fast->next);
> + if (!fast->next)
> + break;
> +
> + fast = phys_to_virt(fast->next);
> + slow = phys_to_virt(slow->next);
> +
> + if (slow == fast) {
> + pr_err("Cyclic list detected\n");
Heh, reminds me of the time I was practicing leetcode for interviews ;-)
> + return false;
> + }
> + }
> +
> + return true;
> +}
> +
> +/**
> + * kho_block_restore - Restore a block set from a physical address.
> + * @bs: The block set to restore.
> + * @head_pa: Physical address of the first block header.
> + *
> + * Return: 0 on success, or a negative errno on failure.
> + */
> +int kho_block_restore(struct kho_block_set *bs, u64 head_pa)
> +{
> + struct kho_block_header_ser *ser;
> + u64 next_pa = head_pa;
> + int err;
> +
> + /* Restored block sets use size from the previous kernel */
> + bs->incoming = true;
> + if (!head_pa)
> + return 0;
> +
> + bs->head_pa = head_pa;
> + if (!kho_cyclic_blocks_check(bs)) {
> + bs->head_pa = 0;
> + return -EINVAL;
> + }
> +
> + while (next_pa) {
> + ser = phys_to_virt(next_pa);
> + if (ser->count > kho_block_count_per_block(bs)) {
> + pr_warn("Block contains too many entries: %llu\n",
> + ser->count);
> + err = -EINVAL;
> + goto err_destroy;
> + }
> + err = kho_block_add(bs, ser);
> + if (err)
> + goto err_destroy;
> + next_pa = ser->next;
> + }
> +
> + return 0;
> +
> +err_destroy:
> + kho_block_destroy(bs);
> + return err;
> +}
> +
> +/**
> + * kho_block_destroy - Destroy all blocks in a block set.
> + * @bs: The block set.
> + */
> +void kho_block_destroy(struct kho_block_set *bs)
> +{
> + u64 head_pa = bs->head_pa;
> + struct kho_block *block;
> +
> + while (!list_empty(&bs->blocks)) {
> + block = list_first_entry(&bs->blocks, struct kho_block, list);
> + list_del(&block->list);
> + kfree(block);
> + }
Nit:
list_for_each_entry_safe(block, tmp, &bs->blocks, list) {
list_del(&block->list);
kfree(block);
}
is a bit more idiomatic (and IMO easier to read).
> + bs->nblocks = 0;
> + bs->head_pa = 0;
> +
> + while (head_pa) {
> + struct kho_block_header_ser *ser = phys_to_virt(head_pa);
> +
> + head_pa = ser->next;
> + kho_block_free_ser(bs, ser);
Nit: also, can't you put this also in the previous loop? Something like:
list_for_each_entry_safe(block, tmp, &bs->blocks, list) {
list_del(&block->list);
kho_block_free_ser(block->ser);
kfree(block);
}
> + }
> +}
> +
> +/**
> + * kho_block_set_clear - Clear all serialized data in a block set.
> + * @bs: The block set to clear.
> + */
> +void kho_block_set_clear(struct kho_block_set *bs)
> +{
> + struct kho_block *block;
> +
> + list_for_each_entry(block, &bs->blocks, list) {
> + block->ser->count = 0;
> + memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
> + }
> +}
> +
> +/**
> + * kho_block_it_init - Initialize a block set iterator.
> + * @it: The iterator to initialize.
> + * @bs: The block set to iterate over.
> + */
> +void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs)
> +{
> + it->bs = bs;
> + it->block = list_first_entry_or_null(&bs->blocks, struct kho_block, list);
> + it->i = 0;
> +}
> +
> +/**
> + * kho_block_it_next - Return the next entry slot in the block set.
> + * @it: The block iterator.
> + *
> + * If the current block is full, it automatically advances to the next block
> + * in the set.
> + *
> + * Return: A pointer to the next entry slot, or NULL if no more slots are
> + * available.
> + */
> +void *kho_block_it_next(struct kho_block_it *it)
The naming and documentation here are very confusing. This and
kho_block_it_read() look pretty much identical, and their documentation
also looks pretty much identical. There seems to be only one tiny
difference: this function returns the slot while incrementing the block
count.
Can we do better something like kho_block_it_write_next(struct
kho_block_it *it, void *entry) (size was specified when creating block
set)? Yes, this results in a copy but does that matter that much?
And if you really want to avoid copying, perhaps
kho_block_it_add_entry()? Or something along the lines? To make it clear
this is adding an entry to the block set.
Also, make the intended usage clear in the documentation.
> +{
> + if (!it->block)
> + return NULL;
> +
> + if (it->i == kho_block_count_per_block(it->bs)) {
> + it->block->ser->count = it->i;
> + if (list_is_last(&it->block->list, &it->bs->blocks))
> + return NULL;
> + it->block = list_next_entry(it->block, list);
> + it->i = 0;
> + }
> +
> + return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
> +}
> +
> +/**
> + * kho_block_it_read - Return the next entry slot for reading.
> + * @it: The block iterator.
> + *
> + * This function iterates through entries that were previously serialized,
> + * respecting the count stored in each block's header.
> + *
> + * Return: A pointer to the next entry slot, or NULL if no more entries are
> + * available.
> + */
> +void *kho_block_it_read(struct kho_block_it *it)
> +{
> + if (!it->block)
> + return NULL;
> +
> + while (it->i == it->block->ser->count) {
Hmm, the while loop suggests we can have blocks with zero count. Do you
think we should detect those and error out instead? Since it doesn't
really make sense to have a block with no entries.
> + if (list_is_last(&it->block->list, &it->bs->blocks))
> + return NULL;
> + it->block = list_next_entry(it->block, list);
> + it->i = 0;
> + }
> +
> + return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
> +}
> +
> +/**
> + * kho_block_it_prev - Return the previous entry slot in the block set.
> + * @it: The block iterator.
> + *
> + * If the current index is at the start of a block, it automatically moves to
> + * the end of the previous block.
> + *
> + * Return: A pointer to the previous entry slot, or NULL if at the very
> + * beginning of the block set.
> + */
> +void *kho_block_it_prev(struct kho_block_it *it)
> +{
> + if (!it->block)
> + return NULL;
> +
> + if (it->i == 0) {
> + if (list_is_first(&it->block->list, &it->bs->blocks))
> + return NULL;
> + it->block = list_prev_entry(it->block, list);
> + it->i = kho_block_count_per_block(it->bs);
> + }
> +
> + return (void *)(it->block->ser + 1) + (--it->i * it->bs->entry_size);
> +}
> +
> +/**
> + * kho_block_it_finalize - Finalize the current block by setting its entry count.
> + * @it: The block iterator.
> + */
> +void kho_block_it_finalize(struct kho_block_it *it)
> +{
> + if (it->block)
> + it->block->ser->count = it->i;
> +}
Doesn't kho_block_it_next() already do this when you add an entry? So
this seems redundant.
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v15 00/23] arm64/riscv: Add support for crashkernel CMA reservation
From: Baoquan He @ 2026-06-01 13:40 UTC (permalink / raw)
To: Jinjie Ruan
Cc: corbet, skhan, catalin.marinas, will, chenhuacai, kernel, maddy,
mpe, npiggin, chleroy, pjw, palmer, aou, alex, tglx, mingo, bp,
dave.hansen, hpa, robh, saravanak, akpm, bhe, rppt,
pasha.tatashin, pratyush, ruirui.yang, rdunlap, feng.tang,
dapeng1.mi, kees, elver, kuba, lirongqing, ebiggers, paulmck,
sourabhjain, thuth, ardb, masahiroy, gshan, james.morse, maz,
leitao, yeoreum.yun, coxu, suzuki.poulose, cfsworks, osandov,
jbohac, ryan.roberts, tangyouling, ritesh.list, adityag, hbathini,
bjorn, songshuaishuai, vishal.moola, junhui.liu,
djordje.todorovic, austin.kim, namcao, djbw, chao.gao, seanjc,
fuqiang.wang, liaoyuanhong, makb, graf, piliu, rafael.j.wysocki,
mario.limonciello, jbouron, chenjiahao16, guoren, bauerman, bgwin,
takahiro.akashi, x86, linux-doc, linux-kernel, linux-arm-kernel,
loongarch, linuxppc-dev, linux-riscv, devicetree, kexec
In-Reply-To: <20260601094805.2928614-1-ruanjinjie@huawei.com>
Hi Jinjie,
On 06/01/26 at 05:47pm, Jinjie Ruan wrote:
...snip...
> Changes in v15:
> - Unify the subject prefix formats as Huacai suggested.
> - Fix powerpc pre-existing NULL pointer dereference [Sashiko [1]]
> - Fix powerpc pre-existing __merge_memory_ranges() memory range
> truncation [Sashiko [1]].
> - Fix pre-existing arm64 CMA page leaks [Sashiko[2]].
> - Fix pre-existing crash_load_dm_crypt_keys() Use-After-Free and
> Double Free issue [Sashiko[3]].
> - Fix vfree(headers) and uninitialized variables issue
> and simplify the fix [Sashiko[2]].
> - As walk_system_ram_res() and for_each_mem_range() use different
> lock, unify and simplify the fix of TOCTOU buffer overflow via memory
> region padding [Sashiko[4]].
> - Fix the arm64 crash dump issues in Sashiko[5].
> - Link to v14: https://lore.kernel.org/all/20260525084932.934910-1-ruanjinjie@huawei.com/
Do these Fixes have anything with the main target of this patch series
you mentioned in cover-letter:"arm64/riscv: Add support for crashkernel CMA"?
The patches become more and more in each new version, I am wondering if
it relies on these Fixes patches to implement your adding support for
crashkernel CMA on arm64/risc-v.
If not relying on them, could you split them into different patchset
on different purpose?
Thanks
Baoquan
>
> [1]: https://lore.kernel.org/all/20260525092207.96B9D1F000E9@smtp.kernel.org/
> [2]: https://lore.kernel.org/all/20260525091149.1A1E01F00A3D@smtp.kernel.org/
> [3]: https://lore.kernel.org/all/20260525105227.3C2421F000E9@smtp.kernel.org/
> [4]: https://lore.kernel.org/all/20260525095447.944E11F000E9@smtp.kernel.org/
> [5]: https://lore.kernel.org/all/20260525101746.9959D1F000E9@smtp.kernel.org/
>
> Changes in v14:
> - Fix image->elf_headers memory leak during retry loop for arm64 as Sashiko
> AI code review pointed out.
> - Solve the hotplug notifier arch_crash_handle_hotplug_event() AA
> self-deadlock problem as Sashiko AI code review pointed out.
> - Fix the TOCTOU issue in prepare_elf_headers() by get_online_mems().
> - -ENOMEM -> -EAGAIN as Breno suggested.
> - Add support for arm64 crash hotplug.
> - Link to v13: https://lore.kernel.org/all/20260511030454.1730881-1-ruanjinjie@huawei.com/
>
> Changes in v13:
> - Rebased on v7.1-rc1.
> - Update the commit message.
> - Add Reviewed-by.
> - Link to v12: https://lore.kernel.org/all/20260402072701.628293-1-ruanjinjie@huawei.com/
>
> Changes in v12:
> - Remove the unused "nr_mem_ranges" for x86.
> - Add "Fix crashk_low_res not exclude bug" test log.
> - Provide a separate patch for each architecture for using
> crash_prepare_headers(), which will make the review more convenient.
> - Add Reviewed-by and Tested-by.
> - Link to v11: https://lore.kernel.org/all/20260328074013.3589544-1-ruanjinjie@huawei.com/
>
> Changes in v11:
> - Avoid silently drop crash memory if the crash kernel is built without
> CONFIG_CMA.
> - Remove unnecessary "cmem->nr_ranges = 0" for arch_crash_populate_cmem()
> as we use kvzalloc().
> - Provide a separate patch for each architecture to fix the existing
> buffer overflow issue.
> - Add Acked-bys for arm64.
>
> Changes in v10:
> - Fix crashk_low_res not excluded bug in the existing
> RISC-V code.
> - Fix an existing memory leak issue in the existing PowerPC code.
> - Fix the ordering issue of adding CMA ranges to
> "linux,usable-memory-range".
> - Fix an existing concurrency issue. A Concurrent memory hotplug may occur
> between reading memblock and attempting to fill cmem during kexec_load()
> for almost all existing architectures.
> - Link to v9: https://lore.kernel.org/all/20260323072745.2481719-1-ruanjinjie@huawei.com/
>
> Changes in v9:
> - Collect Reviewed-by and Acked-by, and prepare for Sashiko AI review.
> - Link to v8: https://lore.kernel.org/all/20260302035315.3892241-1-ruanjinjie@huawei.com/
>
> Changes in v8:
> - Fix the build issues reported by kernel test robot and Sourabh.
> - Link to v7: https://lore.kernel.org/all/20260226130437.1867658-1-ruanjinjie@huawei.com/
>
> Changes in v7:
> - Correct the inclusion of CMA-reserved ranges for kdump kernel in of/kexec
> for arm64 and riscv.
> - Add Acked-by.
> - Link to v6: https://lore.kernel.org/all/20260224085342.387996-1-ruanjinjie@huawei.com/
>
> Changes in v6:
> - Update the crash core exclude code as Mike suggested.
> - Rebased on v7.0-rc1.
> - Add acked-by.
> - Link to v5: https://lore.kernel.org/all/20260212101001.343158-1-ruanjinjie@huawei.com/
>
> Jinjie Ruan (22):
> riscv: kexec_file: Fix crashk_low_res not exclude bug
> powerpc/crash: Fix possible memory leak in update_crash_elfcorehdr()
> powerpc/kexec_file: Fix NULL pointer dereference in
> kexec_extra_fdt_size_ppc64()
> powerpc/kexec_file: Fix memory range truncation in
> __merge_memory_ranges()
> kexec: Extract kexec_free_segment_cma() from kimage_free_cma()
> arm64: kexec_file: Fix CMA page leaks during segment placement retry
> loops
> arm64: kexec_file: Fix image->elf_headers memory leak during retry
> loop
> kexec: Fix UAF and Double Free in crash_load_dm_crypt_keys()
> crash_core: Introduce CRASH_HOTPLUG_SAFETY_PADDING for memory hotplug
> safety
> x86: kexec_file: Fix TOCTOU buffer overflow via memory region padding
> arm64: kexec_file: Fix TOCTOU buffer overflow via memory region
> padding
> riscv: kexec_file: Fix TOCTOU buffer overflow via memory region
> padding
> LoongArch: kexec_file: Fix TOCTOU buffer overflow via memory region
> padding
> crash: Add crash_prepare_headers() to exclude crash kernel memory
> arm64: kexec_file: Use crash_prepare_headers() helper to simplify code
> x86: kexec_file: Use crash_prepare_headers() helper to simplify code
> riscv: kexec_file: Use crash_prepare_headers() helper to simplify code
> LoongArch: kexec_file: Use crash_prepare_headers() helper to simplify
> code
> powerpc/kexec_file: Use crash_exclude_core_ranges() helper
> arm64: kexec_file: Add support for crashkernel CMA reservation
> riscv: kexec_file: Add support for crashkernel CMA reservation
> arm64: crash: Add crash hotplug support
>
> Sourabh Jain (1):
> powerpc/crash: sort crash memory ranges before preparing elfcorehdr
>
> .../admin-guide/kernel-parameters.txt | 16 +-
> arch/arm64/Kconfig | 3 +
> arch/arm64/include/asm/kexec.h | 13 ++
> arch/arm64/kernel/Makefile | 2 +-
> arch/arm64/kernel/crash.c | 152 ++++++++++++++++++
> arch/arm64/kernel/kexec_image.c | 34 ++++
> arch/arm64/kernel/machine_kexec_file.c | 78 ++-------
> arch/arm64/mm/init.c | 5 +-
> arch/loongarch/kernel/machine_kexec_file.c | 44 ++---
> arch/powerpc/include/asm/kexec_ranges.h | 1 -
> arch/powerpc/kexec/crash.c | 7 +-
> arch/powerpc/kexec/file_load_64.c | 3 +
> arch/powerpc/kexec/ranges.c | 113 ++-----------
> arch/riscv/kernel/machine_kexec_file.c | 43 ++---
> arch/riscv/mm/init.c | 5 +-
> arch/x86/kernel/crash.c | 92 ++---------
> drivers/of/fdt.c | 9 +-
> drivers/of/kexec.c | 9 ++
> include/linux/crash_core.h | 15 ++
> include/linux/crash_reserve.h | 4 +-
> include/linux/kexec.h | 2 +
> kernel/crash_core.c | 89 +++++++++-
> kernel/crash_dump_dm_crypt.c | 4 +-
> kernel/kexec_core.c | 25 +--
> 24 files changed, 430 insertions(+), 338 deletions(-)
> create mode 100644 arch/arm64/kernel/crash.c
>
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH v4 08/13] liveupdate: defer session block allocation and PA setting
From: Pratyush Yadav @ 2026-06-01 13:47 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-9-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> Currently, luo_session_setup_outgoing() allocates the session block and
> sets its physical address in the header immediately. With upcoming
> dynamic block-based session management, this makes the first block
> different from the rest. Move the allocation to where it is first needed.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v5 12/13] ima: Return error on deleting measurements already copied during kexec
From: Mimi Zohar @ 2026-06-01 13:47 UTC (permalink / raw)
To: Roberto Sassu, corbet, skhan, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, chenste, nramas, Roberto Sassu
In-Reply-To: <8a0c965e1c2f3eee1006c4941206d70a71e7d0f0.camel@huaweicloud.com>
On Fri, 2026-05-29 at 16:59 +0200, Roberto Sassu wrote:
> On Tue, 2026-05-26 at 10:02 -0400, Mimi Zohar wrote:
> > On Wed, 2026-04-29 at 18:03 +0200, Roberto Sassu wrote:
> > > From: Roberto Sassu <roberto.sassu@huawei.com>
> > >
> > > Refuse to delete staged or active list measurements, if a kexec racing with
> > > the deletion already copied those measurements in the kexec buffer. In this
> > > way, user space becomes aware that those measurements are going to appear
> > > in the secondary kernel, and thus they don't have to be saved twice.
> >
> > There are two reboot notifiers: one to prevent additional measurements extending
> > the TPM, while the other copies the measurements for kexec. This patch prevents
> > deleting the staged measurements after the latter notifier.
> >
> > Instead of introducing a specific method for detecting whether the measurement
> > list has been copied, rely on one of the two existing reboot notifiers. The
> > simplest method would test "ima_measurements_suspended", which would prevent
> > deleting the staged measurements a bit earlier.
>
> Testing that the reboot notifier fired (with the
> ima_measurements_suspended variable) is not enough to know whether the
> measurements dump took place or not.
>
> We need a flag (one is enough) protected by ima_extend_list_mutex, so
> that we know reliably which event occurred first, or the dump or the
> staging/delete (which are also protected by ima_extend_list_mutex).
I'm suggesting not allowing the staged measurements, if there are any, to be
deleted once the reboot notifier has started. They'll be copied at the late
reboot notifier.
Mimi
^ permalink raw reply
* Re: [PATCH v4 04/13] liveupdate: register luo_ser as KHO subtree
From: Pasha Tatashin @ 2026-06-01 13:50 UTC (permalink / raw)
To: Pratyush Yadav
Cc: Pasha Tatashin, linux-kselftest, rppt, shuah, akpm, linux-mm,
skhan, linux-doc, linux-kernel, corbet, dmatlack, kexec, skhawaja,
graf
In-Reply-To: <2vxzv7c2fn8n.fsf@kernel.org>
On 06-01 14:39, Pratyush Yadav wrote:
> On Sat, May 30 2026, Pasha Tatashin wrote:
>
> > Entirely remove the LUO FDT wrapper since the FDT only carries the
> > compatible string and the pointer to the centralized struct luo_ser.
> > Instead, register the struct luo_ser via the KHO raw subtree
> > API, placing the compatibility string inside the structure itself.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> > ---
> > include/linux/kho/abi/luo.h | 57 +++++++++---------------
> > kernel/liveupdate/luo_core.c | 85 +++++++++++-------------------------
> > 2 files changed, 46 insertions(+), 96 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> > index 1b2f865a771a..9a4fe491812b 100644
> > --- a/include/linux/kho/abi/luo.h
> > +++ b/include/linux/kho/abi/luo.h
> > @@ -10,11 +10,11 @@
> > *
> > * Live Update Orchestrator uses the stable Application Binary Interface
> > * defined below to pass state from a pre-update kernel to a post-update
> > - * kernel. The ABI is built upon the Kexec HandOver framework and uses a
> > - * Flattened Device Tree to describe the preserved data.
> > + * kernel. The ABI is built upon the Kexec HandOver framework and registers
> > + * the central `struct luo_ser` via the KHO raw subtree API.
> > *
> > - * This interface is a contract. Any modification to the FDT structure, node
> > - * properties, compatible strings, or the layout of the `__packed` serialization
> > + * This interface is a contract. Any modification to the structure fields,
> > + * compatible strings, or the layout of the `__packed` serialization
> > * structures defined here constitutes a breaking change. Such changes require
> > * incrementing the version number in the relevant `_COMPATIBLE` string to
> > * prevent a new kernel from misinterpreting data from an old kernel.
> > @@ -23,31 +23,15 @@
> > * however, backward/forward compatibility is only guaranteed for kernels
> > * supporting the same ABI version.
> > *
> > - * FDT Structure Overview:
> > + * KHO Structure Overview:
> > * The entire LUO state is encapsulated within a single KHO entry named "LUO".
> > - * This entry contains an FDT with the following layout:
> > - *
> > - * .. code-block:: none
> > - *
> > - * / {
> > - * compatible = "luo-v2";
> > - * luo-abi-header = <phys_addr_of_luo_ser>;
> > - * };
> > - *
> > - * Main LUO Node (/):
> > - *
> > - * - compatible: "luo-v2"
> > - * Identifies the overall LUO ABI version.
> > - * - luo-abi-header: u64
> > - * The physical address of `struct luo_ser`.
> > + * This entry contains the `struct luo_ser` structure.
> > *
> > * Serialization Structures:
> > - * The FDT properties point to memory regions containing arrays of simple,
> > - * `__packed` structures. These structures contain the actual preserved state.
> > - *
> > * - struct luo_ser:
> > * The central ABI structure that contains the overall state of the LUO.
> > - * It includes the liveupdate-number and pointers to sessions and FLBs.
> > + * It includes the compatibility string, the liveupdate-number, and pointers
> > + * to sessions and FLBs.
> > *
> > * - struct luo_session_header_ser:
> > * Header for the session array. Contains the total page count of the
> > @@ -78,26 +62,27 @@
> > #ifndef _LINUX_KHO_ABI_LUO_H
> > #define _LINUX_KHO_ABI_LUO_H
> >
> > +#include <linux/align.h>
> > #include <uapi/linux/liveupdate.h>
> >
> > /*
> > - * The LUO FDT hooks all LUO state for sessions, fds, etc.
> > + * The LUO state is registered under this KHO entry name.
> > */
> > -#define LUO_FDT_SIZE PAGE_SIZE
> > -#define LUO_FDT_KHO_ENTRY_NAME "LUO"
> > -#define LUO_FDT_COMPATIBLE "luo-v2"
> > -#define LUO_FDT_ABI_HEADER "luo-abi-header"
> > +#define LUO_KHO_ENTRY_NAME "LUO"
> > +#define LUO_ABI_COMPATIBLE "luo-v3"
> > +#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
>
> The length of the compatible field will change depending on the length
> of the string. While that is technically fine since a new ABI version is
> allowed to change the layout, it feels odd. I think it would be better
> if we define a static size here, say 64 bytes. This way you can avoid
> all the weirdness that can happen when you move from one version to
> another.
This is what I used initially, but we have cases where one LUO/KHO
subsystem depends on another. For example, the LUO version must change
when the block version changes, making the static length too
restrictive. I would prefer to use proper strncmp() everywhere and allow
the version string to change dynamically between kernels, while still
allowing something like this (from [PATCH v4 09/13] liveupdate: Remove
limit on the number of sessions):
#define LUO_COMPAT_BASE "luo-v3"
#define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-"
KHO_BLOCK_ABI_COMPATIBLE
In the future, we may extend this further as we add more dependencies,
such as your preservable xarray, vmalloc, etc. Everything that depends
on an external version should include that in its compatibility string.
>
> >
> > /**
> > * struct luo_ser - Centralized LUO ABI header.
> > + * @compatible: Compatibility string identifying the LUO ABI version.
> > * @liveupdate_num: A counter tracking the number of successful live updates.
> > * @sessions_pa: Physical address of the first session block header.
> > * @flbs_pa: Physical address of the FLB header.
> > *
> > - * This structure is the root of all preserved LUO state. It is pointed to by
> > - * the "luo-abi-header" property in the LUO FDT.
> > + * This structure is the root of all preserved LUO state.
> > */
> > struct luo_ser {
> > + char compatible[LUO_ABI_COMPAT_LEN];
> > u64 liveupdate_num;
> > u64 sessions_pa;
> > u64 flbs_pa;
> [...]
> > @@ -94,40 +91,29 @@ static int __init luo_early_startup(void)
> > return 0;
> > }
> >
> > - /* Retrieve LUO subtree, and verify its format. */
> > - err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
> > + /* Retrieve LUO state from KHO. */
> > + err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len);
> > if (err) {
> > if (err != -ENOENT) {
> > - pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
> > - LUO_FDT_KHO_ENTRY_NAME, ERR_PTR(err));
> > + pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n",
> > + LUO_KHO_ENTRY_NAME, ERR_PTR(err));
> > return err;
> > }
> >
> > return 0;
> > }
> >
> > - luo_global.fdt_in = phys_to_virt(fdt_phys);
> > - err = fdt_node_check_compatible(luo_global.fdt_in, 0,
> > - LUO_FDT_COMPATIBLE);
> > - if (err) {
> > - pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
> > - LUO_FDT_KHO_ENTRY_NAME, LUO_FDT_COMPATIBLE, err);
> > -
> > + if (len < sizeof(*luo_ser)) {
>
> len != sizeof(*luo_ser) here?
I can change this, but it is not necessary. It is common practice to
verify that a "struct" is not smaller when compatibility is checked,
allowing for future expansion without breaking compatibility with older
kernels. I know we do not support forward/backward compatibility in any
way right now, but I do not think it hurts to put the proper safeguards
in place.
Pasha
>
> > + pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
> > return -EINVAL;
> > }
> >
> > - header_size = 0;
> > - ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
> > - if (!ptr || header_size != sizeof(u64)) {
> > - pr_err("Unable to get ABI header '%s' [%d]\n",
> > - LUO_FDT_ABI_HEADER, header_size);
> > -
> > + luo_ser = phys_to_virt(luo_ser_phys);
> > + if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
> > + pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
> > return -EINVAL;
> > }
> >
> > - luo_ser_pa = get_unaligned((u64 *)ptr);
> > - luo_ser = phys_to_virt(luo_ser_pa);
> > -
> > luo_global.liveupdate_num = luo_ser->liveupdate_num;
> > pr_info("Retrieved live update data, liveupdate number: %lld\n",
> > luo_global.liveupdate_num);
> [...]
>
> --
> Regards,
> Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v4 09/13] liveupdate: Remove limit on the number of sessions
From: Pratyush Yadav @ 2026-06-01 14:03 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-10-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> Currently, the number of LUO sessions is limited by a fixed number of
> pre-allocated pages for serialization (16 pages, allowing for ~819
> sessions).
>
> This limitation is problematic if LUO is used to support things such as
> systemd file descriptor store, and would be used not just as VM memory
> but to save other states on the machine.
>
> Remove this limit by transitioning to a linked-block approach for
> session metadata serialization. Instead of a single contiguous block,
> session metadata is now stored in a chain of 16-page blocks. Each block
> starts with a header containing the physical address of the next block
> and the number of session entries in the current block.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
[...]
> @@ -63,13 +58,15 @@
> #define _LINUX_KHO_ABI_LUO_H
>
> #include <linux/align.h>
> +#include <linux/kho/abi/block.h>
> #include <uapi/linux/liveupdate.h>
>
> /*
> * The LUO state is registered under this KHO entry name.
> */
> #define LUO_KHO_ENTRY_NAME "LUO"
> -#define LUO_ABI_COMPATIBLE "luo-v3"
> +#define LUO_COMPAT_BASE "luo-v3"
> +#define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
That's clever :-)
[...]
> int luo_session_serialize(void)
> {
> struct luo_session_header *sh = &luo_session_global.outgoing;
> struct luo_session *session;
> - int i = 0;
> + struct kho_block_it it;
> int err;
>
> down_write(&luo_session_serialize_rwsem);
> down_write(&sh->rwsem);
> *sh->sessions_pa = 0;
>
> + kho_block_it_init(&it, &sh->block_set);
> +
> list_for_each_entry(session, &sh->list, list) {
> - err = luo_session_freeze_one(session, &sh->ser[i]);
> - if (err)
> + struct luo_session_ser *ser = kho_block_it_next(&it);
> +
> + if (!ser) {
> + err = -ENOSPC;
> goto err_undo;
> + }
>
> - strscpy(sh->ser[i].name, session->name,
> - sizeof(sh->ser[i].name));
> - i++;
> - }
> + err = luo_session_freeze_one(session, ser);
> + if (err) {
> + kho_block_it_prev(&it);
> + goto err_undo;
> + }
>
> - if (sh->header_ser && sh->count > 0) {
> - sh->header_ser->count = sh->count;
> - *sh->sessions_pa = virt_to_phys(sh->header_ser);
> + strscpy(ser->name, session->name, sizeof(ser->name));
> }
> +
> + kho_block_it_finalize(&it);
> +
> + if (sh->sessions_pa && sh->count > 0)
Nit: Why check for sh->sessions_pa? It can never be NULL.
Other than this,
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
> + *sh->sessions_pa = sh->block_set.head_pa;
> up_write(&sh->rwsem);
>
> return 0;
>
> err_undo:
> list_for_each_entry_continue_reverse(session, &sh->list, list) {
> - i--;
> - luo_session_unfreeze_one(session, &sh->ser[i]);
> - memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
> + struct luo_session_ser *ser = kho_block_it_prev(&it);
> +
> + luo_session_unfreeze_one(session, ser);
> + memset(ser->name, 0, sizeof(ser->name));
> }
> up_write(&sh->rwsem);
> up_write(&luo_session_serialize_rwsem);
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH mm-unstable v18 04/14] mm/khugepaged: generalize __collapse_huge_page_* for mTHP support
From: Lorenzo Stoakes @ 2026-06-01 14:04 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, 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, 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 Fri, May 22, 2026 at 08:59:59AM -0600, 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>
One small nit below, and with the fixpatch assumed applied, LGTM so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> 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;
> +
> 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;
> }
>
> /**
> * collapse_max_ptes_shared - Calculate maximum allowed PTEs that map shared
> * anonymous pages for the given collapse operation.
> * @cc: The collapse control struct
> + * @order: The folio order being collapsed to
> *
> * Return: Maximum number of PTEs that map shared anonymous pages for the
> * collapse operation
> */
> -static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
> +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
> @@ -384,6 +406,13 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
> */
> if (!cc->is_khugepaged)
> return HPAGE_PMD_NR;
> + /*
> + * 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 */
> return khugepaged_max_ptes_shared;
> }
>
> @@ -391,11 +420,13 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
> * collapse_max_ptes_swap - Calculate the maximum allowed non-present PTEs or the
> * maximum allowed non-present pagecache entries for the given collapse operation.
> * @cc: The collapse control struct
> + * @order: The folio order being collapsed to
> *
> * Return: Maximum number of non-present PTEs or the maximum allowed non-present
> * pagecache entries for the collapse operation.
> */
> -static unsigned int collapse_max_ptes_swap(struct collapse_control *cc)
> +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
> @@ -403,6 +434,10 @@ static unsigned int collapse_max_ptes_swap(struct collapse_control *cc)
> */
> if (!cc->is_khugepaged)
> return HPAGE_PMD_NR;
> + /* 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 */
> return khugepaged_max_ptes_swap;
> }
>
> @@ -596,10 +631,11 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,
>
> 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)
> + unsigned int order, 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);
> + 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;
> unsigned long addr = start_addr;
> @@ -607,7 +643,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
> int none_or_zero = 0, shared = 0, referenced = 0;
> enum scan_result result = SCAN_FAIL;
>
> - for (_pte = pte; _pte < pte + HPAGE_PMD_NR;
> + for (_pte = pte; _pte < pte + nr_pages;
> _pte++, addr += PAGE_SIZE) {
> pte_t pteval = ptep_get(_pte);
> if (pte_none_or_zero(pteval)) {
> @@ -740,18 +776,18 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
> }
>
> static void __collapse_huge_page_copy_succeeded(pte_t *pte,
> - struct vm_area_struct *vma,
> - unsigned long address,
> - spinlock_t *ptl,
> - struct list_head *compound_pagelist)
> + struct vm_area_struct *vma, unsigned long address,
> + spinlock_t *ptl, unsigned int order,
> + struct list_head *compound_pagelist)
> {
> - unsigned long end = address + HPAGE_PMD_SIZE;
> + const unsigned long nr_pages = 1UL << order;
> + unsigned long end = address + (PAGE_SIZE << order);
Nit: could be address + PAGE_SIZE * nr_pages, be a little nicer as you already
did the << order operation above.
> struct folio *src, *tmp;
> pte_t pteval;
> pte_t *_pte;
> unsigned int nr_ptes;
>
> - for (_pte = pte; _pte < pte + HPAGE_PMD_NR; _pte += nr_ptes,
> + for (_pte = pte; _pte < pte + nr_pages; _pte += nr_ptes,
> address += nr_ptes * PAGE_SIZE) {
> nr_ptes = 1;
> pteval = ptep_get(_pte);
> @@ -804,11 +840,10 @@ static void __collapse_huge_page_copy_succeeded(pte_t *pte,
> }
>
> static void __collapse_huge_page_copy_failed(pte_t *pte,
> - pmd_t *pmd,
> - pmd_t orig_pmd,
> - struct vm_area_struct *vma,
> - struct list_head *compound_pagelist)
> + pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
> + unsigned int order, struct list_head *compound_pagelist)
> {
> + const unsigned long nr_pages = 1UL << order;
> spinlock_t *pmd_ptl;
>
> /*
> @@ -824,7 +859,7 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,
> * Release both raw and compound pages isolated
> * in __collapse_huge_page_isolate.
> */
> - release_pte_pages(pte, pte + HPAGE_PMD_NR, compound_pagelist);
> + release_pte_pages(pte, pte + nr_pages, compound_pagelist);
> }
>
> /*
> @@ -844,16 +879,17 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,
> */
> static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *folio,
> pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
> - unsigned long address, spinlock_t *ptl,
> + unsigned long address, spinlock_t *ptl, unsigned int order,
> struct list_head *compound_pagelist)
> {
> + const unsigned long nr_pages = 1UL << order;
> unsigned int i;
> enum scan_result result = SCAN_SUCCEED;
>
> /*
> * Copying pages' contents is subject to memory poison at any iteration.
> */
> - for (i = 0; i < HPAGE_PMD_NR; i++) {
> + for (i = 0; i < nr_pages; i++) {
> pte_t pteval = ptep_get(pte + i);
> struct page *page = folio_page(folio, i);
> unsigned long src_addr = address + i * PAGE_SIZE;
> @@ -872,10 +908,10 @@ static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *foli
>
> if (likely(result == SCAN_SUCCEED))
> __collapse_huge_page_copy_succeeded(pte, vma, address, ptl,
> - compound_pagelist);
> + order, compound_pagelist);
> else
> __collapse_huge_page_copy_failed(pte, pmd, orig_pmd, vma,
> - compound_pagelist);
> + order, compound_pagelist);
>
> return result;
> }
> @@ -1042,16 +1078,20 @@ static enum scan_result check_pmd_still_valid(struct mm_struct *mm,
> * 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.
> */
> static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
> - struct vm_area_struct *vma, unsigned long start_addr, pmd_t *pmd,
> - int referenced)
> + struct vm_area_struct *vma, unsigned long start_addr,
> + pmd_t *pmd, int referenced, unsigned int order)
> {
> int swapped_in = 0;
> vm_fault_t ret = 0;
> - unsigned long addr, end = start_addr + (HPAGE_PMD_NR * PAGE_SIZE);
> + unsigned long addr, end = start_addr + (PAGE_SIZE << order);
> enum scan_result result;
> pte_t *pte = NULL;
> spinlock_t *ptl;
> @@ -1083,6 +1123,19 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
> pte_present(vmf.orig_pte))
> continue;
>
> + /*
> + * TODO: Support swapin without leading to further mTHP
> + * collapses. Currently bringing in new pages via swapin may
> + * cause a future higher order collapse on a rescan of the same
> + * range.
> + */
> + if (!is_pmd_order(order)) {
> + pte_unmap(pte);
> + mmap_read_unlock(mm);
> + result = SCAN_EXCEED_SWAP_PTE;
> + goto out;
> + }
> +
> vmf.pte = pte;
> vmf.ptl = ptl;
> ret = do_swap_page(&vmf);
> @@ -1203,7 +1256,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> * that case. Continuing to collapse causes inconsistency.
> */
> result = __collapse_huge_page_swapin(mm, vma, address, pmd,
> - referenced);
> + referenced, HPAGE_PMD_ORDER);
> if (result != SCAN_SUCCEED)
> goto out_nolock;
> }
> @@ -1251,6 +1304,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
> pte = pte_offset_map_lock(mm, &_pmd, address, &pte_ptl);
> if (pte) {
> result = __collapse_huge_page_isolate(vma, address, pte, cc,
> + HPAGE_PMD_ORDER,
> &compound_pagelist);
> spin_unlock(pte_ptl);
> } else {
> @@ -1281,6 +1335,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
>
> result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
> vma, address, pte_ptl,
> + HPAGE_PMD_ORDER,
> &compound_pagelist);
> pte_unmap(pte);
> if (unlikely(result != SCAN_SUCCEED))
> @@ -1316,9 +1371,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> struct vm_area_struct *vma, unsigned long start_addr,
> bool *lock_dropped, struct collapse_control *cc)
> {
> - const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma);
> - const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc);
> - const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
> + const unsigned 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);
> pmd_t *pmd;
> pte_t *pte, *_pte;
> int none_or_zero = 0, shared = 0, referenced = 0;
> @@ -2372,8 +2427,8 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
> unsigned long addr, struct file *file, pgoff_t start,
> struct collapse_control *cc)
> {
> - const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL);
> - const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
> + 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;
> XA_STATE(xas, &mapping->i_pages, start);
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH mm-unstable v18 05/14] mm/khugepaged: require collapse_huge_page to enter/exit with the lock dropped
From: Lorenzo Stoakes @ 2026-06-01 14:07 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, 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, 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-6-npache@redhat.com>
On Fri, May 22, 2026 at 09:00:00AM -0600, Nico Pache wrote:
> Currently the collapse_huge_page function requires the mmap_read_lock to
> enter with it held, and exit with it dropped. This function moves the
> unlock into its parent caller, and changes this semantic to requiring it
> to enter/exit with it always unlocked.
>
> In future patches, we need this expectation, as for in mTHP collapse, we
> may have already have dropped the lock, and do not want to conditionally
> check for this by passing through the lock_dropped variable.
>
> No functional change is expected as one of the first things the
> collapse_huge_page function does is drop this lock before allocating the
> hugepage.
>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Nico Pache <npache@redhat.com>
One small nit below, otherwise LGTM, so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> mm/khugepaged.c | 16 ++++++++--------
> 1 file changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index e98ba5b15163..fab35d318641 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -1208,6 +1208,12 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
> return SCAN_SUCCEED;
> }
>
> +/*
> + * 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 address,
> int referenced, int unmapped, struct collapse_control *cc)
> {
> @@ -1223,14 +1229,6 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
>
> VM_BUG_ON(address & ~HPAGE_PMD_MASK);
>
> - /*
> - * Before allocating the hugepage, release the mmap_lock read lock.
> - * The allocation can take potentially a long time if it involves
> - * sync compaction, and we do not need to hold the mmap_lock during
> - * that. We will recheck the vma after taking it again in write mode.
> - */
> - mmap_read_unlock(mm);
> -
NIT: Maybe worth an mmap_assert_locked()?
> result = alloc_charge_folio(&folio, mm, cc, HPAGE_PMD_ORDER);
> if (result != SCAN_SUCCEED)
> goto out_nolock;
> @@ -1535,6 +1533,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> out_unmap:
> pte_unmap_unlock(pte, ptl);
> if (result == SCAN_SUCCEED) {
> + /* collapse_huge_page expects the lock to be dropped before calling */
> + mmap_read_unlock(mm);
> result = collapse_huge_page(mm, start_addr, referenced,
> unmapped, cc);
> /* collapse_huge_page will return with the mmap_lock released */
> --
> 2.54.0
>
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH v5 04/18] mm: skip out-of-range bits in mk_vma_flags()
From: Kiryl Shutsemau @ 2026-06-01 14:08 UTC (permalink / raw)
To: Mike Rapoport
Cc: Lorenzo Stoakes, akpm, peterx, david, surenb, vbabka,
Liam.Howlett, ziy, corbet, skhan, seanjc, pbonzini, jthoughton,
aarcange, sj, usama.arif, linux-mm, linux-kernel, linux-doc,
linux-kselftest, kvm, kernel-team, stable
In-Reply-To: <ahsVyQZ5UXhJLct2@kernel.org>
On Sat, May 30, 2026 at 07:52:25PM +0300, Mike Rapoport wrote:
> I have a PoC of yet another alternative:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/rppt/linux.git/log/?h=uffd/vm-flags
>
> The idea there is to keep a single VMA flag, VMA_UFFD_BIT/VM_UFFD and move
> all the rest into what's now struct vm_userfaultfd_ctx.
Nice!
I assume it can go on top what I did, right?
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply
* Re: [PATCH mm-unstable v18 08/14] mm/khugepaged: add per-order mTHP collapse failure statistics
From: Lorenzo Stoakes @ 2026-06-01 14:13 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, 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, 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-9-npache@redhat.com>
On Fri, May 22, 2026 at 09:00:03AM -0600, Nico Pache wrote:
> Add three new mTHP statistics to track collapse failures for different
> orders when encountering swap PTEs, excessive none PTEs, and shared PTEs:
>
> - collapse_exceed_swap_pte: Increment when mTHP collapse fails due to
> encountering a swap PTE.
>
> - collapse_exceed_none_pte: Counts when mTHP collapse fails due to
> exceeding the none PTE threshold for the given order
>
> - collapse_exceed_shared_pte: Counts when mTHP collapse fails due to
> encountering a shared PTE.
>
> These statistics complement the existing THP_SCAN_EXCEED_* events by
> providing per-order granularity for mTHP collapse attempts. The stats are
> exposed via sysfs under
> `/sys/kernel/mm/transparent_hugepage/hugepages-*/stats/` for each
> supported hugepage size.
>
> As we currently do not support collapsing mTHPs that contain a swap or
> shared entry, those statistics keep track of how often we are
> encountering failed mTHP collapses due to these restrictions.
>
> We will add support for mTHP collapse for anonymous pages next; lets also
> track when this happens at the PMD level within the per-mTHP stats.
>
> Signed-off-by: Nico Pache <npache@redhat.com>
Logic LGTM, small comment below, but otherwise:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> Documentation/admin-guide/mm/transhuge.rst | 14 ++++++++++++++
> include/linux/huge_mm.h | 3 +++
> mm/huge_memory.c | 7 +++++++
> mm/khugepaged.c | 21 +++++++++++++++++++--
> 4 files changed, 43 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
> index c51932e6275d..80a4d0bed70b 100644
> --- a/Documentation/admin-guide/mm/transhuge.rst
> +++ b/Documentation/admin-guide/mm/transhuge.rst
> @@ -714,6 +714,20 @@ nr_anon_partially_mapped
> an anonymous THP as "partially mapped" and count it here, even though it
> is not actually partially mapped anymore.
>
> +collapse_exceed_none_pte
> + The number of collapse attempts that failed due to exceeding the
> + max_ptes_none threshold.
> +
> +collapse_exceed_swap_pte
> + The number of collapse attempts that failed due to exceeding the
> + max_ptes_swap threshold. For non-PMD orders this occurs if a mTHP range
> + contains at least one swap PTE.
> +
> +collapse_exceed_shared_pte
> + The number of collapse attempts that failed due to exceeding the
> + max_ptes_shared threshold. For non-PMD orders this occurs if a mTHP range
> + contains at least one shared PTE.
> +
> As the system ages, allocating huge pages may be expensive as the
> system uses memory compaction to copy data around memory to free a
> huge page for use. There are some counters in ``/proc/vmstat`` to help
> diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
> index ba7ae6808544..48496f09909b 100644
> --- a/include/linux/huge_mm.h
> +++ b/include/linux/huge_mm.h
> @@ -144,6 +144,9 @@ enum mthp_stat_item {
> MTHP_STAT_SPLIT_DEFERRED,
> MTHP_STAT_NR_ANON,
> MTHP_STAT_NR_ANON_PARTIALLY_MAPPED,
> + MTHP_STAT_COLLAPSE_EXCEED_SWAP,
> + MTHP_STAT_COLLAPSE_EXCEED_NONE,
> + MTHP_STAT_COLLAPSE_EXCEED_SHARED,
> __MTHP_STAT_COUNT
> };
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 345c54133c83..5c128cdec810 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -703,6 +703,10 @@ DEFINE_MTHP_STAT_ATTR(split_failed, MTHP_STAT_SPLIT_FAILED);
> DEFINE_MTHP_STAT_ATTR(split_deferred, MTHP_STAT_SPLIT_DEFERRED);
> DEFINE_MTHP_STAT_ATTR(nr_anon, MTHP_STAT_NR_ANON);
> DEFINE_MTHP_STAT_ATTR(nr_anon_partially_mapped, MTHP_STAT_NR_ANON_PARTIALLY_MAPPED);
> +DEFINE_MTHP_STAT_ATTR(collapse_exceed_swap_pte, MTHP_STAT_COLLAPSE_EXCEED_SWAP);
> +DEFINE_MTHP_STAT_ATTR(collapse_exceed_none_pte, MTHP_STAT_COLLAPSE_EXCEED_NONE);
> +DEFINE_MTHP_STAT_ATTR(collapse_exceed_shared_pte, MTHP_STAT_COLLAPSE_EXCEED_SHARED);
> +
>
> static struct attribute *anon_stats_attrs[] = {
> &anon_fault_alloc_attr.attr,
> @@ -719,6 +723,9 @@ static struct attribute *anon_stats_attrs[] = {
> &split_deferred_attr.attr,
> &nr_anon_attr.attr,
> &nr_anon_partially_mapped_attr.attr,
> + &collapse_exceed_swap_pte_attr.attr,
> + &collapse_exceed_none_pte_attr.attr,
> + &collapse_exceed_shared_pte_attr.attr,
> NULL,
> };
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 928e32a0d4d7..fff6a8fbf1d4 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -649,7 +649,9 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
> if (pte_none_or_zero(pteval)) {
> if (++none_or_zero > max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> - count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> + if (is_pmd_order(order))
> + count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> + count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE);
> goto out;
> }
> continue;
> @@ -683,9 +685,17 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
>
> /* See collapse_scan_pmd(). */
> if (folio_maybe_mapped_shared(folio)) {
> + /*
> + * TODO: Support shared pages without leading to further
> + * mTHP collapses. Currently bringing in new pages via
> + * shared may cause a future higher order collapse on a
> + * rescan of the same range.
> + */
Seems weird to add this comment in a stats update patch?
Not a massively big deal but maybe worth moving to the relevant commit.
> if (++shared > max_ptes_shared) {
> result = SCAN_EXCEED_SHARED_PTE;
> - count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
> + if (is_pmd_order(order))
> + count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
> + count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SHARED);
> goto out;
> }
> }
> @@ -1138,6 +1148,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
> * range.
> */
> if (!is_pmd_order(order)) {
> + count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SWAP);
> pte_unmap(pte);
> mmap_read_unlock(mm);
> result = SCAN_EXCEED_SWAP_PTE;
> @@ -1433,6 +1444,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> if (++none_or_zero > max_ptes_none) {
> result = SCAN_EXCEED_NONE_PTE;
> count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
> + count_mthp_stat(HPAGE_PMD_ORDER,
> + MTHP_STAT_COLLAPSE_EXCEED_NONE);
> goto out_unmap;
> }
> continue;
> @@ -1441,6 +1454,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> if (++unmapped > max_ptes_swap) {
> result = SCAN_EXCEED_SWAP_PTE;
> count_vm_event(THP_SCAN_EXCEED_SWAP_PTE);
> + count_mthp_stat(HPAGE_PMD_ORDER,
> + MTHP_STAT_COLLAPSE_EXCEED_SWAP);
> goto out_unmap;
> }
> /*
> @@ -1498,6 +1513,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> if (++shared > max_ptes_shared) {
> result = SCAN_EXCEED_SHARED_PTE;
> count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
> + count_mthp_stat(HPAGE_PMD_ORDER,
> + MTHP_STAT_COLLAPSE_EXCEED_SHARED);
> goto out_unmap;
> }
> }
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH v4 10/13] liveupdate: Remove limit on the number of files per session
From: Pratyush Yadav @ 2026-06-01 14:16 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-11-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> To remove the fixed limit on the number of preserved files per session,
> transition the file metadata serialization from a single contiguous
> memory block to a chain of linked blocks.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
> include/linux/kho/abi/luo.h | 13 +--
> kernel/liveupdate/luo_file.c | 144 +++++++++++++++----------------
> kernel/liveupdate/luo_internal.h | 6 +-
> 3 files changed, 80 insertions(+), 83 deletions(-)
>
> diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> index 79758d92ed5f..16df550ef143 100644
> --- a/include/linux/kho/abi/luo.h
> +++ b/include/linux/kho/abi/luo.h
> @@ -35,8 +35,8 @@
> *
> * - struct luo_session_ser:
> * Metadata for a single session, including its name and a physical pointer
> - * to another preserved memory block containing an array of
> - * `struct luo_file_ser` for all files in that session.
> + * to the first `struct kho_block_header_ser` for all files in that session.
> + * Multiple blocks are linked via the `next` field in the header.
> *
> * - struct luo_file_ser:
> * Metadata for a single preserved file. Contains the `compatible` string to
> @@ -65,7 +65,7 @@
> * The LUO state is registered under this KHO entry name.
> */
> #define LUO_KHO_ENTRY_NAME "LUO"
> -#define LUO_COMPAT_BASE "luo-v3"
> +#define LUO_COMPAT_BASE "luo-v4"
> #define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
> #define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
>
> @@ -103,9 +103,10 @@ struct luo_file_ser {
>
> /**
> * struct luo_file_set_ser - Represents the serialized metadata for file set
> - * @files: The physical address of a contiguous memory block that holds
> - * the serialized state of files (array of luo_file_ser) in this file
> - * set.
> + * @files: The physical address of the first `struct kho_block_header_ser`.
> + * This structure is the header for a block of memory containing
> + * an array of `struct luo_file_ser` entries. Multiple blocks are
> + * linked via the `next` field in the header.
> * @count: The total number of files that were part of this session during
> * serialization. Used for iteration and validation during
> * restoration.
> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> index 9eec07a9e9fc..a445b1950ca7 100644
> --- a/kernel/liveupdate/luo_file.c
> +++ b/kernel/liveupdate/luo_file.c
> @@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
> /* Keep track of files being preserved by LUO */
> static DEFINE_XARRAY(luo_preserved_files);
>
> -/* 2 4K pages, give space for 128 files per file_set */
> -#define LUO_FILE_PGCNT 2ul
> -#define LUO_FILE_MAX \
> - ((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
> -
> /**
> * struct luo_file - Represents a single preserved file instance.
> * @fh: Pointer to the &struct liveupdate_file_handler that manages
> @@ -174,39 +169,6 @@ struct luo_file {
> u64 token;
> };
>
> -static int luo_alloc_files_mem(struct luo_file_set *file_set)
> -{
> - size_t size;
> - void *mem;
> -
> - if (file_set->files)
> - return 0;
> -
> - WARN_ON_ONCE(file_set->count);
> -
> - size = LUO_FILE_PGCNT << PAGE_SHIFT;
> - mem = kho_alloc_preserve(size);
> - if (IS_ERR(mem))
> - return PTR_ERR(mem);
> -
> - file_set->files = mem;
> -
> - return 0;
> -}
> -
> -static void luo_free_files_mem(struct luo_file_set *file_set)
> -{
> - /* If file_set has files, no need to free preservation memory */
> - if (file_set->count)
> - return;
> -
> - if (!file_set->files)
> - return;
> -
> - kho_unpreserve_free(file_set->files);
> - file_set->files = NULL;
> -}
> -
> static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
> struct file *file)
> {
> @@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> if (luo_token_is_used(file_set, token))
> return -EEXIST;
>
> - if (file_set->count == LUO_FILE_MAX)
> - return -ENOSPC;
> + err = kho_block_grow(&file_set->block_set, file_set->count);
> + if (err)
> + return err;
>
> file = fget(fd);
> - if (!file)
> - return -EBADF;
> -
> - err = luo_alloc_files_mem(file_set);
> - if (err)
> - goto err_fput;
> + if (!file) {
> + err = -EBADF;
> + goto err_shrink;
> + }
>
> err = -ENOENT;
> down_read(&luo_register_rwlock);
> @@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
>
> /* err is still -ENOENT if no handler was found */
> if (err)
> - goto err_free_files_mem;
> + goto err_fput;
>
> err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
> file, GFP_KERNEL);
> @@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> xa_erase(&luo_preserved_files, luo_get_id(fh, file));
> err_module_put:
> module_put(fh->ops->owner);
> -err_free_files_mem:
> - luo_free_files_mem(file_set);
> err_fput:
> fput(file);
> +err_shrink:
> + kho_block_shrink(&file_set->block_set, file_set->count);
>
> return err;
> }
> @@ -392,13 +353,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
>
> list_del(&luo_file->list);
> file_set->count--;
> + kho_block_shrink(&file_set->block_set, file_set->count);
>
> fput(luo_file->file);
> mutex_destroy(&luo_file->mutex);
> kfree(luo_file);
> }
>
> - luo_free_files_mem(file_set);
> + kho_block_destroy(&file_set->block_set);
> }
>
> static int luo_file_freeze_one(struct luo_file_set *file_set,
> @@ -454,7 +416,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> luo_file_unfreeze_one(file_set, luo_file);
> }
>
> - memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
> + kho_block_set_clear(&file_set->block_set);
> }
>
> /**
> @@ -493,19 +455,23 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> int luo_file_freeze(struct luo_file_set *file_set,
> struct luo_file_set_ser *file_set_ser)
> {
> - struct luo_file_ser *file_ser = file_set->files;
> struct luo_file *luo_file;
> + struct kho_block_it it;
> int err;
> - int i;
>
> if (!file_set->count)
> return 0;
>
> - if (WARN_ON(!file_ser))
> - return -EINVAL;
> + kho_block_it_init(&it, &file_set->block_set);
>
> - i = 0;
> list_for_each_entry(luo_file, &file_set->files_list, list) {
> + struct luo_file_ser *file_ser = kho_block_it_next(&it);
> +
> + if (!file_ser) {
> + err = -ENOSPC;
> + goto err_unfreeze;
> + }
This should not fail normally, right? Since we pre-allocate the memory.
Perhaps add a comment saying that?
> +
> err = luo_file_freeze_one(file_set, luo_file);
> if (err < 0) {
> pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> @@ -514,16 +480,21 @@ int luo_file_freeze(struct luo_file_set *file_set,
> goto err_unfreeze;
> }
>
> - strscpy(file_ser[i].compatible, luo_file->fh->compatible,
> - sizeof(file_ser[i].compatible));
> - file_ser[i].data = luo_file->serialized_data;
> - file_ser[i].token = luo_file->token;
> - i++;
> + strscpy(file_ser->compatible, luo_file->fh->compatible,
> + sizeof(file_ser->compatible));
> + file_ser->data = luo_file->serialized_data;
> + file_ser->token = luo_file->token;
> }
> + kho_block_it_finalize(&it);
>
> file_set_ser->count = file_set->count;
> - if (file_set->files)
> - file_set_ser->files = virt_to_phys(file_set->files);
> + if (!list_empty(&file_set->block_set.blocks)) {
> + struct kho_block *block;
> +
> + block = list_first_entry(&file_set->block_set.blocks,
> + struct kho_block, list);
> + file_set_ser->files = virt_to_phys(block->ser);
> + }
Please, add an API in KHO block to return the header physical address.
Poking into the internals of the data structure like this is not a good
idea.
I missed that patch 9 also does this. So please use that there too.
>
> return 0;
>
> @@ -741,14 +712,12 @@ int luo_file_finish(struct luo_file_set *file_set)
> module_put(luo_file->fh->ops->owner);
> list_del(&luo_file->list);
> file_set->count--;
> + kho_block_shrink(&file_set->block_set, file_set->count);
> mutex_destroy(&luo_file->mutex);
> kfree(luo_file);
> }
>
> - if (file_set->files) {
> - kho_restore_free(file_set->files);
> - file_set->files = NULL;
> - }
> + kho_block_destroy(&file_set->block_set);
>
> return 0;
> }
> @@ -822,16 +791,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> struct luo_file_set_ser *file_set_ser)
> {
> struct luo_file_ser *file_ser;
> + struct kho_block_it it;
> int err;
> - u64 i;
>
> if (!file_set_ser->files) {
> WARN_ON(file_set_ser->count);
> return 0;
> }
>
> - file_set->count = file_set_ser->count;
> - file_set->files = phys_to_virt(file_set_ser->files);
> + file_set->count = 0;
> + err = kho_block_restore(&file_set->block_set, file_set_ser->files);
> + if (err)
> + return err;
>
> /*
> * Note on error handling:
> @@ -848,25 +819,50 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> * userspace to detect the failure and trigger a reboot, which will
> * reliably reset devices and reclaim memory.
> */
> - file_ser = file_set->files;
> - for (i = 0; i < file_set->count; i++) {
> - err = luo_file_deserialize_one(file_set, &file_ser[i]);
> + kho_block_it_init(&it, &file_set->block_set);
> + while ((file_ser = kho_block_it_read(&it))) {
> + err = luo_file_deserialize_one(file_set, file_ser);
> if (err)
> - return err;
> + goto err_destroy_blocks;
> + file_set->count++;
> + }
> +
> + if (file_set->count != file_set_ser->count) {
> + pr_warn("File count mismatch: expected %llu, found %llu\n",
> + file_set_ser->count, file_set->count);
> + err = -EINVAL;
> + goto err_destroy_blocks;
> }
>
> return 0;
> +
> +err_destroy_blocks:
> + while (!list_empty(&file_set->files_list)) {
> + struct luo_file *luo_file;
> +
> + luo_file = list_first_entry(&file_set->files_list,
> + struct luo_file, list);
> + list_del(&luo_file->list);
> + module_put(luo_file->fh->ops->owner);
> + mutex_destroy(&luo_file->mutex);
> + kfree(luo_file);
> + }
> + file_set->count = 0;
> + kho_block_destroy(&file_set->block_set);
> + return err;
> }
>
> void luo_file_set_init(struct luo_file_set *file_set)
> {
> INIT_LIST_HEAD(&file_set->files_list);
> + kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
> }
>
> void luo_file_set_destroy(struct luo_file_set *file_set)
> {
> WARN_ON(file_set->count);
> WARN_ON(!list_empty(&file_set->files_list));
> + WARN_ON(!list_empty(&file_set->block_set.blocks));
Here too.
> }
>
> /**
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index ee18f9a11b91..64879ffe7378 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -10,6 +10,7 @@
>
> #include <linux/liveupdate.h>
> #include <linux/uaccess.h>
> +#include <linux/kho_block.h>
>
> struct luo_ucmd {
> void __user *ubuffer;
> @@ -44,14 +45,13 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
> * struct luo_file_set - A set of files that belong to the same sessions.
> * @files_list: An ordered list of files associated with this session, it is
> * ordered by preservation time.
> - * @files: The physically contiguous memory block that holds the serialized
> - * state of files.
> + * @block_set: The set of serialization blocks.
> * @count: A counter tracking the number of files currently stored in the
> * @files_list for this session.
> */
> struct luo_file_set {
> struct list_head files_list;
> - struct luo_file_ser *files;
> + struct kho_block_set block_set;
> u64 count;
> };
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v3] docs/zh_CN: usb: refine translated wording and formatting
From: Kefan Bai @ 2026-06-01 6:19 UTC (permalink / raw)
To: Alex Shi
Cc: linux-usb, si.yanteng, gregkh, dzm91, corbet, skhan, linux-doc,
linux-kernel, doubled, alexs
In-Reply-To: <bf39b996-246a-484f-b4a3-c77f77cca0a3@gmail.com>
On Mon, 1 Jun 2026 12:00:03 +0800
Alex Shi <seakeel@gmail.com> wrote:
> Hi Kefan,
>
> It looks fine.
>
> BTW, like any other kernel patches, document patch also requires a
> line length limit of either 80 or 100 English characters per line
> (which corresponds to 40 or 50 Chinese characters). Within the same
> document, you should keep it consistent by using either the
> 80-character or the 100-character limit throughout.
>
> You should try your best to align your patch accordingly, as this
> makes it look much cleaner and more readable. To make this easier,
> you can use a text editor with a column indicator (ruler) to help you
> align the text, or you can use the command :set cc=80 in Vim to check
> your formatting.
>
> Thanks
> Alex
Hi Alex,
Thanks for taking the time to review this patch.
I'll rewrap each translated USB document to maintain a consistent
80-column limit and check for any remaining formatting issues before
sending v4.
Thanks again for the review and the formatting reminder.
Thanks,
Kefan
>
> On 2026/6/1 11:39, Kefan Bai wrote:
> > Refine the zh_CN USB translations for clarity and consistency.
> >
> > Improve wording, wrapping, and formatting across the translated
> > USB documents.
> >
> > Link:https://lore.kernel.org/r/2026053149-flaky-shallow-2460@gregkh
> > Suggested-by: Alex Shi<seakeel@gmail.com>
> > Signed-off-by: Kefan Bai<baikefan@leap-io-kernel.com>
> > ---
> > v3:
> > - refine the subject and commit message
> > - add a Link trailer to the cleanup request thread
> > - move revision notes below the cut line
> >
> > v2:
> > - replace the obsolete FSF mailing address reference in acm.rst
> > - trim the commit message to satisfy checkpatch
> >
> > Documentation/translations/zh_CN/usb/CREDITS | 146 ++++---
> > Documentation/translations/zh_CN/usb/acm.rst | 62 ++-
> > .../translations/zh_CN/usb/authorization.rst | 79 ++--
> > .../translations/zh_CN/usb/chipidea.rst | 58 ++-
> > Documentation/translations/zh_CN/usb/dwc3.rst | 37 +-
> > Documentation/translations/zh_CN/usb/ehci.rst | 247 +++++-------
> > .../translations/zh_CN/usb/index.rst | 18 +-
> > .../translations/zh_CN/usb/usbmon.rst | 363
> > ++++++++---------- 8 files changed, 442 insertions(+), 568
> > deletions(-)
> >
> > diff --git a/Documentation/translations/zh_CN/usb/CREDITS
> > b/Documentation/translations/zh_CN/usb/CREDITS index
> > c133b1a5daff..a37958d139cc 100644 ---
> > a/Documentation/translations/zh_CN/usb/CREDITS +++
> > b/Documentation/translations/zh_CN/usb/CREDITS @@ -10,12 +10,10 @@
> > :校译:
> >
> >
> > -简易 Linux USB 驱动的致谢名单:
> > +Simple Linux USB 驱动项目致谢名单:
> >
> > -以下人员都为 Linux USB 驱动代码作出了贡献
> > -(按姓氏字母顺序排列)。我相信这份名单本应
> > -更长一些,但确实不容易维护。
> > -如需将自己加入名单,请提交补丁。
> > +以下人员都为 Linux USB
> > 驱动代码作出过贡献(按姓氏字母顺序排列)。这份名
> > +单本该更长,只是确实不易维护;如果你也应列名其中,欢迎提交补丁把自己加上。
> > Georg Acher<acher@informatik.tu-muenchen.de>
> > David Brownell<dbrownell@users.sourceforge.net>
> > @@ -41,123 +39,123 @@
> > 特别感谢:
> >
> > Inaky Perez Gonzalez<inaky@peloncho.fis.ucm.es>
> > - 感谢他发起了 Linux USB 驱动开发工作,并编写了体量较大的 uusbd
> > - 驱动中的大部分代码。我们从那项工作中学到了很多。
> > + 感谢他牵头开发 Linux USB 驱动,并编写了 uusbd
> > 驱动的大部分代码,我们
> > + 从中学到了很多。
> >
> > NetBSD 和 FreeBSD 的 USB 开发者们
> > 感谢他们加入 Linux USB 邮件列表,提供建议并分享实现经验。
> >
> > -附加感谢:
> > -
> > 还要感谢以下公司与个人在硬件、支持、时间投入和开发方面提供的捐赠与帮助
> > - (摘自 Inaky 驱动原始的 THANKS 文件):
> > +另外还要感谢:
> >
> > - 以下公司曾帮助我们开发 Linux USB / UUSBD:
> > + 以下公司和个人在硬件、支持、时间和开发工作上给予了帮助(摘自
> > Inaky
> > + 驱动原始的 THANKS 文件):
> >
> > - - 3Com GmbH 捐赠了一台 ISDN Pro
> > TA,并在技术问题和测试设备方面为我
> > - 提供支持。没想到能得到这么大的帮助。
> > + 以下公司曾为 Linux USB / UUSBD 的开发提供帮助:
> >
> > - - USAR Systems 向我们提供了他们出色的 USB 评估套件,
> > - 使我们能够测试 Linux USB 驱动对最新 USB 规范的符合性。
> > - USAR Systems 认识到保持开放操作系统与时俱进的重要性,
> > - 并以硬件支持这个项目。感谢!
> > + - 3Com GmbH 捐赠了一台 ISDN Pro
> > TA,并在技术问题和测试设备方面提供
> > + 了大力支持。
> > +
> > + - USAR Systems 向我们提供了出色的 USB
> > 评估套件,使我们得以测试
> > + Linux USB 驱动对最新 USB 规范的符合性。USAR Systems
> > 也认识到,
> > +
> > 让开放操作系统跟上时代很重要,因此以硬件支持了这个项目,在此
> > + 致谢。
> >
> > - 感谢英特尔提供的宝贵帮助。
> >
> > - 我们与 Cherry 合作,使 Linux 成为首个内置 USB
> > 支持的操作系统。 Cherry 是全球最大的键盘制造商之一。
> >
> > - - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI-to-USB
> > - 控制卡,用于测试 OHCI 实现。
> > + - CMD Technology, Inc. 慷慨捐赠了一块 CSA-6700 PCI 转 USB
> > 控制卡,
> > + 用于测试 OHCI 实现。
> >
> > - - 由于他们对我们的支持,Keytronic 可以放心,
> > - 他们的键盘能卖给至少 300 万 Linux 用户中的一部分。
> > + - 有了他们的支持,Keytronic 至少可以确信,其键盘能够销售给
> > 300 万
> > + Linux 用户中的一部分。
> >
> > - - ing büro h doran [http://www.ibhdoran.com]!
> > - 在欧洲,想给主板买一个 PC 背板 USB 连接器几乎是不可能的
> > - (我自己做的那个相当糟糕
> > :))。现在我知道该去哪里买漂亮的 USB
> > - 配件了!
> > + - 特别感谢 ing büro h doran [http://www.ibhdoran.com]。
> > + 在欧洲,想给主板配一个 PC 背板 USB
> > 连接器几乎是不可能的(我自己
> > + 做的那个效果并不好)。现在我知道该去哪里购买合适的 USB
> > 配件了。
> > - Genius Germany 捐赠了一只 USB
> > 鼠标,用于测试鼠标启动协议;
> > - 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro。感谢!
> > + 他们还捐赠了 F-23 数字摇杆和 NetMouse Pro,在此致谢。
> >
> > - - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN
> > Controller B1 USB 驱动。
> > - AVM 是领先的 ISDN 控制器制造商,其主动式设计对包括 Linux
> > 在内的
> > - 所有操作系统平台开放。
> > + - AVM GmbH Berlin 支持我们开发 Linux 下的 AVM ISDN
> > Controller B1 USB
> > + 驱动。AVM 是领先的 ISDN
> > 控制器制造商,其开放式设计适用于包括
> > + Linux 在内的所有操作系统平台。
> >
> > - - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB 软驱,
> > - 使我们能够测试批量传输代码。
> > + - 非常感谢 Y-E Data, Inc 捐赠的 FlashBuster-U USB
> > 软驱,使我们能够测试
> > + 批量传输代码。
> >
> > - 感谢 Logitech 捐赠了一只三轴 USB 鼠标。
> >
> > - Logitech 负责设计、制造并销售各种人机接口设备,
> > -
> > 在键盘、鼠标、轨迹球、摄像头、扬声器,以及面向游戏和专业用途的
> > - 控制设备方面拥有悠久历史和丰富经验。
> > + Logitech
> > 负责设计、制造并销售各种人机接口设备,在键盘、鼠标、轨迹球、
> > +
> > 摄像头、扬声器,以及面向游戏和专业用途的控制设备方面拥有悠久历史和
> > + 丰富经验。
> >
> > - 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB
> > 鼠标、
> > - 摇杆和扫描仪,以表明 Linux 的重要性,也让 Logitech 的客户
> > - 能在自己喜欢的操作系统上获得支持,并让所有 Linux
> > 用户都能使用
> > - Logitech 以及其他 USB 硬件。
> > + 作为这些设备广为人知的供应商和销售商,他们捐赠了 USB
> > 鼠标、摇杆和
> > + 扫描仪,以表明 Linux 的重要性,也让 Logitech
> > 的客户能在自己偏爱的
> > + 操作系统上获得支持,并让所有 Linux 用户都能使用 Logitech
> > 及其他
> > + USB 硬件。
> >
> > Logitech 也是 1999 年 2 月 11 日维也纳 Linux
> > 大会的官方赞助商,我们将在会上展示 Linux USB 工作的最新进展。
> >
> > - - 感谢 CATC 提供 USB Inspector,帮助我们揭开 UHCI
> > 内部实现中
> > - 那些不为人知的角落。
> > + - 感谢 CATC 提供 USB Inspector,帮助我们看到 UHCI
> > 内部实现中的那些
> > + 隐秘角落。
> >
> > - 感谢 Entrega 为开发工作提供 PCI 转 USB
> > 卡、集线器和转换器产品。
> > - - 感谢 ConnectTech 提供 WhiteHEAT USB
> > 转串口转换器以及相关文档,
> > - 让这个驱动得以写成。
> > + - 感谢 ConnectTech 提供 WhiteHEAT USB
> > 转串口转换器以及相关文档,让
> > + 这个驱动得以写成。
> >
> > - - 感谢 ADMtek 提供 Pegasus 和 Pegasus II 评估板、规格说明,
> > - 以及驱动开发过程中的宝贵建议。
> > + - 感谢 ADMtek 提供 Pegasus 和 Pegasus II
> > 评估板、规格说明,以及驱动
> > + 开发过程中的宝贵建议。
> >
> > - 另外还要感谢以下个人(嘿,顺序不分先后 :))
> > + 另外还要感谢以下个人(排名不分先后):
> >
> > - - Oren Tirosh<orenti@hishome.net>,
> > - 他非常耐心地听我唠叨各种 USB 疑问,还给了很多很酷的想法。
> > + - Oren Tirosh<orenti@hishome.net>
> > + 他非常耐心地解答我反复提出的各种 USB
> > 问题,并提供了许多有价值的
> > + 想法。
> >
> > - - Jochen Karrer<karrer@wpfd25.physik.uni-wuerzburg.de>,
> > - 指出了致命 bug,并给出了宝贵建议。
> > + - Jochen Karrer<karrer@wpfd25.physik.uni-wuerzburg.de>
> > + 指出了严重问题,并给出了宝贵建议。
> >
> > - - Edmund
> > Humemberger<ed@atnet.at>,他在公共关系与项目管理方面
> > - 为 Linux-USB 项目付出了巨大的努力。
> > + - Edmund
> > Humemberger<ed@atnet.at>,他在公共关系与项目管理方面为
> > + Linux-USB 项目付出了巨大的努力。
> >
> > - - Alberto Menegazzi<flash@flash.iol.it> 正在着手编写 UUSBD
> > 文档,加油!
> > + - Alberto Menegazzi<flash@flash.iol.it> 正在着手编写 UUSBD
> > 文档。
> > - - Ric Klaren<ia_ric@cs.utwente.nl> 编写了很好的入门文档,
> > - 与 Alberto 的作品形成良性竞争:)。
> > + - Ric Klaren<ia_ric@cs.utwente.nl> 编写了很好的入门文档,与
> > + Alberto 的作品形成了良性互补。
> >
> > - - Christian
> > Groessler<cpg@aladdin.de>,感谢他在那些棘手细节上的帮助。
> > + - Christian
> > Groessler<cpg@aladdin.de>,感谢他在诸多复杂细节上的帮助。
> > - - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac 的支持,
> > - 并提供了大量的改进意见。
> > + - Paul MacKerras 改进了 OHCI 实现,推动了对 iMac
> > 的支持,并提供了
> > + 大量的改进意见。
> >
> > - - Fernando Herrera<fherrera@eurielec.etsit.upm.es>
> > - 负责撰写、维护并不断补充那份期待已久、独一无二又精彩的
> > - UUSBD FAQ!太棒了!
> > + - Fernando Herrera<fherrera@eurielec.etsit.upm.es>
> > 负责撰写、维护并
> > + 持续补充那份期待已久、内容翔实的 UUSBD FAQ。
> >
> > - - Rasca Gmelch<thron@gmx.de> 重新启用了 raw 驱动,
> > - 指出了一些错误,并启动了 uusbd-utils 软件包。
> > + - Rasca Gmelch<thron@gmx.de> 重新启用了 raw
> > 驱动,指出了一些错误,并
> > + 启动了 uusbd-utils 软件包。
> >
> > - - Peter Dettori<dettori@ozy.dec.com>,像疯了一样挖掘 bug,
> > - 还提出了很多很酷的建议,太棒了!
> > + - Peter
> > Dettori<dettori@ozy.dec.com>,持续发现问题,并提出了许多
> > + 有价值的建议。
> >
> > - - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、
> > - MIT X 联盟、TeX 社区等等,谢谢你们!
> > + - 自由软件与 Linux 社区的所有成员,包括 FSF、GNU 项目、MIT
> > X 联盟、
> > + TeX 社区等,在此一并致谢。
> >
> > - - 特别感谢 Richard Stallman 创造了 Emacs!
> > + - 特别感谢 Richard Stallman 创造了 Emacs。
> >
> > - - 感谢 linux-usb
> > 邮件列表的所有成员,读了那么多邮件——不开玩笑了,
> > - 感谢你们提出的所有建议!
> > + - 感谢 linux-usb
> > 邮件列表的所有成员阅读了大量邮件,并提出了诸多
> > + 建议。
> >
> > - 感谢 USB Implementers Forum 成员们的帮助与支持。
> >
> > - - Nathan Myers<ncm@cantrip.org>,感谢他的建议!
> > - (希望你喜欢 Cibeles 的派对。)
> > + - Nathan
> > Myers<ncm@cantrip.org>,感谢他的建议。(也希望你喜欢
> > + Cibeles 的派对。)
> >
> > - - 感谢 Linus Torvalds 创建、开发并管理 Linux。
> > + - 感谢 Linus Torvalds 创立、开发并维护 Linux。
> >
> > - Mike Smith、Craig Keithley、Thierry Giron 和 Janet
> > Schank
> > - 感谢他们让我认识到标准 USB 集线器其实也没那么“标准”,
> > - 这有助于我们在标准集线器驱动中加入厂商特定的特殊处理。
> > + 感谢他们让我认识到,标准 USB
> > 集线器其实一点也不“标准”;也正因
> > + 如此,我们才能在标准集线器驱动中加入厂商特定的处理。
> > diff --git a/Documentation/translations/zh_CN/usb/acm.rst
> > b/Documentation/translations/zh_CN/usb/acm.rst index
> > 51d6eb8f5660..b2e35787af45 100644 ---
> > a/Documentation/translations/zh_CN/usb/acm.rst +++
> > b/Documentation/translations/zh_CN/usb/acm.rst @@ -20,33 +20,26 @@
> > Linux ACM 驱动 v0.16
> > 0. 免责声明
> > ~~~~~~~~~~~
> > -本程序是自由软件;你可以在自由软件基金会发布的
> > -GNU 通用公共许可证第 2 版,或者(按你的选择)
> > -任何后续版本的条款下重新发布和/或修改它。
> > +本程序是自由软件;你可以在自由软件基金会发布的 GNU
> > 通用公共许可证第 2 版,
> > +或者(按你的选择)任何后续版本的条款下重新发布和/或修改它。
> > -发布本程序是希望它能发挥作用,但它不附带任何担保;
> > -甚至不包括对适销性或特定用途适用性的默示担保。
> > -详情见 GNU 通用公共许可证。
> > +发布本程序是希望它能发挥作用,但它不附带任何担保;甚至不包括对适销性或
> > +特定用途适用性的默示担保。详情见 GNU 通用公共许可证。
> >
> > -你应该已经随本程序收到了 GNU 通用公共许可证的副本;
> > -如果没有,请致信:Free Software Foundation, Inc., 59
> > -Temple Place, Suite 330, Boston, MA 02111-1307 USA。
> > +你应该已经随本程序收到了 GNU 通用公共许可证的副本;如果没有,请参见
> > +COPYING 文件。
> >
> > -如需联系作者,可发送电子邮件至vojtech@suse.cz,
> > -或邮寄至:
> > -Vojtech Pavlik, Ucitelska 1576, Prague 8,
> > -182 00, Czech Republic。
> > +如需联系作者,可发送电子邮件至vojtech@suse.cz,或邮寄至:
> > +Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00, Czech Republic。
> >
> > -为方便起见,软件包中已附带 GNU 通用公共许可证
> > -第 2 版:见 COPYING 文件。
> > +为方便起见,软件包中已附带 GNU 通用公共许可证第 2 版:见 COPYING
> > 文件。
> > 1. 使用方法
> > ~~~~~~~~~~~
> > -``drivers/usb/class/cdc-acm.c`` 驱动可用于符合 USB
> > -通信设备类抽象控制模型(USB CDC ACM)规范的
> > -USB 调制解调器和 USB ISDN 终端适配器。
> > +``drivers/usb/class/cdc-acm.c`` 驱动适用于符合 USB
> > 通信设备类抽象控制模型 +(USB CDC ACM)规范的 USB 调制解调器和 USB
> > ISDN 终端适配器。
> > -许多调制解调器支持此驱动,以下是我所知道的一些型号:
> > +已知支持该驱动的调制解调器包括:
> >
> > - 3Com OfficeConnect 56k
> > - 3Com Voice FaxModem Pro
> > @@ -56,17 +49,16 @@ USB 调制解调器和 USB ISDN 终端适配器。
> > - Compaq 56k FaxModem
> > - ELSA Microlink 56k
> >
> > -我知道有一款 ISDN 终端适配器可以与 ACM 驱动一起使用:
> > +已知有一款 ISDN 终端适配器可以配合 ACM 驱动使用:
> >
> > - 3Com USR ISDN Pro TA
> >
> > -一些手机也可以通过 USB 连接。
> > -我知道以下机型可以正常工作:
> > +一些手机也可以通过 USB 连接,已知可用的机型有:
> >
> > - SonyEricsson K800i
> >
> > -遗憾的是,许多调制解调器和大多数 ISDN TA
> > -都使用专有接口,因此无法与此驱动配合工作。
> > +遗憾的是,很多调制解调器和大多数 ISDN TA
> > 都使用专有接口,因此无法配合该 +驱动工作。
> > 购买前请先确认设备是否符合 ACM 规范。
> >
> > 要使用这些调制解调器,需要加载以下模块::
> > @@ -75,15 +67,13 @@ USB 调制解调器和 USB ISDN 终端适配器。
> > uhci-hcd.ko ohci-hcd.ko or ehci-hcd.ko
> > cdc-acm.ko
> >
> > -之后就应该可以访问这些调制解调器了。
> > -应当可以使用 ``minicom``、``ppp`` 和 ``mgetty``
> > -与它们通信。
> > +之后就应该能访问这些调制解调器,并用 ``minicom``、``ppp`` 和
> > +``mgetty`` 与它们通信。
> >
> > 2. 验证驱动是否正常工作
> > ~~~~~~~~~~~~~~~~~~~~~~~
> >
> > -第一步是检查 ``/sys/kernel/debug/usb/devices``,
> > -其内容应该类似如下::
> > +第一步是查看
> > ``/sys/kernel/debug/usb/devices``,其内容应当类似下面这样::
> > T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh=
> > 2 B: Alloc= 0/900 us ( 0%), #Int= 0, #Iso= 0
> > @@ -112,11 +102,10 @@ USB 调制解调器和 USB ISDN 终端适配器。
> > E: Ad=85(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
> > E: Ad=04(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
> >
> > -这三行的存在很关键(以及 ``Cls=`` 字段里出现的
> > -``comm`` 和 ``data`` 类);它说明这是一个 ACM
> > -设备。``Driver=acm`` 表示该设备正在使用 acm 驱动。
> > -如果只看到 ``Cls=ff(vend.)``,那就无能为力了:
> > -这说明你手上的设备使用的是厂商专有接口::
> > +关键是看这三行,再结合 ``Cls=`` 字段里出现的 ``comm`` 和 ``data``
> > 类,就 +能判断这是一台 ACM 设备。``Driver=acm`` 表示该设备正在使用
> > acm 驱动。如果 +只看到
> > ``Cls=ff(vend.)``,那就说明这台设备使用的是厂商专有接口,ACM 驱动
> > +无法处理::
> > D: Ver= 1.00 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs= 2
> > I: If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=02 Prot=01
> > Driver=acm @@ -142,6 +131,5 @@ USB 调制解调器和 USB ISDN
> > 终端适配器。 usb.c: acm driver claimed interface c7b5f3f8
> > usb.c: acm driver claimed interface c7691fa0
> >
> > -如果以上都正常,请启动 ``minicom``,
> > -把它配置为连接 ``ttyACM`` 设备,然后
> > -尝试输入 ``at``。如果返回 ``OK``,说明一切工作正常。
> > +如果这些都正常,请启动 ``minicom``,把它配置为连接到 ``ttyACM``
> > 设备,然后 +尝试输入 ``at``。如果返回 ``OK``,说明驱动工作正常。
> > diff --git a/Documentation/translations/zh_CN/usb/authorization.rst
> > b/Documentation/translations/zh_CN/usb/authorization.rst index
> > 2aa311f6b967..e2ff2282bd03 100644 ---
> > a/Documentation/translations/zh_CN/usb/authorization.rst +++
> > b/Documentation/translations/zh_CN/usb/authorization.rst @@ -10,34
> > +10,32 @@ :校译:
> >
> >
> > -=============================
> > -授权或禁止 USB 设备连接到系统
> > -=============================
> > +===========================
> > +允许或禁止 USB 设备接入系统
> > +===========================
> >
> > 版权 (C) 2007 Inaky Perez-Gonzalez
> > <inaky@linux.intel.com> 英特尔公司
> >
> > -此功能允许你控制 USB 设备是否可以在系统中使用。
> > -借助它,你可以完全通过用户空间实现对 USB 设备的锁定。
> > +有了这项功能,你就可以控制 USB 设备是否允许在系统中使用,并把 USB
> > 设备锁 +定机制完全放在用户空间实现。
> >
> > -目前,当插入一个 USB 设备时,系统会对其进行配置,
> > -其接口会立即向用户开放。
> > -有了这项改动,只有在 root 授权设备完成配置后,
> > -设备才可被使用。
> > +目前,USB
> > 设备一接入系统就会被立即配置,其接口也会立刻向用户开放。引入
> > +这项机制后,只有在 root 明确授权后,设备才会完成配置并允许使用。
> >
> > 用法
> > ====
> >
> > -授权设备接入::
> > +授权设备接入系统::
> >
> > $ echo 1 > /sys/bus/usb/devices/DEVICE/authorized
> >
> > -取消对设备的授权::
> > +取消设备授权::
> >
> > $ echo 0 > /sys/bus/usb/devices/DEVICE/authorized
> >
> > -将新连接到 ``hostX`` 的设备默认设为未授权(即锁定)::
> > +将连接到 ``hostX`` 的新设备默认设为未授权(即锁定)::
> >
> > $ echo 0 > /sys/bus/usb/devices/usbX/authorized_default
> >
> > @@ -45,15 +43,14 @@
> >
> > $ echo 1 > /sys/bus/usb/devices/usbX/authorized_default
> >
> > -默认情况下,所有 USB 设备都是授权的。
> > -向 ``authorized_default`` 属性写入 ``2`` 会使内核
> > -默认只授权连接到内部 USB 端口的设备。
> > +默认情况下,所有 USB 设备都是授权的。向 ``authorized_default``
> > 属性写入 +``2`` 会使内核默认只授权连接到内部 USB 端口的设备。
> >
> > -系统锁定示例(比较粗糙)
> > -------------------------
> > +系统锁定示例(简化版)
> > +----------------------
> >
> > -假设你想实现一个锁定功能,只允许类型为 XYZ 的设备接入
> > -(例如某台带有外露 USB 端口的自助服务终端)::
> > +假设你想做一个锁定机制,只允许 XYZ
> > 类型的设备接入(例如一台带有外露 USB +端口的自助终端)::
> >
> > 启动系统
> > rc.local ->
> > @@ -63,21 +60,18 @@
> > echo 0 > $host/authorized_default
> > done
> >
> > -给 udev 挂一个脚本,用于处理新插入的 USB 设备::
> > +为 udev 配置一个脚本,用于处理新插入的 USB 设备::
> >
> > if device_is_my_type $DEV
> > then
> > echo 1 > $device_path/authorized
> > - done
> > + fi
> >
> >
> > -``device_is_my_type()`` 才是锁定方案真正见功夫的
> > -地方。仅仅检查 class、type 和 protocol 是否匹配
> > -某个值,是你能做出的最糟糕的安全验证之一;
> > -对想绕过它的人来说,这反而是最容易利用的方案。
> > -如果你需要真正安全的办法,那就该使用加密、
> > -证书认证之类的机制。把 USB 存储设备当作
> > -“钥匙”的一个简单例子可以是::
> > +锁定方案是否可靠,关键全在 ``device_is_my_type()`` 的实现。仅仅检查
> > +class、type 和 protocol
> > 是否匹配,几乎是最差的一种安全校验方式;对想绕过
> > +它的人来说,这种做法反而最容易伪造。如果你真要做安全控制,就该使用加密、
> > +证书认证之类的机制。把 USB
> > 存储设备当作“钥匙”的一个简单示例可以写成:: function
> > device_is_my_type() {
> > @@ -87,7 +81,7 @@
> > sum=$(md5sum/mntpoint/.signature)
> > if [ $sum = $(cat /etc/lockdown/keysum) ]
> > then
> > - echo "We are good, connected"
> > + echo "验证通过,已连接"
> > umount /mntpoint
> > # 再做一些额外处理,让其他人也能使用它
> > else
> > @@ -96,17 +90,16 @@
> > }
> >
> >
> > -当然,这个例子很粗糙;真正要做的话,
> > -你会想用基于 PKI 的证书校验,这样就不必依赖
> > -共享密钥之类的东西。不过你应该已经明白意思了。
> > -任何拿到设备仿真工具包的人都能伪造描述符和设备信息。
> > -别信这个。
> > +当然,这个例子仍然比较简化。真正落地时,更合适的做法是使用基于 PKI
> > 的证
> > +书校验,这样就不必依赖共享密钥之类的机制了。不过意思已经很清楚:任何拿到
> > +设备仿真工具包的人,都能伪造描述符和设备信息,所以别把这类检查当成真正
> > +的安全保障。
> > 接口授权
> > --------
> >
> > -也有类似的方法用于允许或拒绝特定 USB 接口。
> > -这使得你可以只阻止某个 USB 设备中的部分接口。
> > +也可以用类似的方法允许或拒绝特定的 USB
> > 接口。这样一来,你只需要阻止某个 +USB 设备中的部分接口。
> >
> > 授权接口::
> >
> > @@ -126,14 +119,12 @@
> >
> > $ echo 0 >
> > /sys/bus/usb/devices/usbX/interface_authorized_default
> > -默认情况下,
> > -``interface_authorized_default`` 位为 ``1``,
> > -因此所有接口默认都处于已授权状态。
> > +默认情况下,``interface_authorized_default`` 位为
> > ``1``,因此所有接口默认 +都会处于授权状态。
> >
> > 注意:
> > - 如果把一个先前未授权的接口改为已授权,
> > - 则必须通过将 ``INTERFACE`` 写入 ``/sys/bus/usb/drivers_probe``
> > - 来手动触发驱动探测。
> > + 如果把一个先前未授权的接口改为已授权,则必须通过将 ``INTERFACE``
> > 写入
> > + ``/sys/bus/usb/drivers_probe`` 来手动触发驱动探测。
> >
> > -对于需要多个接口的驱动程序,应先授权所有必需接口,
> > -然后再触发驱动探测。这样做可以避免副作用。
> > +对于需要多个接口的驱动程序,应先授权所有必需接口,然后再触发驱动探测。
> > +这样做可以避免副作用。
> > diff --git a/Documentation/translations/zh_CN/usb/chipidea.rst
> > b/Documentation/translations/zh_CN/usb/chipidea.rst index
> > ea0dc3043189..011fb16f3350 100644 ---
> > a/Documentation/translations/zh_CN/usb/chipidea.rst +++
> > b/Documentation/translations/zh_CN/usb/chipidea.rst @@ -17,18
> > +17,17 @@ ChipIdea 高速双角色控制器驱动 1. 如何测试 OTG FSM(HNP 和
> > SRP) ---------------------------------
> >
> > -下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,
> > -说明如何通过 sysfs 输入文件演示 OTG 的 HNP 和 SRP 功能。
> > +下面以两块 Freescale i.MX6Q Sabre SD 开发板为例,演示如何通过
> > sysfs 属性 +测试 OTG 的 HNP 和 SRP 功能。
> >
> > -1.1 如何使能 OTG FSM
> > +1.1 如何启用 OTG FSM
> > --------------------
> >
> > 1.1.1 在 ``menuconfig`` 中选择
> > ``CONFIG_USB_OTG_FSM``,并重新编译内核
> > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > -重新构建内核镜像和模块。如果想查看 OTG FSM 的
> > -一些内部变量,可以挂载 ``debugfs``;其中有两个文件
> > -可以显示 OTG FSM 变量以及部分控制器寄存器的值::
> > +重新构建内核镜像和模块。如果想查看 OTG FSM 的内部变量,可以挂载
> > +``debugfs``;其中有两个文件,分别显示 OTG FSM
> > 的变量和部分控制器寄存器值::
> > cat /sys/kernel/debug/ci_hdrc.0/otg
> > cat /sys/kernel/debug/ci_hdrc.0/registers
> > @@ -44,11 +43,10 @@ ChipIdea 高速双角色控制器驱动
> > 1.2 测试步骤
> > ------------
> >
> > -1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,
> > - 并加载 gadget 类驱动(例如 ``g_mass_storage``)。
> > +1) 给两块 Freescale i.MX6Q Sabre SD 开发板上电,并加载 gadget
> > 类驱动(例如
> > + ``g_mass_storage``)。
> >
> > -2) 用 USB 线连接两块开发板:
> > - 一端是 micro A 插头,另一端是 micro B 插头。
> > +2) 用 USB 线连接两块开发板:一端是 micro A 插头,另一端是 micro B
> > 插头。
> > 插入 micro A 插头的一端是 A 设备,它应枚举另一端的 B 设备。
> >
> > @@ -66,32 +64,28 @@ ChipIdea 高速双角色控制器驱动
> >
> > echo 0 >
> > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
> > - 或者,通过引入 HNP 轮询,B 端主机可以知道
> > - A 端外设希望切换为主机角色,因此这次角色切换
> > - 也可以通过 A 端外设响应 B 端主机的轮询,
> > - 在 A 侧触发。
> > - 这可以通过在 A 设备上执行下面的命令来完成::
> > + 或者,也可以借助 HNP 轮询,让 B 端主机知道 A
> > 端外设希望切回主机角色。
> > + 因此,这次切换也可以由 A 侧触发,也就是由 A 端外设响应 B
> > 端主机的轮询
> > + 来完成。可在 A 设备上执行下面的命令::
> >
> > echo 1 >
> > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
> > A 设备应切回主机角色并枚举 B 设备。
> >
> > -5) 拔掉 B 设备(拔掉 micro B 插头),
> > - 并在 10 秒内重新插入;
> > +5) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒内重新插入。
> > A 设备应重新枚举 B 设备。
> >
> > -6) 拔掉 B 设备(拔掉 micro B 插头),
> > - 并在 10 秒后重新插入;
> > +6) 拔掉 B 设备(拔掉 micro B 插头),并在 10 秒后重新插入。
> > A 设备不应重新枚举 B 设备。
> >
> > - 如果 A 设备希望使用总线:
> > + 如果 A 设备还想继续使用总线:
> >
> > 在 A 设备上执行::
> >
> > echo 0 >
> > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_drop echo 1 >
> > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req
> > - 如果 B 设备希望使用总线:
> > + 如果 B 设备想使用总线:
> >
> > 在 B 设备上执行::
> >
> > @@ -111,40 +105,38 @@ ChipIdea 高速双角色控制器驱动
> >
> > echo 1 >
> > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req
> > - A 设备应恢复 USB 总线并枚举 B 设备。
> > + A 设备应恢复 USB 总线,并枚举 B 设备。
> >
> > 1.3 参考文档
> > ------------
> > -《On-The-Go and Embedded Host Supplement
> > -to the USB Revision 2.0 Specification
> > +《On-The-Go and Embedded Host Supplement to the USB Revision 2.0
> > Specification July 27, 2012 Revision 2.0 version 1.1a》
> >
> > 2. 如何将 USB 用作系统唤醒源
> > ----------------------------
> > -下面是在 i.MX6 平台上把 USB 用作系统唤醒源的示例。
> > +下面给出在 i.MX6 平台上将 USB 用作系统唤醒源的示例。
> >
> > -2.1 使能核心控制器的唤醒功能::
> > +2.1 启用核心控制器的唤醒功能::
> >
> > echo enabled >
> > /sys/bus/platform/devices/ci_hdrc.0/power/wakeup
> > -2.2 使能 glue 层的唤醒功能::
> > +2.2 启用 glue 层的唤醒功能::
> >
> > echo enabled >
> > /sys/bus/platform/devices/2184000.usb/power/wakeup
> > -2.3 使能 PHY 的唤醒功能(可选)::
> > +2.3 启用 PHY 的唤醒功能(可选)::
> >
> > echo enabled >
> > /sys/bus/platform/devices/20c9000.usbphy/power/wakeup
> > -2.4 使能根集线器的唤醒功能::
> > +2.4 启用根集线器的唤醒功能::
> >
> > echo enabled > /sys/bus/usb/devices/usb1/power/wakeup
> >
> > -2.5 使能相关设备的唤醒功能::
> > +2.5 启用相关设备的唤醒功能::
> >
> > echo enabled > /sys/bus/usb/devices/1-1/power/wakeup
> >
> > -如果系统只有一个 USB 端口,
> > -而你希望在该端口上启用 USB 唤醒功能,
> > -可以使用下面的脚本::
> > +如果系统只有一个 USB 端口,而你希望在该端口上启用 USB
> > 唤醒功能,可以使用 +下面的脚本::
> >
> > for i in $(find /sys -name wakeup | grep usb);do echo
> > enabled > $i;done; diff --git
> > a/Documentation/translations/zh_CN/usb/dwc3.rst
> > b/Documentation/translations/zh_CN/usb/dwc3.rst index
> > 3468ce50c5ba..9584cbbf6d03 100644 ---
> > a/Documentation/translations/zh_CN/usb/dwc3.rst +++
> > b/Documentation/translations/zh_CN/usb/dwc3.rst @@ -18,46 +18,43 @@
> > DWC3 驱动待办 ~~~~
> >
> > -阅读时如果想顺手认领点任务,可以从下面挑一项 :)
> > +如果你愿意接手其中一项任务,可以从下面选择:
> >
> > - 将中断处理程序改为每个端点各自使用线程化 IRQ
> >
> > - 事实证明,有些 DWC3 命令大约需要 ``~1 ms`` 才能完成。
> > - 当前代码会一直自旋等待命令完成,这种设计并不好。
> > + 实践表明,某些 DWC3 命令大约需要 ``~1 ms``
> > 才能完成。当前代码会一直自旋
> > + 等待命令完成,这并不是好办法。
> >
> > 实现思路:
> >
> > - - DWC 核心实现了一个按端点对中断进行解复用的 IRQ 控制器。
> > - 中断号在探测(``probe``)阶段分配,并归属于该设备。
> > - 如果硬件通过 ``MSI`` 为每个端点提供独立中断,
> > - 那么这个“虚拟”IRQ 控制器就可以被真实的端点中断取代。
> > + - DWC 核心实现了一个按端点分发中断的 IRQ 控制器。中断号在探测
> > + (``probe``)阶段分配,并归属于该设备。如果硬件通过 ``MSI``
> > 为每个
> > + 端点提供独立中断,那么这个“虚拟”IRQ
> > 控制器就可以被真实的端点中断
> > + 取代。
> >
> > - - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,
> > - 在调用 ``usb_ep_disable()`` 时释放中断资源。
> > - 最坏情况下需要 32 个中断,最少是 ``ep0/1`` 的两个中断。
> > + - 在调用 ``usb_ep_enable()`` 时请求并分配中断资源,在调用
> > + ``usb_ep_disable()`` 时释放中断资源。最坏情况下需要 32
> > 个中断,最少是
> > + ``ep0/1`` 的两个中断。
> > - ``dwc3_send_gadget_ep_cmd()`` 将在
> > ``wait_for_completion_timeout()`` 中休眠,直到命令完成。
> > - 中断处理程序分为以下几个部分:
> >
> > - 设备级主中断处理程序
> > - 遍历每个事件,并对其调用 ``generic_handle_irq()``。
> > - 从 ``generic_handle_irq()``
> > 返回后,确认事件计数器,使中断最终消失。
> > + 遍历每个事件,并调用 ``generic_handle_irq()``
> > 处理。返回后再确认
> > + 事件计数器,让中断最终消失。
> >
> > - 设备级线程化处理程序
> > 无。
> >
> > - 端点中断的主处理程序
> > - 读取事件并尽量处理它。凡是需要睡眠的操作都交给线程处理。
> > - 事件保存在每个端点的数据结构中。
> > - 还要注意,一旦把某项工作交给线程处理,
> > - 就不要再在主处理程序里处理它,
> > - 以免出现优先级反转之类的问题。
> > +
> > 读取事件并尽量处理;凡是需要睡眠的操作都交给线程处理。事件保存在
> > +
> > 每个端点的数据结构中。一旦某项工作已经交给线程处理,主处理程序里就
> > + 不要再碰它,以免出现优先级反转之类的问题。
> >
> > - 端点中断的线程化处理程序
> > 处理剩余的端点工作,这些工作可能会睡眠,例如等待命令完成。
> >
> > - 延迟:
> > + 延迟:
> >
> > - 不应增加延迟,因为中断线程具有较高优先级,
> > - 会在普通用户态任务之前运行
> > +
> > 不应增加额外延迟,因为中断线程优先级较高,会在普通用户任务之前运行(除非用户更改了调度优先级)。
> > diff --git a/Documentation/translations/zh_CN/usb/ehci.rst
> > b/Documentation/translations/zh_CN/usb/ehci.rst index
> > e05e493a30d3..c4c52303b13e 100644 ---
> > a/Documentation/translations/zh_CN/usb/ehci.rst +++
> > b/Documentation/translations/zh_CN/usb/ehci.rst @@ -14,45 +14,37 @@
> > EHCI 驱动
> > =========
> >
> > -2002年12月27日
> > +2002 年 12 月 27 日
> >
> > -EHCI 驱动用于通过支持 USB 2.0 的主机控制器
> > -硬件与高速 USB 2.0 设备通信。USB 2.0 兼容
> > -USB 1.1 标准,它定义了三种传输速率:
> > +EHCI 驱动用于借助支持 USB 2.0 的主机控制器,与高速 USB 2.0
> > 设备通信。USB +2.0 向下兼容 USB 1.1,并定义了三种传输速率:
> >
> > - “高速”(High Speed)480 Mbit/sec(60 MByte/sec)
> > - “全速”(Full Speed)12 Mbit/sec(1.5 MByte/sec)
> > - “低速”(Low Speed)1.5 Mbit/sec
> >
> > -USB 1.1 仅支持全速与低速。
> > -高速设备可以在 USB 1.1 系统上使用,
> > -但速度会降到 USB 1.1 的速率。
> > +USB 1.1 仅支持全速与低速。高速设备可以在 USB 1.1
> > 系统上使用,但速度会 +降到 USB 1.1 的速率。
> >
> > -USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
> > -插入 EHCI 控制器时,会被交由 USB 1.1 的伴随
> > -(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
> > +USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们插入 EHCI
> > 控制器时,会交给 +USB 1.1
> > 的伴随(companion)控制器处理,该控制器通常是 OHCI 或 UHCI。
> > -当 USB 1.1 设备插入 USB 2.0 集线器时,它们通过
> > -集线器中的事务转换器(Transaction Translator,TT)
> > -与 EHCI 控制器交互,该转换器将低速或全速事务转换为
> > -高速分割事务,从而避免浪费传输带宽。
> > +当 USB 1.1 设备插入 USB 2.0
> > 集线器时,它们会通过集线器里的事务转换器 +(Transaction
> > Translator,TT)与 EHCI 控制器通信。该转换器会把低速或全
> > +速事务转换为高速分割事务,从而避免浪费传输带宽。
> > -截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行
> > -(按字母顺序):Intel、NEC、Philips 和 VIA。
> > -其他供应商的 EHCI 实现正在陆续问世;
> > -预计该驱动在这些实现上也可正常运行。
> > +截至本文撰写时,该驱动已在以下 EHCI 实现上成功运行(按字母顺序):
> > +Intel、NEC、Philips 和 VIA。随着其他供应商的 EHCI
> > 实现陆续问世,预计该 +驱动在那些实现上也能正常运行。
> >
> > -自 2001 年年中起,usb-storage 设备就已可用
> > -(在 2.4 版该驱动上速度相当不错),
> > -集线器则直到 2001 年底才开始可用,而其他类型的高速设备
> > -似乎要等到更多系统内置 USB 2.0 后才会出现。
> > -这类新系统从 2002 年初开始上市,
> > -并在 2002 年下半年变得更加常见。
> > +自 2001 年年中起,usb-storage 设备就已可用(在 2.4
> > 版该驱动上速度表现相当 +不错),集线器则直到 2001
> > 年底才开始可用。其他类型的高速设备似乎要等到 +更多系统内置 USB 2.0
> > 后才会出现。这类新系统从 2002 年初开始上市,并在 +2002
> > 年下半年变得更加常见。
> > -注意,USB 2.0 支持并不只是 EHCI 本身。
> > -它还需要对 Linux-USB 核心 API 作出其他修改,
> > -包括 hub 驱动;不过这些修改并不需要真正改变
> > -暴露给 USB 设备驱动的基本 ``usbcore`` API。
> > +注意,USB 2.0 的支持并不只靠 EHCI 本身。它还需要对 Linux-USB 核心
> > API +做其他修改,包括 hub 驱动;不过这些修改并不需要真正改变向 USB
> > 设备驱动 +暴露的基本 ``usbcore`` API。
> >
> > - David Brownell
> > <dbrownell@users.sourceforge.net>
> > @@ -61,58 +53,46 @@ USB 1.1 设备也可以在 USB 2.0 系统上使用。当它们
> > 功能
> > ====
> >
> > -该驱动会定期在 x86 硬件上进行测试,
> > -也已在 PPC 硬件上使用,因此大小端问题应当已经解决。
> > -因此可以认为,它已经处理好了所有必要的 PCI 细节,
> > -所以即便在 DMA 映射有些特殊的系统上,
> > -I/O 也应能正常运行。
> > +该驱动长期在 x86 硬件上接受测试,也在 PPC
> > 平台上使用过,因此大小端问题应 +该都已解决。再加上各种必要的 PCI
> > 细节都已处理妥当,即便在 DMA 映射较特 +殊的系统上,I/O
> > 也应能正常工作。
> > 传输类型
> > --------
> >
> > -截至本文撰写时,该驱动应当已经能够很好地处理
> > -所有控制传输、批量传输和中断传输,
> > -包括通过 USB 2.0 集线器中的事务转换器
> > -与 USB 1.1 设备通信;但仍可能存在 bug。
> > +截至本文撰写时,该驱动应当已经能够稳定处理所有控制传输、批量传输和中断传
> > +输,包括经由 USB 2.0 集线器里的事务转换器访问 USB 1.1
> > 设备;不过仍可能 +存在 bug。
> >
> > -高速等时(ISO)传输支持也已可用,但截至本文撰写时,
> > -还没有 Linux 驱动使用这项支持。
> > +高速等时(ISO)传输支持也已可用,不过截至本文撰写时,还没有 Linux
> > 驱动真 +正使用它。
> >
> > -目前尚不支持通过事务转换器实现全速等时传输。
> > -需要注意,ISO 传输的 split transaction 支持
> > -与高速 ISO 传输几乎无法共用代码,
> > -因为 EHCI 用不同的数据结构表示它们。
> > -因此,目前大多数 USB 音频和视频设备
> > -还不能通过高速总线连接使用。
> > +目前尚不支持通过事务转换器实现全速等时传输。需要注意,ISO
> > 传输的分割 +事务支持与高速 ISO 传输几乎无法共用代码,因为 EHCI
> > 用不同的数据结构表示 +它们。因此,目前大多数 USB
> > 音频和视频设备还无法在高速总线上使用。
> > 驱动行为
> > --------
> >
> > -所有类型的传输都可以排队。
> > -这意味着来自一个接口驱动的控制传输
> > -(或通过 usbfs 发出的控制传输)不会干扰
> > -另一个驱动的控制传输,而且中断传输可以使用 1 帧的周期,
> > -而不必担心中断处理开销导致的数据丢失。
> > +所有类型的传输都可以排队提交。这意味着某个接口驱动发出的控制传输(或经由
> > +usbfs 提交的控制传输)不会干扰其他驱动的控制传输,而中断传输可以按
> > 1 帧 +周期运行,不必担心中断处理开销导致数据丢失。
> >
> >
> > -EHCI 根集线器代码会将 USB 1.1 设备移交给其伴随控制器。
> > -该驱动不需要了解那些驱动的任何细节;
> > -一个原本就能正常工作的 OHCI 或 UHCI 驱动,
> > -并不会因为 EHCI 驱动也存在而需要更改。
> > +EHCI 根集线器代码会将 USB 1.1
> > 设备交给其伴随控制器处理。该驱动无需了解
> > +那些驱动的任何细节;一个原本就能正常工作的 OHCI 或 UHCI
> > 驱动,也不会因为 +EHCI 驱动存在而需要修改。
> > -电源管理方面还有一些问题;
> > -当前挂起/恢复的行为还不完全正确。
> > +电源管理方面仍有一些问题;当前挂起/恢复行为还不完全正确。
> >
> > -此外,在调度周期性事务
> > -(中断和等时传输)时还采取了一些简化处理。
> > -这些简化会限制可调度的周期性事务数量,
> > -并且无法使用小于一帧的轮询间隔。
> > +此外,在调度周期性事务(中断和等时传输)时还采取了一些简化处理。这些
> > +简化会限制可调度的周期性事务数量,并且无法使用小于一帧的轮询间隔。
> >
> > 使用方式
> > ========
> >
> > -假设有一个 EHCI 控制器(位于 PCI 卡或主板上),
> > -并且已将此驱动编译为模块,可这样加载::
> > +假设系统中有一个 EHCI 控制器(位于 PCI
> > 卡或主板上),并且此驱动是以模块形 +式编译的,那么可以这样加载::
> >
> > # modprobe ehci-hcd
> >
> > @@ -120,27 +100,24 @@ EHCI 根集线器代码会将 USB 1.1
> > 设备移交给其伴随控制器。
> > # rmmod ehci-hcd
> >
> > -还应加载一个伴随控制器驱动,
> > -例如 ``ohci-hcd`` 或 ``uhci-hcd``。
> > -如果 EHCI 驱动出现任何问题,只需卸载它的模块,
> > -随后该伴随控制器驱动就会接手
> > -此前由 EHCI 驱动处理的所有设备
> > -(但速度会降低)。
> > +还应加载一个伴随控制器驱动,例如 ``ohci-hcd`` 或
> > ``uhci-hcd``。如果 EHCI
> > +驱动出了问题,只要卸载它的模块,伴随控制器驱动就会接管此前由 EHCI
> > 驱动处 +理的全部设备(只是速度会降低)。
> > 模块参数(传给 ``modprobe``)包括:
> >
> > log2_irq_thresh(默认值 0):
> > - 默认中断延迟的 log2 值,单位是微帧。默认值 0 表示 1 个微帧
> > - (125 微秒)。最大值 6 表示 2^6 = 64 个微帧。
> > + 默认中断延迟的 log2 值,单位为微帧。默认值 0 表示 1 个微帧
> > + (125 微秒),最大值 6 表示 2^6 = 64 个微帧。
> > 该值控制 EHCI 控制器发出中断的频率。
> >
> > -如果在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,
> > -则会在任一 EHCI 控制器的 ``sysfs`` 目录中看到三个文件:
> > +如果你在 2.5 内核上使用此驱动,并且启用了 USB 调试支持,那么任一
> > EHCI 控 +制器对应的 ``sysfs`` 目录下都会看到三个文件:
> >
> > ``async``
> > 转储异步调度,用于控制传输和批量传输。它会显示每个活动的
> > ``qh`` 以及待处理的 ``qtd``,通常每个 ``urb`` 对应一个 ``qtd``。
> > - (可以在 ``usb-storage`` 做磁盘 I/O
> > 时看它;顺便观察请求队列!)
> > + (可以在 ``usb-storage`` 执行磁盘 I/O
> > 时查看;也可顺便观察请求队列。)
> > ``periodic``
> > 转储周期性调度,用于中断传输和等时传输。不显示 ``qtd``。
> > @@ -151,111 +128,81 @@ EHCI 根集线器代码会将 USB 1.1
> > 设备移交给其伴随控制器。这些文件的内容有助于定位驱动问题。
> >
> >
> > -设备驱动通常不需要关心自己是否运行在 EHCI 之上,
> > -但它们可能想检查
> > -``usb_device->speed == USB_SPEED_HIGH``。
> > -高速设备能做到全速(或低速)设备做不到的事,
> > -例如高带宽的周期性传输(中断或 ISO 传输)。
> > -另外,设备描述符中的某些值
> > -(例如周期性传输的轮询间隔)
> > -在高速模式下使用不同的编码方式。
> > +设备驱动通常不需要关心自己是否运行在 EHCI 之上,但有时可能会想检查
> > +``usb_device->speed ==
> > USB_SPEED_HIGH``。高速设备能做到全速(或低速)设备
> > +做不到的事,例如高带宽的周期性传输(中断或 ISO
> > 传输)。另外,设备描述符
> > +中的某些值(例如周期性传输的轮询间隔)在高速模式下使用不同的编码方式。
> > -不过,一定要让设备驱动经过 USB 2.0 集线器的测试。 -
> > 当使用事务转换器时,这些集线器报告某些故障 -
> > (例如断开连接)的方式会不同; -已经见过一些驱动在遇到与 OHCI 或
> > UHCI -所报告的不同故障时表现不佳。
> > +不过,设备驱动一定要在 USB 2.0
> > 集线器后面测一遍。使用事务转换器时,这些
> > +集线器报告某些故障(例如断开连接)的方式会有所不同;已经见过一些驱动在
> > +遇到与 OHCI 或 UHCI 不同的故障时表现不佳。
> > 性能
> > ====
> >
> > -USB 2.0 吞吐量主要受两个因素制约:
> > -主机控制器处理请求的速度,以及设备响应这些请求的速度。
> > -480 Mbit/sec 的“原始传输率”对所有设备都成立,
> > -但总吞吐量还会受到诸如单个高速包之间的延迟、
> > -驱动是否足够聪明,以及系统整体负载等因素的影响。
> > -延迟也是性能考量因素。
> > +USB 2.0
> > 的吞吐量主要受两个因素制约:主机控制器处理请求的速度,以及设备响
> > +应这些请求的速度。480 Mbit/sec
> > 的“原始传输率”对所有设备都一样,但整体吞
> > +吐量还会受到诸如高速包之间的间隔、驱动实现是否足够高效以及系统总体负载等
> > +因素影响。延迟同样是需要考虑的性能指标。 -
> > 批量传输最常用于关注吞吐量的场景。 -需要记住的是,批量传输总是以
> > 512 字节包为单位, -而一个 USB 2.0 微帧中最多只能容纳 13
> > 个这样的包。 -8 个 USB 2.0 微帧构成一个 USB 1.1 帧;
> > -一个微帧的时长是 1 毫秒 / 8 = 125 微秒。
> > +批量传输通常用于看重吞吐量的场景。需要记住的是,批量传输总是以 512
> > 字节包 +为单位,而一个 USB 2.0 微帧中最多只能容纳 13 个这样的包。8
> > 个 USB 2.0 微 +帧构成一个 USB 1.1 帧,因此一个微帧的时长就是 125
> > 微秒。
> > -因此,只要硬件和设备驱动软件都允许,
> > -批量传输可提供超过 50 MByte/sec 的带宽。
> > -周期性传输模式(等时和中断)允许使用更大的包大小,
> > -从而可以逼近所宣称的 480 Mbit/sec 传输率。
> > +因此,只要硬件和驱动实现都足够成熟,批量传输就可以提供 50
> > MByte/sec 以上
> > +的带宽。周期性传输模式(等时和中断)允许使用更大的包大小,从而可以逼近所
> > +宣称的 480 Mbit/sec 传输率。
> > 硬件性能
> > --------
> >
> > -截至本文撰写时,单个 USB 2.0 设备的最大传输速率
> > -通常约为 20 MByte/sec。
> > -这当然会随着时间改变:一些设备现在更快,一些更慢。
> > +截至本文撰写时,单个 USB 2.0 设备的最大传输速率通常约为 20
> > MByte/sec。
> > +这种情况当然会随时间变化:有些设备现在更快,有些则更慢。
> > -第一代 NEC EHCI 实现似乎存在
> > -大约 28 MByte/sec 的硬件瓶颈。
> > -虽然这对单个 20 MByte/sec 的设备显然已经够用,
> > -但把三个这样的设备挂到同一总线上,
> > -并不能得到 60 MByte/sec。
> > -问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
> > -因此它每个微帧只会尝试 6 次(也许是 7 次)
> > -USB 事务,而不是 13 次。
> > -(对一个比其他产品早上市一年的芯片来说,
> > -这是个合理的妥协!)
> > +第一代 NEC EHCI 实现似乎存在大约 28 MByte/sec
> > 的硬件瓶颈。虽然这对单个 +20 MByte/sec
> > 的设备显然已经够用,但把三个这样的设备挂到同一总线上,并不 +能得到
> > 60 MByte/sec。问题似乎在于控制器硬件无法并发进行 USB 与 PCI 访问,
> > +因此它每个微帧只会尝试 6 次(也许是 7 次)USB 事务,而不是 13 次。
> > +(对一款比其他产品早上市一年的芯片来说,这样的取舍也算合理。)
> >
> > -预计较新的实现会在这方面做得更好,
> > -通过投入更多芯片面积来解决这个问题,
> > -使新的主板芯片组更接近 60 MByte/sec 的目标。
> > -这既包括 NEC 的更新实现,也包括其他厂商的芯片。
> > +预计较新的实现会在这方面做得更好,通过投入更多芯片面积来解决这个问题,
> > +使新的主板芯片组更接近 60 MByte/sec 的目标。这既包括 NEC
> > 的更新实现,也 +包括其他厂商的芯片。
> >
> >
> > -主机从 EHCI 控制器收到“请求已完成”中断的最小延迟
> > -为一个微帧(125 微秒)。该延迟可以调节;
> > -驱动提供了一个模块选项。默认情况下,
> > -``ehci-hcd`` 使用最小延迟,这意味着当发出一个控制
> > -或批量请求时,通常可以在不到 250 微秒内得知它已完成
> > -(具体取决于传输大小)。
> > +主机从 EHCI 控制器收到“请求已完成”中断的最小延迟为一个微帧
> > +(125 微秒)。该延迟可以调节;驱动提供了一个模块选项。
> > +默认情况下,``ehci-hcd``
> > 使用最小延迟,这意味着发出控制或批量请求后,通 +常不到 250
> > 微秒就能得知它已经完成(具体取决于传输大小)。
> > 软件性能
> > --------
> >
> > -即便只是要达到 20 MByte/sec 的传输速率,
> > -Linux-USB 设备驱动也必须让 EHCI 队列始终保持满载。
> > -这意味着要发出较大的请求,
> > -或者在需要发出一连串小请求时使用批量请求排队。
> > -如果驱动未做到这一点,那么会直接从性能结果上表现出来。
> > +即便只是要达到 20 MByte/sec 的传输速率,Linux-USB 设备驱动也必须让
> > EHCI
> > +队列始终保持满载。这意味着要发出较大的请求,或者在需要发出一连串小请求
> > +时使用批量请求排队。如果驱动做不到这一点,性能就会明显受影响。
> >
> > -在典型情况下,使用 ``usb_bulk_msg()``
> > -以 4 KB 块循环写出,
> > -会浪费超过一半的 USB 2.0 带宽。
> > -I/O 完成与驱动发出下一次请求之间的延迟,
> > -通常会比一次 I/O 本身耗时更长。
> > -如果同样的循环改用 16 KB 块,会好一些;
> > -若使用一连串 128 KB 块,则浪费会少得多。
> > +在典型场景下,如果使用 ``usb_bulk_msg()`` 以 4 KB
> > 块循环写出,会浪费超过 +一半的 USB 2.0 带宽。I/O
> > 完成与驱动发出下一次请求之间的空档,往往比一次 +I/O
> > 本身耗时还长。如果同样的循环改用 16 KB
> > 块,情况会好一些;若使用一连串 +128 KB 块,则浪费会少得多。
> > +但与其依赖这么大的 I/O 缓冲区来提升同步 I/O
> > 的效率,不如直接向主机控制器
> > +排队提交多个(批量)请求,然后等待它们全部完成(或在出错时取消)。这种
> > +URB 排队方式对所有 USB 1.1 主机控制器驱动同样适用。
> > -但与其依赖这么大的 I/O 缓冲区来让同步 I/O 高效,
> > -不如直接向主机控制器排入多个(批量)请求,
> > -然后等待它们全部完成(或在出错时取消)。
> > -这种 URB 排队方式对所有 USB 1.1
> > -主机控制器驱动也同样适用。
> >
> > -
> > -在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;
> > -它们会把 scatterlist 中的所有缓冲区都排入队列。
> > -它们还使用 scatterlist 的 DMA 映射
> > -(其中可能应用 IOMMU)并减少中断次数,
> > -这些都有助于让高速传输尽可能快地运行。
> > +在 Linux 2.5 内核中,定义了新的 ``usb_sg_*()`` API;它们会把
> > scatterlist +中的所有缓冲区都排入队列。它们还使用 scatterlist 的
> > DMA 映射(其中可能 +应用
> > IOMMU)并减少中断次数,这些都有助于让高速传输尽可能快地运行。
> > 待办:
> > 中断传输和等时(ISO)传输的性能问题。
> > -
> > 这些周期性传输都是完全调度的,因此,主要问题可能在于如何触发高带宽模式。
> > +
> > 这些周期性传输都是完全调度的,因此主要问题可能在于如何触发高带宽模式。
> > 待办:
> > - 通过 ``sysfs`` 中的 ``uframe_periodic_max`` 参数,
> > - 可以分配超过标准 80% 的周期性带宽。
> > + 通过 ``sysfs`` 中的 ``uframe_periodic_max``
> > 参数,可以分配超过标准
> > + 80% 的周期性带宽。
> > 后续将对此进行说明。
> > diff --git a/Documentation/translations/zh_CN/usb/index.rst
> > b/Documentation/translations/zh_CN/usb/index.rst index
> > eb5aca0c13ec..df99814c6497 100644 ---
> > a/Documentation/translations/zh_CN/usb/index.rst +++
> > b/Documentation/translations/zh_CN/usb/index.rst @@ -1,4 +1,14 @@
> > .. SPDX-License-Identifier: GPL-2.0
> > +
> > +.. only:: subproject and latex
> > +
> > + .. raw:: latex
> > +
> > + \renewcommand{\thesection}{}
> > + \renewcommand{\thesubsection}{}
> > + \kerneldocCJKon
> > + \kerneldocBeginSC{
> > +
> > .. include:: ../disclaimer-zh_CN.rst
> >
> > :Original: Documentation/usb/index.rst
> > @@ -24,7 +34,7 @@ USB 支持
> > ehci
> > usbmon
> >
> > -Todolist:
> > +待翻译文档:
> >
> > * functionfs
> > * functionfs-desc
> > @@ -52,3 +62,9 @@ Todolist:
> > ====
> >
> > * :ref:`genindex`
> > +
> > +.. only:: subproject and latex
> > +
> > + .. raw:: latex
> > +
> > + }\kerneldocEndSC
> > diff --git a/Documentation/translations/zh_CN/usb/usbmon.rst
> > b/Documentation/translations/zh_CN/usb/usbmon.rst index
> > 11b6d5b59dce..bbfcbf875e26 100644 ---
> > a/Documentation/translations/zh_CN/usb/usbmon.rst +++
> > b/Documentation/translations/zh_CN/usb/usbmon.rst @@ -16,67 +16,56
> > @@ usbmon
> > 简介
> > ====
> > -小写形式的 ``usbmon`` 指的是内核中的一项功能,
> > -用于收集 USB 总线上的 I/O 跟踪信息。它类似于网络监控工具
> > -``tcpdump(1)`` 或 Ethereal 所使用的数据包套接字。
> > -类似地,人们希望使用 usbdump 或 USBMon
> > -(首字母大写)之类的工具来检查
> > -usbmon 生成的原始跟踪数据。
> > -
> > -usbmon 报告的是各个外设驱动
> > -向主机控制器驱动(HCD)发出的请求。
> > -因此,如果 HCD 本身有 bug,那么 usbmon 报告的跟踪信息
> > -可能无法精确对应实际的总线事务。
> > -这和 tcpdump 的情况是一样的。
> > -
> > -目前实现了两种 API: ``text`` 和 ``binary``。
> > -二进制 API 通过 ``/dev`` 命名空间中的字符设备提供,
> > -并且属于 ABI。文本 API 自内核 2.6.35 起已废弃,
> > -但为了方便仍然可用。
> > +小写的 ``usbmon`` 指的是内核中的一项功能,用于收集 USB 总线上的
> > I/O 跟踪 +信息。它类似于网络监控工具 ``tcpdump(1)`` 或 Ethereal
> > 使用的数据包套接 +字。通常会用 usbdump 或
> > USBMon(首字母大写)之类的工具来查看 usbmon 生成 +的原始跟踪数据。
> > +
> > +usbmon
> > 记录的是各个设备驱动向主机控制器驱动(HCD)发出的请求。因此,如果
> > +HCD 自身有 bug,usbmon
> > 输出的跟踪信息就未必能和真实的总线事务一一对应。 +这和 tcpdump
> > 的情况类似。 + +目前实现了两种 API:``text`` 和 ``binary``。二进制
> > API 通过 ``/dev`` 下的 +字符设备提供,是 ABI 的一部分。文本 API
> > 自内核 2.6.35 起已废弃,但为了 +兼容和使用方便,至今仍然保留。
> >
> > 如何使用 usbmon 收集原始文本跟踪信息
> > ====================================
> >
> > -与数据包套接字不同,usbmon 提供了一种接口,
> > -可以输出文本格式的跟踪信息。这样做有两个目的:
> > -第一,在更完善的格式最终确定之前,
> > -它作为工具间通用的跟踪交换格式;
> > -第二,在不使用工具的情况下,人们也可以直接阅读这些信息。
> > +与数据包套接字不同,usbmon
> > 还提供了一个输出文本格式跟踪信息的接口。这样
> > +做主要有两个目的:一是在更完善的格式最终确定之前,将其作为工具间通用的跟
> > +踪交换格式;二是在没有工具时也能直接阅读这些信息。
> > -要收集原始文本跟踪信息,请按以下步骤进行操作。
> > +要收集原始文本跟踪信息,按下面的步骤做即可。
> >
> > 1. 准备
> > -------
> >
> > -挂载 debugfs(内核配置中必须启用它),并加载 usbmon 模块
> > -(如果它是作为模块构建的)。如果 usbmon 已经编入内核,
> > -那么第二步可以省略。
> > +挂载 debugfs(内核配置里必须启用它),并加载 usbmon
> > 模块(如果它是以模块 +方式构建的)。如果 usbmon
> > 已经编译进内核,这一步就可以省略。
> > 命令示例::
> >
> > - # mount -t debugfs none_debugs /sys/kernel/debug
> > + # mount -t debugfs none /sys/kernel/debug
> > # modprobe usbmon
> > #
> >
> > -确认总线套接字是否存在::
> > +确认 ``usbmon`` 目录下是否有这些条目::
> >
> > # ls /sys/kernel/debug/usb/usbmon
> > 0s 0u 1s 1t 1u 2s 2t 2u 3s 3t 3u 4s 4t 4u
> > #
> >
> > -现在,你可以选择使用 ``0u`` 捕获所有总线上的数据包,
> > -并跳到第 3 步;
> > -也可以先按第 2 步找到目标设备所在的总线。
> > -这样可以过滤掉那些持续输出数据的烦人设备。
> > +现在,你可以直接用 ``0u`` 捕获所有总线上的数据包,然后跳到第 3
> > 步;也可 +以先按第 2
> > 步找出目标设备所在的总线。这样可以把那些持续产生流量的设备过 +滤掉。
> >
> > 2. 查找目标设备连接的是哪条总线
> > -------------------------------
> >
> > -运行 ``cat /sys/kernel/debug/usb/devices``,
> > -找到对应设备的 T 行。通常可以通过厂商字符串来查找。
> > -如果有许多类似设备,可以拔掉其中一个,
> > -再比较前后两次 ``/sys/kernel/debug/usb/devices``
> > -的输出。T 行里会包含总线编号。
> > +运行 ``cat /sys/kernel/debug/usb/devices``,找到对应设备的 T
> > 行。通常可以通过
> > +厂商字符串来查找。如果有很多相似设备,可以拔掉其中一个,再比较前后两次
> > +``/sys/kernel/debug/usb/devices`` 的输出。T 行里会包含总线编号。
> > 示例::
> >
> > @@ -86,8 +75,8 @@ usbmon 报告的是各个外设驱动
> > S: Manufacturer=ATEN
> > S: Product=UC100KM V2.00
> >
> > -``Bus=03`` 表示它位于 3 号总线上。或者,
> > -也可以查看 ``lsusb`` 的输出,并从对应行得到总线编号。
> > +``Bus=03`` 表示它位于 3 号总线上。或者,也可以查看 ``lsusb``
> > 的输出,并从 +对应条目里找到总线编号。
> >
> > 示例如下::
> >
> > @@ -97,133 +86,108 @@ usbmon 报告的是各个外设驱动
> > 3. 启动 cat 命令
> > ----------------
> >
> > -如果只监听单条总线,可执行::
> > +如果只监听单条总线,执行::
> >
> > # cat /sys/kernel/debug/usb/usbmon/3u > /tmp/1.mon.out
> >
> > -否则,如果要监听所有总线,则执行::
> > +否则,如果要监听所有总线,执行::
> >
> > # cat /sys/kernel/debug/usb/usbmon/0u > /tmp/1.mon.out
> >
> > -此进程会一直读取,直到被终止。
> > -由于输出通常会很长,因此更推荐将输出重定向到某个位置。
> > +这个进程会一直运行到被终止为止。由于输出通常会很长,最好把它重定向到文件
> > +或其他位置。
> >
> >
> > 4. 在 USB 总线上执行期望的操作
> > ------------------------------
> >
> > -此处需要执行一些会产生 USB 流量的动作,
> > -比如插入 U 盘、拷贝文件、操作摄像头等。
> > +这里做一些会产生 USB 流量的操作即可,比如插入 U
> > 盘、拷贝文件、操作摄像头 +等。
> >
> >
> > 5. 停止 cat
> > -----------
> >
> > -这一步通常通过键盘中断(Control-C)完成。
> > +这一步通常按下键盘中断(Control-C)即可完成。
> >
> > -此时输出文件(本例中为 ``/tmp/1.mon.out``)
> > -可以保存、通过电子邮件发送,或使用文本编辑器查看。
> > -如果使用最后一种方式,请确保文件不会大到编辑器无法打开。
> > +此时,输出文件(本例中为
> > ``/tmp/1.mon.out``)可以保存下来,通过电子邮件发
> > +送,也可以用文本编辑器查看。如果要用文本编辑器查看,请确保文件大小不会
> > +大到编辑器无法处理。
> >
> > 原始文本数据格式
> > ================
> >
> > -目前支持两种格式:原始格式,也就是 ``1t`` 格式,
> > -以及 ``1u`` 格式。``1t`` 格式在内核 2.6.21 中已被废弃。
> > -``1u`` 格式增加了一些字段,例如 ISO 帧描述符、
> > -``interval`` 等。它生成的行会稍长一些,
> > -但在其他方面是 ``1t`` 格式的完整超集。
> > -
> > -如果程序需要区分上述两种格式,
> > -可以查看 ``address`` 字段(见下文)。
> > -如果其中有两个冒号,就是 ``1t`` 格式;
> > -否则是 ``1u`` 格式。
> > -
> > -任何文本格式的数据由一系列事件组成,
> > -如 URB 提交、URB 回调、提交错误等。
> > -每个事件对应单独的一行文本,
> > -由使用空白符间隔的若干字段组成。
> > -字段的数量与位置可能取决于事件类型,
> > -但以下字段对所有类型都通用:
> > -
> > -下面按从左到右的顺序列出这些共有字段:
> > -
> > -- URB Tag。用于标识 URB,通常是 URB 结构体在内核中的地址
> > - (以十六进制表示),
> > - 但也可能是序号或其他合理的唯一字符串。
> > -
> > -- 时间戳(微秒),十进制数字。
> > - 时间戳的精度取决于可用时钟,
> > - 因此可能远差于
> > - 1 微秒(例如实现使用的是 jiffies)。
> > -
> > -- 事件类型。它表示的是事件的格式,而不是 URB 的类型。
> > - 可用值为:``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。
> > -
> > -- ``Address`` 字段(以前称作 ``pipe``)。
> > - 它包含四个由冒号分隔的字段:
> > - URB 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下:
> > -
> > - == == ==========================
> > - Ci Co 控制输入和输出
> > - Zi Zo 等时输入和输出
> > - Ii Io 中断输入和输出
> > - Bi Bo 批量输入和输出
> > - == == ==========================
> > -
> > - 总线号、设备地址和端点号使用十进制,但可能有前导零。
> > -
> > -- URB 状态字段。这个字段要么是一个字母,
> > - 要么是几个由冒号分隔的数字:
> > - URB 状态、``interval``、``start frame`` 和 ``error count``。
> > - 与 ``address`` 字段不同,除了状态外,其余字段都是可选的。
> > - ``interval`` 只会为中断和等时 URB 打印;``start frame`` 只会为
> > - 等时 URB 打印;错误计数只会在等时回调事件中打印。
> > -
> > - 状态字段是一个十进制数字,有时为负数,
> > - 对应 URB 的 ``status`` 字段。
> > - 对于提交事件,这个字段本身没有实际意义,
> > - 但为了便于脚本解析,它仍然存在。
> > - 当发生错误时,该字段包含错误码。
> > -
> > - 在提交控制包时,这个字段包含的是 ``Setup Tag``,
> > - 而不是一组数字。
> > - 判断 ``Setup Tag`` 是否存在很容易,因为它从来不是数字。
> > - 因此,如果脚本在这个字段里发现的是一组数字,
> > - 就会继续读取数据长度(等时 URB 除外)。
> > - 如果发现的是其他内容,比如一个字母,
> > - 那么脚本会先读取 ``Setup`` 包,再读取数据长度或等时描述符。
> > -
> > -- ``Setup`` 包由 5 个字段组成:
> > - ``bmRequestType``、``bRequest``、``wValue``、
> > - ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。
> > - 如果 ``Setup Tag`` 为 ``s``,就可以安全地解码这些字段。
> > - 否则,说明 Setup
> > 包虽然存在,但并未被捕获,此时各字段中会填入占位内容。
> > +目前支持两种格式:原始的 ``1t`` 格式和 ``1u`` 格式。``1t``
> > 格式在内核 +2.6.21 中已被废弃。``1u`` 格式增加了一些字段,例如 ISO
> > 帧描述符和 +``interval``。它生成的行会稍长一些,但除此之外,它是
> > ``1t`` 格式的完整 +超集。 +
> > +如果程序需要区分上述两种格式,可以查看 ``address``
> > 字段(见下文)。如果 +其中有两个冒号,就是 ``1t`` 格式;否则是
> > ``1u`` 格式。 +
> > +任何文本格式的数据都由一系列事件构成,例如 URB 提交、URB
> > 回调和提交错
> > +误。每个事件占一行,由若干以空白符分隔的字段组成。字段数量和位置会随事件
> > +类型变化,但下面这些字段对所有类型都通用: +
> > +下面按从左到右的顺序说明这些通用字段:
> > +
> > +- URB 标识(URB Tag)。用于标识 URB,通常是 URB
> > 结构体在内核中的地址
> > + (十六进制),也可能是序号或其他足以唯一标识 URB 的字符串。
> > +
> > +-
> > 时间戳(微秒),十进制数字。时间戳的精度取决于可用时钟,所以可能远低于
> > + 1 微秒(例如实现使用 jiffies 时)。
> > +
> > +- 事件类型。它表示的是这一行事件的格式,而不是 URB
> > 的类型。可用值为:
> > + ``S`` 表示提交,``C`` 表示回调,``E`` 表示提交错误。
> > +
> > +- ``Address`` 字段(以前称为
> > ``pipe``)。它包含四个由冒号分隔的字段:URB
> > + 类型及方向、总线号、设备地址和端点号。类型与方向的编码如下:
> > +
> > + - ``Ci`` / ``Co``:控制输入 / 输出
> > + - ``Zi`` / ``Zo``:等时输入 / 输出
> > + - ``Ii`` / ``Io``:中断输入 / 输出
> > + - ``Bi`` / ``Bo``:批量输入 / 输出
> > +
> > +
> > 总线号、设备地址和端点号都是十进制数,但可能有前导零,方便人阅读。 +
> > +- URB
> > 状态字段。这个字段要么是一个字母,要么是几个用冒号分隔的数字,依次
> > + 表示 URB 状态、``interval``、``start frame`` 和 ``error
> > count``。与
> > + ``address`` 字段不同,除状态外,其余字段都可能省略。``interval``
> > 只会在
> > + 中断和等时 URB 中打印;``start frame`` 只会在等时 URB
> > 中打印;错误计数只
> > + 会在等时回调事件中打印。
> > +
> > + 状态字段是一个十进制数,有时为负数,对应 URB 的 ``status``
> > 字段。对于提
> > +
> > 交事件,这个字段本身并无实际语义,但为了便于脚本解析仍会保留。发生错误
> > + 时,这里填的是错误码。
> > +
> > + 如果是控制包的提交事件,这个字段里放的不是一组数字,而是 ``Setup
> > Tag``。
> > + 这很容易分辨,因为 ``Setup Tag``
> > 永远不是数字。所以脚本如果在这里读到一
> > + 组数字,就会继续读取数据长度(等时 URB
> > 除外);如果读到的是字母之类的内
> > + 容,就要先读取 ``Setup`` 包,再读取数据长度或等时描述符。
> > +
> > +- ``Setup`` 包由 5
> > 个字段组成:``bmRequestType``、``bRequest``、``wValue``、
> > + ``wIndex`` 和 ``wLength``。这些字段由 USB 2.0 规范定义。如果
> > ``Setup Tag``
> > + 是 ``s``,就可以安全解码这些字段。否则,说明 Setup
> > 包虽然存在,但并未被
> > + 捕获,此时各字段中会填入占位内容。
> >
> > - 等时传输帧描述符的数量及其内容:
> > - 如果一个等时传输事件带有一组描述符,首先打印该 URB
> > 中描述符的总数,
> > - 然后为每个描述符打印一个字段,最多打印 5 个字段。
> > - 每个字段由三个用冒号分隔的十进制数字组成,
> > - 分别表示状态(status)、偏移(offset)和长度(length)。
> > - 对于提交(submission),报告的是初始长度;
> > - 对于回调(callback),报告的是实际长度。
> > + 如果某个等时传输事件带有描述符,会先打印该 URB
> > 的描述符总数,再为每个描
> > + 述符打印一个字段,最多 5
> > 个。每个字段由三个用冒号分隔的十进制数组成,依
> > +
> > 次表示状态(status)、偏移(offset)和长度(length)。对于提交事件,报
> > + 告的是初始长度;对于回调事件,报告的是实际长度。
> >
> > -- 数据长度:
> > - 对于提交,表示请求的长度;对于回调,表示实际传输的长度。
> > +-
> > 数据长度:对于提交,表示请求的长度;对于回调,表示实际传输的长度。
> > -- 数据标签:
> > - 即使数据长度非零,usbmon 也不一定会捕获数据。
> > - 仅当标签为 ``=`` 时,才会有数据字段。
> > +- 数据标签:即使数据长度非零,usbmon 也不一定会捕获数据。只有标签为
> > + ``=`` 时,才会有数据字段。
> >
> > -- 数据字段:
> > - 以大端十六进制格式显示。注意,这些并不是真正的机器字,
> > - 而只是把字节流拆成若干“字”以便阅读。因此最后一个字可能只包含
> > - 1 到 4 个字节。
> > - 收集的数据长度是有限的,可能小于数据长度字段中报告的值。
> > -
> > 因为数据长度字段只统计实际接收到的字节,而数据字段包含整个传输缓冲区,
> > - 所以,在等时输入(Zi)完成且缓冲区中接收到的数据稀疏的情况下,
> > - 收集的数据长度可能大于数据长度字段的值。
> > +-
> > 数据字段:以大端十六进制格式显示。注意,这些并不是真正的机器字,只是为
> > + 了便于阅读,把字节流按“字”分组显示。因此最后一个字可能只包含 1
> > 到 4 个
> > +
> > 字节。捕获的数据长度是有限的,可能小于数据长度字段中报告的值。对于等时
> > +
> > 输入(Zi)完成事件,如果缓冲区里的接收数据比较稀疏,捕获数据的长度甚至
> > +
> > 可能大于数据长度字段,因为后者只统计实际接收到的字节,而数据字段展示的
> > + 是整个传输缓冲区。
> >
> >
> >
> > @@ -234,18 +198,16 @@ usbmon 报告的是各个外设驱动
> > d5ea89a0 3575914555 SCi:1:001:0 s a3 00 0000 0003 0004 4 <
> > d5ea89a0 3575914560 CCi:1:001:0 0 4 = 01050000
> >
> > -向地址为 5 的存储设备发送
> > -31 字节 Bulk 包装的 SCSI 命令 ``0x28``
> > -(``READ_10``)的输出批量传输::
> > +向地址为 5 的存储设备发送一个输出批量传输,其中 31 字节的 Bulk
> > 封装用于承 +载 SCSI 命令 ``0x28``(``READ_10``)::
> >
> > dd65f0e8 4128379752 SBo:1:005:2 -115 31 = 55534243 ad000000
> > 00800000 80010a28 20000000 20000040 00000000 000000 dd65f0e8
> > 4128379808 CBo:1:005:2 0 31 >
> > 原始二进制格式与 API
> > ====================
> > -API 的整体架构与前文大体相同,只是事件以二进制格式传递。
> > -每个事件都通过下面的结构发送
> > -(这个名字是为了叙述方便而虚构的)::
> > +API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递。每个事件都通过
> > +下面的结构发送(这个结构名只是为了叙述方便而虚构的)::
> >
> > struct usbmon_packet {
> > @@ -275,29 +237,22 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递 unsigned int
> > ndesc; /* 60: 实际 ISO 描述符数量 */ };
> > /* 64 总长度 */
> > -可以用 ``read(2)``、``ioctl(2)``,
> > -或者通过 ``mmap`` 访问缓冲区,
> > -从字符设备接收这些事件。
> > -不过,出于兼容性原因,``read(2)``
> > -只返回前 48 个字节。
> > +可以用 ``read(2)``、``ioctl(2)``,或者通过 ``mmap``
> > 访问缓冲区,从字符设
> > +备接收这些事件。不过,出于兼容性原因,``read(2)`` 只返回前 48
> > 个字节。 -字符设备通常命名为 ``/dev/usbmonN``,
> > -其中 ``N`` 是 USB 总线号。
> > -编号为零的设备(``/dev/usbmon0``)比较特殊,
> > -表示“所有总线”。
> > -请注意,具体命名策略由 Linux 发行版决定。
> > +字符设备通常命名为 ``/dev/usbmonN``,其中 ``N`` 是 USB
> > 总线号。编号为零的
> > +设备(``/dev/usbmon0``)比较特殊,表示“所有总线”。具体命名策略由
> > Linux +发行版决定。
> > -如果你手动创建 ``/dev/usbmon0``,
> > -请确保它归 root 所有,并且权限为 ``0600``。
> > -否则,非特权用户将能够窃听键盘流量。
> > +如果你手动创建 ``/dev/usbmon0``,请确保它归 root 所有,并且权限为
> > ``0600``。 +否则,非特权用户就能窃听键盘输入流量。
> >
> > 以下 ``MON_IOC_MAGIC`` 为 ``0x92`` 的 ioctl 调用可用:
> >
> > ``MON_IOCQ_URB_LEN``,定义为 ``_IO(MON_IOC_MAGIC, 1)``
> >
> > -该调用返回下一个事件的数据长度。
> > -注意大多数事件不包含数据,
> > -因此如果该调用返回零,并不意味着没有事件。
> > +该调用返回下一个事件的数据长度。注意大多数事件不包含数据,因此如果它返回
> > +零,并不意味着没有事件。
> >
> > ``MON_IOCG_STATS``,定义为
> > ``_IOR(MON_IOC_MAGIC, 3, struct mon_bin_stats)``
> > @@ -309,31 +264,28 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递 u32 dropped;
> > };
> >
> > -成员 ``queued`` 表示当前缓冲区中已经排队的事件数量,
> > -而不是自上次重置以来处理过的事件数量。
> > +成员 ``queued``
> > 表示当前缓冲区中已经排队的事件数量,而不是自上次重置以来
> > +处理过的事件数量。
> > -成员 ``dropped`` 表示自上次调用
> > -``MON_IOCG_STATS`` 以来丢失的事件数量。
> > +成员 ``dropped`` 表示自上次调用 ``MON_IOCG_STATS``
> > 以来丢失的事件数量。
> > ``MON_IOCT_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 4)``
> >
> > -此调用设置缓冲区大小。参数为以字节为单位的缓冲区大小。
> > -大小可能会向下取整到下一个块(或页)。
> > -如果请求的大小超出该内核的 [未指定] 范围,
> > -则调用会失败并返回 ``-EINVAL``。
> > +此调用设置缓冲区大小。参数是以字节为单位的缓冲区大小。大小可能会向下取整
> > +到下一个块(或页)。如果请求的大小超出当前内核允许的范围,则调用会失败并
> > +返回 ``-EINVAL``。
> >
> > ``MON_IOCQ_RING_SIZE``,定义为 ``_IO(MON_IOC_MAGIC, 5)``
> >
> > 该调用返回缓冲区当前大小(以字节为单位)。
> >
> > -``MON_IOCX_GET``,定义为
> > -``_IOW(MON_IOC_MAGIC, 6, struct mon_get_arg)``
> > -``MON_IOCX_GETX``,定义为
> > -``_IOW(MON_IOC_MAGIC, 10, struct mon_get_arg)``
> > +``MON_IOCX_GET`` 和 ``MON_IOCX_GETX`` 的定义分别如下:
> > +
> > +- ``MON_IOCX_GET``:``_IOW(MON_IOC_MAGIC, 6, struct mon_get_arg)``
> > +- ``MON_IOCX_GETX``:``_IOW(MON_IOC_MAGIC, 10, struct
> > mon_get_arg)``
> > -如果内核缓冲区中没有事件,
> > -这些调用就会一直等待,直到有事件到达,
> > -然后返回第一个事件。
> > +如果内核缓冲区中没有事件,这些调用就会一直等待,直到有事件到达,然后返回
> > +第一个事件。
> > 参数是指向以下结构的指针::
> >
> > struct mon_get_arg {
> > @@ -343,20 +295,18 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递 };
> >
> >
> > -调用前,应填好 ``hdr``、``data`` 和 ``alloc``。
> > -调用返回后,``hdr`` 指向的区域中包含下一个事件的结构;
> > -如果存在数据,那么数据缓冲区中也会包含相应数据。
> > -该事件会从内核缓冲区中移除。
> > +调用前,应填好 ``hdr``、``data`` 和 ``alloc``。调用返回后,``hdr``
> > 指向的
> > +内存区域中会写入下一个事件的结构;如果存在数据,数据缓冲区中也会填入相应
> > +内容。该事件会从内核缓冲区中移除。
> > -``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr`` 区域,
> > -``MON_IOCX_GETX`` 会复制 64 字节。
> > +``MON_IOCX_GET`` 会将 48 字节的数据复制到 ``hdr``
> > 区域,``MON_IOCX_GETX`` +会复制 64 字节。
> >
> > ``MON_IOCX_MFETCH``,定义为
> > ``_IOWR(MON_IOC_MAGIC, 7, struct mon_mfetch_arg)``
> >
> > -当应用程序通过 ``mmap(2)`` 访问缓冲区时,
> > -主要使用这个 ioctl。
> > -其参数是指向以下结构的指针::
> > +应用程序通过 ``mmap(2)`` 访问缓冲区时,主要使用这个
> > ioctl。其参数是指向 +以下结构的指针::
> >
> > struct mon_mfetch_arg {
> > uint32_t *offvec; /* 获取的事件偏移向量 */
> > @@ -365,41 +315,36 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递 };
> >
> >
> > -该 ioctl 的操作分为三个阶段:
> > +这个 ioctl 的流程分为三个阶段:
> >
> > -首先,从内核缓冲区移除并丢弃最多 ``nflush`` 个事件。
> > -实际丢弃的事件数量会写回 ``nflush``。
> > +首先,从内核缓冲区移除并丢弃最多 ``nflush``
> > 个事件。实际丢弃的事件数量会 +写回 ``nflush``。
> >
> > -其次,除非伪设备以 ``O_NONBLOCK`` 打开,否则会一直等待,
> > -直到缓冲区中出现事件。
> > +其次,除非设备以 ``O_NONBLOCK``
> > 打开,否则会一直等待,直到缓冲区中出现 +事件。
> >
> > -第三,将最多 ``nfetch`` 个偏移量提取到 mmap
> > -缓冲区,并存入 ``offvec`` 中。
> > -实际提取到的事件偏移数量会存回 ``nfetch``。
> > +第三,将最多 ``nfetch`` 个偏移量提取到 mmap 缓冲区,并存入
> > ``offvec`` 中。 +实际提取到的事件偏移数量会写回 ``nfetch``。
> >
> > ``MON_IOCH_MFLUSH``,定义为 ``_IO(MON_IOC_MAGIC, 8)``
> >
> > -此调用从内核缓冲区移除若干事件。
> > -其参数为要移除的事件数量。
> > -如果缓冲区中的事件少于请求数量,
> > -则移除所有事件,且不报告错误。
> > -当没有事件时也可使用。
> > +此调用从内核缓冲区移除若干事件。其参数是要移除的事件数量。如果缓冲区中的
> > +事件少于请求数量,则移除全部现有事件,且不报告错误。即使当前没有事件,也
> > +可以调用。
> >
> > ``FIONBIO``
> >
> > 如果有需要,将来可能会实现 ``FIONBIO`` ioctl。
> >
> > -除了 ``ioctl(2)`` 和 ``read(2)`` 之外,
> > -二进制 API 的特殊文件也可以用 ``select(2)`` 和
> > -``poll(2)`` 轮询。
> > -但 ``lseek(2)`` 不起作用。
> > +除了 ``ioctl(2)`` 和 ``read(2)`` 之外,二进制 API
> > 对应的特殊文件还可以用 +``select(2)`` 和 ``poll(2)`` 轮询,但
> > ``lseek(2)`` 不可用。
> > * 二进制 API 的内核缓冲区内存映射访问
> >
> > -基本思想很简单:
> > +基本思路很简单:
> >
> > -准备时,先获取当前大小,再用 ``mmap(2)`` 映射缓冲区。
> > -然后执行类似下面伪代码的循环::
> > +准备时,先查询当前大小,再用 ``mmap(2)``
> > 映射缓冲区。之后运行与下面伪代码 +类似的循环::
> >
> > struct mon_mfetch_arg fetch;
> > struct usbmon_packet *hdr;
> > @@ -411,7 +356,7 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递 ioctl(fd,
> > MON_IOCX_MFETCH, &fetch); // 同时处理错误 nflush = fetch.nfetch;
> > // 完成后要刷新这么多包 for (i = 0; i < nflush; i++) {
> > - hdr = (struct ubsmon_packet *) &mmap_area[vec[i]];
> > + hdr = (struct usbmon_packet *) &mmap_area[vec[i]];
> > if (hdr->type == '@') // 填充包
> > continue;
> > caddr_t data = &mmap_area[vec[i]] + 64;
> > @@ -421,7 +366,7 @@ API
> > 的整体架构与前文大体相同,只是事件以二进制格式传递
> >
> >
> > -因此,主要思想是每 N 个事件只执行一次 ioctl。
> > +因此,这里的核心思路就是每 N 个事件只执行一次 ioctl。
> >
> > -虽然缓冲区是环形的,但返回的头和数据不会跨越缓冲区末端,
> > -因此上面的伪代码无需任何合并操作。
> > +虽然缓冲区是环形的,但返回的头部和数据不会跨越缓冲区末端,因此上面的伪代
> > +码无需做任何拼接。
> > -- 2.34.1
> >
>
>
>
^ permalink raw reply
* Re: [PATCH v4 11/13] selftests/liveupdate: Test session and file limit removal
From: Pratyush Yadav @ 2026-06-01 14:17 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-12-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> With the removal of static limits on the number of sessions and files per
> session, the orchestrator now uses dynamic allocation.
>
> Add new test cases to verify that the system can handle a large number of
> sessions and files. These tests ensure that the dynamic block allocation
> and reuse logic for session metadata and outgoing files work correctly
> beyond the previous static limits.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
> ---
> .../testing/selftests/liveupdate/liveupdate.c | 75 +++++++++++++++++++
> .../selftests/liveupdate/luo_test_utils.c | 24 ++++++
> .../selftests/liveupdate/luo_test_utils.h | 2 +
> 3 files changed, 101 insertions(+)
>
> diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
> index c7d94b9181e1..502fb3567e38 100644
> --- a/tools/testing/selftests/liveupdate/liveupdate.c
> +++ b/tools/testing/selftests/liveupdate/liveupdate.c
> @@ -26,6 +26,7 @@
>
> #include <linux/liveupdate.h>
>
> +#include "luo_test_utils.h"
> #include "../kselftest.h"
> #include "../kselftest_harness.h"
>
> @@ -499,4 +500,78 @@ TEST_F(liveupdate_device, get_session_name_max_length)
> ASSERT_EQ(close(session_fd), 0);
> }
>
> +/*
> + * Test Case: Manage Many Sessions
> + *
> + * Verifies that a large number of sessions can be created and then
> + * destroyed during normal system operation. This specifically tests the
> + * dynamic block allocation and reuse logic for session metadata management
> + * without preserving any files.
> + */
> +TEST_F(liveupdate_device, preserve_many_sessions)
> +{
> +#define MANY_SESSIONS 2000
> + int session_fds[MANY_SESSIONS];
> + int ret, i;
> +
> + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
> + if (self->fd1 < 0 && errno == ENOENT)
> + SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
> + ASSERT_GE(self->fd1, 0);
> +
> + ret = luo_ensure_nofile_limit(MANY_SESSIONS);
> + if (ret == -EPERM)
> + SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
> + ASSERT_EQ(ret, 0);
> +
> + for (i = 0; i < MANY_SESSIONS; i++) {
> + char name[64];
> +
> + snprintf(name, sizeof(name), "many-session-%d", i);
> + session_fds[i] = create_session(self->fd1, name);
> + ASSERT_GE(session_fds[i], 0);
> + }
> +
> + for (i = 0; i < MANY_SESSIONS; i++)
> + ASSERT_EQ(close(session_fds[i]), 0);
> +}
> +
> +/*
> + * Test Case: Preserve Many Files
> + *
> + * Verifies that a large number of files can be preserved in a single session
> + * and then destroyed during normal system operation. This tests the dynamic
> + * block allocation and management for outgoing files.
> + */
> +TEST_F(liveupdate_device, preserve_many_files)
> +{
> +#define MANY_FILES 500
> + int mem_fds[MANY_FILES];
> + int session_fd, ret, i;
> +
> + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
> + if (self->fd1 < 0 && errno == ENOENT)
> + SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
> + ASSERT_GE(self->fd1, 0);
> +
> + session_fd = create_session(self->fd1, "many-files-test");
> + ASSERT_GE(session_fd, 0);
> +
> + ret = luo_ensure_nofile_limit(MANY_FILES + 10);
> + if (ret == -EPERM)
> + SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE");
> + ASSERT_EQ(ret, 0);
> +
> + for (i = 0; i < MANY_FILES; i++) {
> + mem_fds[i] = memfd_create("test-memfd", 0);
> + ASSERT_GE(mem_fds[i], 0);
> + ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0);
> + }
> +
> + for (i = 0; i < MANY_FILES; i++)
> + ASSERT_EQ(close(mem_fds[i]), 0);
> +
> + ASSERT_EQ(close(session_fd), 0);
> +}
> +
> TEST_HARNESS_MAIN
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
> index 3c8721c505df..333a3530051b 100644
> --- a/tools/testing/selftests/liveupdate/luo_test_utils.c
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
> @@ -17,6 +17,7 @@
> #include <sys/syscall.h>
> #include <sys/mman.h>
> #include <sys/types.h>
> +#include <sys/resource.h>
> #include <sys/stat.h>
> #include <errno.h>
> #include <stdarg.h>
> @@ -28,6 +29,29 @@ int luo_open_device(void)
> return open(LUO_DEVICE, O_RDWR);
> }
>
> +int luo_ensure_nofile_limit(long min_limit)
> +{
> + struct rlimit hl;
> +
> + /* Allow to extra files to be used by test itself */
> + min_limit += 32;
> +
> + if (getrlimit(RLIMIT_NOFILE, &hl) < 0)
> + return -errno;
> +
> + if (hl.rlim_cur >= min_limit)
> + return 0;
> +
> + hl.rlim_cur = min_limit;
> + if (hl.rlim_cur > hl.rlim_max)
> + hl.rlim_max = hl.rlim_cur;
> +
> + if (setrlimit(RLIMIT_NOFILE, &hl) < 0)
> + return -errno;
> +
> + return 0;
> +}
> +
> int luo_create_session(int luo_fd, const char *name)
> {
> struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
> index 90099bf49577..6a0d85386613 100644
> --- a/tools/testing/selftests/liveupdate/luo_test_utils.h
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
> @@ -26,6 +26,8 @@ int luo_create_session(int luo_fd, const char *name);
> int luo_retrieve_session(int luo_fd, const char *name);
> int luo_session_finish(int session_fd);
>
> +int luo_ensure_nofile_limit(long min_limit);
> +
> int create_and_preserve_memfd(int session_fd, int token, const char *data);
> int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v2 03/10] riscv: Standardize extension capitalization
From: Guodong Xu @ 2026-06-02 2:11 UTC (permalink / raw)
To: Anup Patel, Paul Walmsley
Cc: Guodong Xu, Jonathan Corbet, Palmer Dabbelt, Conor Dooley,
Albert Ou, Alexandre Ghiti, Shuah Khan, Atish Patra, Shuah Khan,
Deepak Gupta, Zong Li, Christian Brauner, Andrew Jones,
Charlie Jenkins, Samuel Holland, Charlie Jenkins, linux-doc,
linux-riscv, linux-kernel, linux-kselftest, kvm, kvm-riscv
In-Reply-To: <CAAhSdy0pHPAMRRR-Mri735zLu3ymr=ePQ7gA6zZ11BkHEXj+AA@mail.gmail.com>
Hi Anup,
> Acked-by: Anup Patel <anup@brainfault.org>
> Reviewed-by: Anup Patel <anup@brainfault.org>
Thanks for the review.
> @Paul, This patch is independent of other changes
> in this series. If you are okay then I can take this single
> patch through KVM RISC-V tree.
There is a dependency ordering I should point out. Patch 08
("riscv: cpufeature: Introduce ISA bases bitmap and rva23u64 detection")
uses the upper-case RISCV_ISA_EXT_I/M/A... macros introduced in this patch,
so it won't build without patch 03. I mean, if patch 03 goes into a
different tree on its own.
But of course, I can rebase them onto your tree if patch 03 lands earlier
than the other patches.
Either way should be fine with me, your call.
Thanks,
Guodong Xu
docular.xu@gmail.com
^ permalink raw reply
* Re: [PATCH v4 12/13] selftests/liveupdate: Add stress-sessions kexec test
From: Pratyush Yadav @ 2026-06-01 14:19 UTC (permalink / raw)
To: Pasha Tatashin
Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, skhan, linux-doc,
linux-kernel, corbet, dmatlack, kexec, pratyush, skhawaja, graf
In-Reply-To: <20260530221938.115978-13-pasha.tatashin@soleen.com>
On Sat, May 30 2026, Pasha Tatashin wrote:
> Add a new test that creates 2000 LUO sessions before a kexec
> reboot and verifies their presence after the reboot. This ensures
> that the linked-block serialization mechanism works correctly for
> a large number of sessions.
>
> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v4 04/13] liveupdate: register luo_ser as KHO subtree
From: Pratyush Yadav @ 2026-06-01 14:27 UTC (permalink / raw)
To: Pasha Tatashin
Cc: Pratyush Yadav, linux-kselftest, rppt, shuah, akpm, linux-mm,
skhan, linux-doc, linux-kernel, corbet, dmatlack, kexec, skhawaja,
graf
In-Reply-To: <ah2IuC1Z1ssIquXZ@google.com>
On Mon, Jun 01 2026, Pasha Tatashin wrote:
> On 06-01 14:39, Pratyush Yadav wrote:
>> On Sat, May 30 2026, Pasha Tatashin wrote:
>>
>> > Entirely remove the LUO FDT wrapper since the FDT only carries the
>> > compatible string and the pointer to the centralized struct luo_ser.
>> > Instead, register the struct luo_ser via the KHO raw subtree
>> > API, placing the compatibility string inside the structure itself.
>> >
>> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
>> > ---
>> > include/linux/kho/abi/luo.h | 57 +++++++++---------------
>> > kernel/liveupdate/luo_core.c | 85 +++++++++++-------------------------
>> > 2 files changed, 46 insertions(+), 96 deletions(-)
>> >
>> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
>> > index 1b2f865a771a..9a4fe491812b 100644
>> > --- a/include/linux/kho/abi/luo.h
>> > +++ b/include/linux/kho/abi/luo.h
>> > @@ -10,11 +10,11 @@
>> > *
>> > * Live Update Orchestrator uses the stable Application Binary Interface
>> > * defined below to pass state from a pre-update kernel to a post-update
>> > - * kernel. The ABI is built upon the Kexec HandOver framework and uses a
>> > - * Flattened Device Tree to describe the preserved data.
>> > + * kernel. The ABI is built upon the Kexec HandOver framework and registers
>> > + * the central `struct luo_ser` via the KHO raw subtree API.
>> > *
>> > - * This interface is a contract. Any modification to the FDT structure, node
>> > - * properties, compatible strings, or the layout of the `__packed` serialization
>> > + * This interface is a contract. Any modification to the structure fields,
>> > + * compatible strings, or the layout of the `__packed` serialization
>> > * structures defined here constitutes a breaking change. Such changes require
>> > * incrementing the version number in the relevant `_COMPATIBLE` string to
>> > * prevent a new kernel from misinterpreting data from an old kernel.
>> > @@ -23,31 +23,15 @@
>> > * however, backward/forward compatibility is only guaranteed for kernels
>> > * supporting the same ABI version.
>> > *
>> > - * FDT Structure Overview:
>> > + * KHO Structure Overview:
>> > * The entire LUO state is encapsulated within a single KHO entry named "LUO".
>> > - * This entry contains an FDT with the following layout:
>> > - *
>> > - * .. code-block:: none
>> > - *
>> > - * / {
>> > - * compatible = "luo-v2";
>> > - * luo-abi-header = <phys_addr_of_luo_ser>;
>> > - * };
>> > - *
>> > - * Main LUO Node (/):
>> > - *
>> > - * - compatible: "luo-v2"
>> > - * Identifies the overall LUO ABI version.
>> > - * - luo-abi-header: u64
>> > - * The physical address of `struct luo_ser`.
>> > + * This entry contains the `struct luo_ser` structure.
>> > *
>> > * Serialization Structures:
>> > - * The FDT properties point to memory regions containing arrays of simple,
>> > - * `__packed` structures. These structures contain the actual preserved state.
>> > - *
>> > * - struct luo_ser:
>> > * The central ABI structure that contains the overall state of the LUO.
>> > - * It includes the liveupdate-number and pointers to sessions and FLBs.
>> > + * It includes the compatibility string, the liveupdate-number, and pointers
>> > + * to sessions and FLBs.
>> > *
>> > * - struct luo_session_header_ser:
>> > * Header for the session array. Contains the total page count of the
>> > @@ -78,26 +62,27 @@
>> > #ifndef _LINUX_KHO_ABI_LUO_H
>> > #define _LINUX_KHO_ABI_LUO_H
>> >
>> > +#include <linux/align.h>
>> > #include <uapi/linux/liveupdate.h>
>> >
>> > /*
>> > - * The LUO FDT hooks all LUO state for sessions, fds, etc.
>> > + * The LUO state is registered under this KHO entry name.
>> > */
>> > -#define LUO_FDT_SIZE PAGE_SIZE
>> > -#define LUO_FDT_KHO_ENTRY_NAME "LUO"
>> > -#define LUO_FDT_COMPATIBLE "luo-v2"
>> > -#define LUO_FDT_ABI_HEADER "luo-abi-header"
>> > +#define LUO_KHO_ENTRY_NAME "LUO"
>> > +#define LUO_ABI_COMPATIBLE "luo-v3"
>> > +#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
>>
>> The length of the compatible field will change depending on the length
>> of the string. While that is technically fine since a new ABI version is
>> allowed to change the layout, it feels odd. I think it would be better
>> if we define a static size here, say 64 bytes. This way you can avoid
>> all the weirdness that can happen when you move from one version to
>> another.
>
> This is what I used initially, but we have cases where one LUO/KHO
> subsystem depends on another. For example, the LUO version must change
> when the block version changes, making the static length too
> restrictive. I would prefer to use proper strncmp() everywhere and allow
> the version string to change dynamically between kernels, while still
> allowing something like this (from [PATCH v4 09/13] liveupdate: Remove
> limit on the number of sessions):
>
> #define LUO_COMPAT_BASE "luo-v3"
> #define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-"
> KHO_BLOCK_ABI_COMPATIBLE
>
> In the future, we may extend this further as we add more dependencies,
> such as your preservable xarray, vmalloc, etc. Everything that depends
> on an external version should include that in its compatibility string.
Hmm, it feels odd, but I don't have any real counter arguments. So let's
keep this as-is.
>
>>
>> >
>> > /**
>> > * struct luo_ser - Centralized LUO ABI header.
>> > + * @compatible: Compatibility string identifying the LUO ABI version.
>> > * @liveupdate_num: A counter tracking the number of successful live updates.
>> > * @sessions_pa: Physical address of the first session block header.
>> > * @flbs_pa: Physical address of the FLB header.
>> > *
>> > - * This structure is the root of all preserved LUO state. It is pointed to by
>> > - * the "luo-abi-header" property in the LUO FDT.
>> > + * This structure is the root of all preserved LUO state.
>> > */
>> > struct luo_ser {
>> > + char compatible[LUO_ABI_COMPAT_LEN];
>> > u64 liveupdate_num;
>> > u64 sessions_pa;
>> > u64 flbs_pa;
>> [...]
>> > @@ -94,40 +91,29 @@ static int __init luo_early_startup(void)
>> > return 0;
>> > }
>> >
>> > - /* Retrieve LUO subtree, and verify its format. */
>> > - err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
>> > + /* Retrieve LUO state from KHO. */
>> > + err = kho_retrieve_subtree(LUO_KHO_ENTRY_NAME, &luo_ser_phys, &len);
>> > if (err) {
>> > if (err != -ENOENT) {
>> > - pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
>> > - LUO_FDT_KHO_ENTRY_NAME, ERR_PTR(err));
>> > + pr_err("failed to retrieve LUO state '%s' from KHO: %pe\n",
>> > + LUO_KHO_ENTRY_NAME, ERR_PTR(err));
>> > return err;
>> > }
>> >
>> > return 0;
>> > }
>> >
>> > - luo_global.fdt_in = phys_to_virt(fdt_phys);
>> > - err = fdt_node_check_compatible(luo_global.fdt_in, 0,
>> > - LUO_FDT_COMPATIBLE);
>> > - if (err) {
>> > - pr_err("FDT '%s' is incompatible with '%s' [%d]\n",
>> > - LUO_FDT_KHO_ENTRY_NAME, LUO_FDT_COMPATIBLE, err);
>> > -
>> > + if (len < sizeof(*luo_ser)) {
>>
>> len != sizeof(*luo_ser) here?
>
> I can change this, but it is not necessary. It is common practice to
> verify that a "struct" is not smaller when compatibility is checked,
> allowing for future expansion without breaking compatibility with older
> kernels. I know we do not support forward/backward compatibility in any
> way right now, but I do not think it hurts to put the proper safeguards
> in place.
Yeah, that was my point. We don't support anything other than exact
agreement on formats. But let's keep it this way for now, so we can grow
the struct in a backwards compatible way if needed.
>
> Pasha
>
>>
>> > + pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
>> > return -EINVAL;
>> > }
>> >
>> > - header_size = 0;
>> > - ptr = fdt_getprop(luo_global.fdt_in, 0, LUO_FDT_ABI_HEADER, &header_size);
>> > - if (!ptr || header_size != sizeof(u64)) {
>> > - pr_err("Unable to get ABI header '%s' [%d]\n",
>> > - LUO_FDT_ABI_HEADER, header_size);
>> > -
>> > + luo_ser = phys_to_virt(luo_ser_phys);
>> > + if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
>> > + pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
>> > return -EINVAL;
>> > }
>> >
>> > - luo_ser_pa = get_unaligned((u64 *)ptr);
>> > - luo_ser = phys_to_virt(luo_ser_pa);
>> > -
>> > luo_global.liveupdate_num = luo_ser->liveupdate_num;
>> > pr_info("Retrieved live update data, liveupdate number: %lld\n",
>> > luo_global.liveupdate_num);
>> [...]
>>
>> --
>> Regards,
>> Pratyush Yadav
--
Regards,
Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v5 04/18] mm: skip out-of-range bits in mk_vma_flags()
From: Mike Rapoport @ 2026-06-01 14:28 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Lorenzo Stoakes, akpm, peterx, david, surenb, vbabka,
Liam.Howlett, ziy, corbet, skhan, seanjc, pbonzini, jthoughton,
aarcange, sj, usama.arif, linux-mm, linux-kernel, linux-doc,
linux-kselftest, kvm, kernel-team, stable
In-Reply-To: <ah2SL2c0nMj0KBtP@thinkstation>
On Mon, Jun 01, 2026 at 03:08:19PM +0100, Kiryl Shutsemau wrote:
> On Sat, May 30, 2026 at 07:52:25PM +0300, Mike Rapoport wrote:
> > I have a PoC of yet another alternative:
> >
> > https://git.kernel.org/pub/scm/linux/kernel/git/rppt/linux.git/log/?h=uffd/vm-flags
> >
> > The idea there is to keep a single VMA flag, VMA_UFFD_BIT/VM_UFFD and move
> > all the rest into what's now struct vm_userfaultfd_ctx.
>
> Nice!
>
> I assume it can go on top what I did, right?
Yes, it's already on top of a previous version of RWP.
> --
> Kiryl Shutsemau / Kirill A. Shutemov
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH mm-unstable v18 10/14] mm/khugepaged: introduce collapse_allowable_orders helper function
From: Lorenzo Stoakes @ 2026-06-01 14:35 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
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, 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: <b7ddff01-9adf-4ff7-84a6-021087936c34@kernel.org>
On Sun, May 31, 2026 at 10:18:16PM +0200, David Hildenbrand (Arm) wrote:
> On 5/22/26 17:00, Nico Pache wrote:
> > Add collapse_allowable_orders() to generalize THP order eligibility. The
> > function determines which THP orders are permitted based on collapse
> > context (khugepaged vs madv_collapse).
> >
> > This consolidates collapse configuration logic and provides a clean
> > interface for future mTHP collapse support where the orders may be
> > different.
>
> It would have been good to describe here that, for now, it only ever returns
> PMDs, and that it will be extended next.
>
> Logically, this patch belongs to #12, not #11 ... so seeing it before #11 was a bit
>
> ... and there, it is clear that we don't even want to know the orders?
>
> So can we just call this function
>
> "collapse_possible" and make it return a boolean?
Yeah agreed.
But I also don't love the naming, we now have thp_vma_allowable_orders(),
__thp_vma_allowable_orders(), and then collapse_allowable_orders() and we also
have 3 different ways of collapsing, one of which we call MADV_... COLLAPSE,
and the other khugepaged + fault-in too for laughs.
It's like a big circle of confusion.
And of course we call THP collapse 'collapse' in general, so :)
Anyway I'm fine with collapse_possible() so we can move on and then maybe
cleanup later.
Also - if I look at khugepaged.c I see thp_vma_allowable_orders() still used in
hugepage_vma_revalidate().
Wouldn't it be better then to do the abstraction once mTHP order checking is
properly introduced and change this also and have _every_ order check be
consistent in khugepaged.c?
>
> >
> > Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
> > Signed-off-by: Nico Pache <npache@redhat.com>
> > ---
> > mm/khugepaged.c | 15 ++++++++++++---
> > 1 file changed, 12 insertions(+), 3 deletions(-)
> >
> > diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> > index 4534025bc81d..64ceebc9d8a7 100644
> > --- a/mm/khugepaged.c
> > +++ b/mm/khugepaged.c
> > @@ -552,12 +552,21 @@ void __khugepaged_enter(struct mm_struct *mm)
> > wake_up_interruptible(&khugepaged_wait);
> > }
> >
> > +/* Check what orders are allowed based on the vma and collapse type */
I'd expand this comment to explain that it's explicitly for accounting for
whether mTHP is used, but that also argues for this to be moved to a later
commit as David says.
Otherwise the comment is useless.
> > +static unsigned long collapse_allowable_orders(struct vm_area_struct *vma,
> > + vm_flags_t vm_flags, enum tva_type tva_flags)
> > +{
> > + unsigned long orders = BIT(HPAGE_PMD_ORDER);
Could be a const also.
> > +
> > + return thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);
> > +}
> > +
> > void khugepaged_enter_vma(struct vm_area_struct *vma,
> > vm_flags_t vm_flags)
> > {
> > if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) &&
> > hugepage_pmd_enabled()) {
> > - if (thp_vma_allowable_order(vma, vm_flags, TVA_KHUGEPAGED, PMD_ORDER))
> > + if (collapse_allowable_orders(vma, vm_flags, TVA_KHUGEPAGED))
I hate that we separate out the VMA flags like this just for this case, but
that's something for a follow up probably from me as part of a VMA flags
conversion series...
> > __khugepaged_enter(vma->vm_mm);
> > }
> > }
> > @@ -2680,7 +2689,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> > cc->progress++;
> > break;
> > }
> > - if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_KHUGEPAGED, PMD_ORDER)) {
> > + if (!collapse_allowable_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> > cc->progress++;
> > continue;
> > }
> > @@ -2989,7 +2998,7 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
> > BUG_ON(vma->vm_start > start);
> > BUG_ON(vma->vm_end < end);
> >
> > - if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_FORCED_COLLAPSE, PMD_ORDER))
> > + if (!collapse_allowable_orders(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
> > return -EINVAL;
> >
> > cc = kmalloc_obj(*cc);
>
> Having a simple
>
> static bool collapse_possible(...)
> {
> return collapse_allowable_orders(...)
> }
>
> Would make the above slightly more readable.
Yup.
>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
>
> --
> Cheers,
>
> David
Cheers, Lorenzo
^ permalink raw reply
* Re: [PATCH v4 07/13] kho: add support for linked-block serialization
From: Pasha Tatashin @ 2026-06-01 14:37 UTC (permalink / raw)
To: Pratyush Yadav
Cc: Pasha Tatashin, linux-kselftest, rppt, shuah, akpm, linux-mm,
skhan, linux-doc, linux-kernel, corbet, dmatlack, kexec, skhawaja,
graf
In-Reply-To: <2vxzqzmqfkit.fsf@kernel.org>
On 06-01 15:38, Pratyush Yadav wrote:
> On Sat, May 30 2026, Pasha Tatashin wrote:
>
> > Introduce a linked-block serialization mechanism for state handover.
> >
> > Previously, LUO used contiguous memory blocks for serializing sessions
> > and files, which imposed limits on the total number of items that could
> > be preserved across a live update.
> >
> > This commit adds the infrastructure for a more flexible, block-based
> > approach where serialized data is stored in a chain of linked blocks.
> > This is a generic KHO serialization block infrastructure that can be
> > used by multiple subsystems.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> > ---
> > Documentation/core-api/kho/abi.rst | 5 +
> > Documentation/core-api/kho/index.rst | 11 +
> > MAINTAINERS | 1 +
> > include/linux/kho/abi/block.h | 56 ++++
> > include/linux/kho_block.h | 79 ++++++
> > kernel/liveupdate/Makefile | 1 +
> > kernel/liveupdate/kho_block.c | 384 +++++++++++++++++++++++++++
> > 7 files changed, 537 insertions(+)
> > create mode 100644 include/linux/kho/abi/block.h
> > create mode 100644 include/linux/kho_block.h
> > create mode 100644 kernel/liveupdate/kho_block.c
> >
> > diff --git a/Documentation/core-api/kho/abi.rst b/Documentation/core-api/kho/abi.rst
> > index 799d743105a6..edeb5b311963 100644
> > --- a/Documentation/core-api/kho/abi.rst
> > +++ b/Documentation/core-api/kho/abi.rst
> > @@ -28,6 +28,11 @@ KHO persistent memory tracker ABI
> > .. kernel-doc:: include/linux/kho/abi/kexec_handover.h
> > :doc: KHO persistent memory tracker
> >
> > +KHO serialization block ABI
> > +===========================
> > +
> > +.. kernel-doc:: include/linux/kho/abi/block.h
> > +
> > See Also
> > ========
> >
> > diff --git a/Documentation/core-api/kho/index.rst b/Documentation/core-api/kho/index.rst
> > index 0a2dee4f8e7d..320914a42178 100644
> > --- a/Documentation/core-api/kho/index.rst
> > +++ b/Documentation/core-api/kho/index.rst
> > @@ -83,6 +83,17 @@ Public API
> > .. kernel-doc:: kernel/liveupdate/kexec_handover.c
> > :export:
> >
> > +KHO Serialization Blocks API
> > +============================
> > +
> > +.. kernel-doc:: kernel/liveupdate/kho_block.c
> > + :doc: KHO Serialization Blocks
> > +
> > +.. kernel-doc:: include/linux/kho_block.h
> > +
> > +.. kernel-doc:: kernel/liveupdate/kho_block.c
> > + :internal:
> > +
> > See Also
> > ========
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 2fb1c75afd16..fd119b343e99 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -14194,6 +14194,7 @@ F: Documentation/admin-guide/mm/kho.rst
> > F: Documentation/core-api/kho/*
> > F: include/linux/kexec_handover.h
> > F: include/linux/kho/
> > +F: include/linux/kho_block.h
> > F: kernel/liveupdate/kexec_handover*
> > F: lib/test_kho.c
> > F: tools/testing/selftests/kho/
> > diff --git a/include/linux/kho/abi/block.h b/include/linux/kho/abi/block.h
> > new file mode 100644
> > index 000000000000..8641c20b379b
> > --- /dev/null
> > +++ b/include/linux/kho/abi/block.h
> > @@ -0,0 +1,56 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2026, Google LLC.
> > + * Pasha Tatashin <pasha.tatashin@soleen.com>
> > + */
> > +
> > +/**
> > + * DOC: KHO Serialization Blocks ABI
> > + *
> > + * Subsystems using the KHO Serialization Blocks framework rely on the stable
> > + * Application Binary Interface defined below to pass serialized state from a
> > + * pre-update kernel to a post-update kernel.
> > + *
> > + * This interface is a contract. Any modification to the structure fields,
> > + * compatible strings, or the layout of the `__packed` serialization
> > + * structures defined here constitutes a breaking change. Such changes require
> > + * incrementing the version number in the `KHO_BLOCK_ABI_COMPATIBLE` string to
> > + * prevent a new kernel from misinterpreting data from an old kernel.
> > + *
> > + * Changes are allowed provided the compatibility version is incremented;
> > + * however, backward/forward compatibility is only guaranteed for kernels
> > + * supporting the same ABI version.
> > + */
> > +
> > +#ifndef _LINUX_KHO_ABI_BLOCK_H
> > +#define _LINUX_KHO_ABI_BLOCK_H
> > +
> > +#include <asm/page.h>
> > +#include <linux/types.h>
> > +
> > +#define KHO_BLOCK_ABI_COMPATIBLE "kho-block-v1"
>
> During KHO radix development, I argued for a separate compatible for the
> radix tree, but at that time, we tied the radix tree to core KHO ABI.
> The argument being that all core KHO data structures belong to the KHO
> ABI set. I imagine this will be used by kho_vmalloc, so it will also be
> end up being used by a core KHO API.
>
> So, do we want separate ABI? I don't much have a preference myself, but
> I do think the compatible management will be a bit easier if this relied
> on KHO compatible, especially once kho_vmalloc starts using it.
I prefer to make them fine-grained, now that we are adding more and more
features: kho vmalloc, kho radix, and kho block should all have their
own compatibility strings. Furthermore, any components that depend on
them should include these compatibility strings in their own
compatibility strings, in the same manner I have done in this series.
>
> > +
> > +/**
> > + * KHO_BLOCK_SIZE - The size of each serialization block.
> > + *
> > + * This is defined as PAGE_SIZE. PAGE_SIZE is ABI compliant because live
> > + * update between kernels with different page sizes is not supported by KHO.
> > + */
> > +#define KHO_BLOCK_SIZE PAGE_SIZE
> > +
> > +/**
> > + * struct kho_block_header_ser - Header for the serialized data block.
> > + * @next: Physical address of the next struct kho_block_header_ser.
> > + * @count: The number of entries that immediately follow this header in the
> > + * memory block.
> > + *
> > + * This structure is located at the beginning of a block of physical memory
> > + * preserved across a kexec. It provides the necessary metadata to interpret
> > + * the array of entries that follow.
> > + */
> > +struct kho_block_header_ser {
> > + u64 next;
> > + u64 count;
> > +} __packed;
> > +
> > +#endif /* _LINUX_KHO_ABI_BLOCK_H */
> > diff --git a/include/linux/kho_block.h b/include/linux/kho_block.h
> > new file mode 100644
> > index 000000000000..5e6b87b1befa
> > --- /dev/null
> > +++ b/include/linux/kho_block.h
> > @@ -0,0 +1,79 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2026, Google LLC.
> > + * Pasha Tatashin <pasha.tatashin@soleen.com>
> > + */
> > +
> > +#ifndef _LINUX_KHO_BLOCK_H
> > +#define _LINUX_KHO_BLOCK_H
> > +
> > +#include <linux/list.h>
> > +#include <linux/types.h>
> > +#include <linux/kho/abi/block.h>
> > +
> > +/**
> > + * struct kho_block - Internal representation of a serialization block.
> > + * @list: List head for linking blocks in memory.
> > + * @ser: Pointer to the serialized header in preserved memory.
> > + */
> > +struct kho_block {
> > + struct list_head list;
> > + struct kho_block_header_ser *ser;
> > +};
> > +
> > +/**
> > + * struct kho_block_set - A set of blocks that belong to the same object.
> > + * @blocks: The list of serialization blocks (struct kho_block).
> > + * @nblocks: The number of allocated serialization blocks.
> > + * @head_pa: Physical address of the first block header.
> > + * @entry_size: The size of each entry in the blocks.
> > + * @count_per_block: The maximum number of entries each block can hold.
> > + * @incoming: True if this block set was restored from the previous kernel.
> > + */
> > +struct kho_block_set {
> > + struct list_head blocks;
> > + long nblocks;
> > + u64 head_pa;
> > + size_t entry_size;
>
> I think we should add the entry_size to kho_block_header_ser? I think it
> is a part of the ABI of the block set. If this changes, we cannot parse
> a block set with a different size. If a subsystem wants to change entry
> size, they create a new block set with different entry size, and then
> they bump their compatible version.
I have considered that, and we can certainly do it; however, I do not
see how it would affect the current implementation. If luo_file or
luo_session change entry_size, they must change the LUO compatibility
version, which would prevent LU from one kernel to the next. However,
for flexibility and future extensibility, I believe it would be useful
to add entry_size and block_size (which is PAGE_SIZE, but could be
larger for some users) to the header. This is more of a feature request
than an issue with the current series.
>
> > + u64 count_per_block;
> > + bool incoming;
> > +};
> > +
> > +/**
> > + * struct kho_block_it - Iterator for serializing entries into blocks.
> > + * @bs: The block set being iterated.
> > + * @block: The current block.
> > + * @i: The current entry index within @block.
> > + */
> > +struct kho_block_it {
> > + struct kho_block_set *bs;
> > + struct kho_block *block;
> > + u64 i;
> > +};
> > +
> > +/**
> > + * KHO_BLOCK_SET_INIT - Initialize a static kho_block_set.
> > + * @_name: Name of the kho_block_set variable.
> > + * @_entry_size: The size of each entry in the block set.
> > + */
> > +#define KHO_BLOCK_SET_INIT(_name, _entry_size) { \
> > + .blocks = LIST_HEAD_INIT((_name).blocks), \
> > + .entry_size = _entry_size, \
> > +}
> > +
> > +void kho_block_set_init(struct kho_block_set *bs, size_t entry_size);
> > +
> > +int kho_block_grow(struct kho_block_set *bs, u64 count);
> > +void kho_block_shrink(struct kho_block_set *bs, u64 count);
>
> These block management functions seem like internal details of the block
This is not so. The confusion here is that they must be allocated and
preserved at runtime as resources are registered/unregistered, while
these blocks are only used serialization phase,
These calls are more like notifiers that more files/sessions are created
removed, so we can adjust block count accordingly if necessary (allocate
preserver memory), and have them available durign
serialization/deserialization
> set API. Do we need to export them? I think users should not have to
> worry about block management. They should read, set, or clear entries
> using the iterators, and internally the block management should take of
> allocation or freeing. So here for example, I th
something is missing :-)
>
> > +
> > +int kho_block_restore(struct kho_block_set *bs, u64 head_pa);
> > +void kho_block_destroy(struct kho_block_set *bs);
>
> Nit: kho_block_set_{restore,destroy}()? At first glance I thought they
> manipulated a single block.
Makes sense.
>
> > +void kho_block_set_clear(struct kho_block_set *bs);
> > +
> > +void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs);
> > +void *kho_block_it_next(struct kho_block_it *it);
> > +void *kho_block_it_read(struct kho_block_it *it);
> > +void *kho_block_it_prev(struct kho_block_it *it);
> > +void kho_block_it_finalize(struct kho_block_it *it);
> > +
> > +#endif /* _LINUX_KHO_BLOCK_H */
> > diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
> > index d2f779cbe279..eec9d3ae07eb 100644
> > --- a/kernel/liveupdate/Makefile
> > +++ b/kernel/liveupdate/Makefile
> > @@ -1,6 +1,7 @@
> > # SPDX-License-Identifier: GPL-2.0
> >
> > luo-y := \
> > + kho_block.o \
> > luo_core.o \
> > luo_file.o \
> > luo_flb.o \
> > diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
> > new file mode 100644
> > index 000000000000..a4e650af946f
> > --- /dev/null
> > +++ b/kernel/liveupdate/kho_block.c
> > @@ -0,0 +1,384 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +/*
> > + * Copyright (c) 2026, Google LLC.
> > + * Pasha Tatashin <pasha.tatashin@soleen.com>
> > + */
> > +
> > +/**
> > + * DOC: KHO Serialization Blocks
> > + *
> > + * KHO provides a mechanism to preserve stateful data across a kexec handover
> > + * by serializing it into memory blocks. This file provides the common
> > + * infrastructure for managing these blocks.
> > + *
> > + * Each block consists of a header (struct kho_block_header_ser) followed by an
> > + * array of serialized entries. Multiple blocks are linked together via a
> > + * physical pointer in the header, forming a linked list that can be easily
> > + * traversed in both the current and the next kernel.
> > + */
> > +
> > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > +
> > +#include <linux/io.h>
> > +#include <linux/kexec_handover.h>
> > +#include <linux/kho/abi/block.h>
> > +#include <linux/kho_block.h>
> > +#include <linux/slab.h>
> > +
> > +/*
> > + * Safeguard limit for the number of serialization blocks. This is used to
> > + * prevent infinite loops and excessive memory allocation in case of memory
> > + * corruption in the preserved state.
> > + */
> > +#define KHO_MAX_BLOCKS 10000
> > +
> > +/**
> > + * kho_block_set_init - Initialize a block set.
> > + * @bs: The block set to initialize.
> > + * @entry_size: The size of each entry in the blocks.
> > + */
> > +void kho_block_set_init(struct kho_block_set *bs, size_t entry_size)
> > +{
> > + *bs = (struct kho_block_set)KHO_BLOCK_SET_INIT(*bs, entry_size);
> > +}
> > +
> > +static inline u64 kho_block_count_per_block(struct kho_block_set *bs)
> > +{
> > + if (unlikely(!bs->count_per_block)) {
> > + bs->count_per_block = (KHO_BLOCK_SIZE -
> > + sizeof(struct kho_block_header_ser)) /
> > + bs->entry_size;
> > + WARN_ON(!bs->count_per_block);
> > + }
> > + return bs->count_per_block;
> > +}
>
> This looks odd. I don't see a reason to calculate this lazily. Why not
> just do it when initializing the block set, in kho_block_set_init() or
> kho_block_restore()? And then use bs->count_per_block directly.
This allows for blocks to use static initilziation, I like static inits
:-)
>
> > +
> > +/* Free serialized data */
> > +static void kho_block_free_ser(struct kho_block_set *bs,
> > + struct kho_block_header_ser *ser)
> > +{
> > + if (bs->incoming)
> > + kho_restore_free(ser);
> > + else
> > + kho_unpreserve_free(ser);
> > +}
> > +
> > +static struct kho_block_header_ser *kho_block_alloc_ser(struct kho_block_set *bs)
> > +{
> > + WARN_ON(bs->incoming);
>
> WARN_ON_ONCE?
Sure
>
> > + return kho_alloc_preserve(KHO_BLOCK_SIZE);
> > +}
> > +
> > +static int kho_block_add(struct kho_block_set *bs,
> > + struct kho_block_header_ser *ser)
> > +{
> > + struct kho_block *block, *last;
> > +
> > + if (bs->nblocks >= KHO_MAX_BLOCKS)
> > + return -ENOSPC;
> > +
> > + block = kzalloc_obj(*block);
> > + if (!block)
> > + return -ENOMEM;
> > +
> > + block->ser = ser;
> > + last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
> > + list_add_tail(&block->list, &bs->blocks);
> > + bs->nblocks++;
> > +
> > + if (last)
> > + last->ser->next = virt_to_phys(ser);
> > + else
> > + bs->head_pa = virt_to_phys(ser);
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * kho_block_grow - Create a new block if the current capacity is reached.
> > + * @bs: The block set.
> > + * @count: The current number of entries.
> > + *
> > + * This function handles the dynamic expansion of a block set. It allocates
> > + * and links a new serialization block if the provided entry count matches
> > + * the current total capacity of the set.
> > + *
> > + * Return: 0 on success, or a negative errno on failure.
> > + */
> > +int kho_block_grow(struct kho_block_set *bs, u64 count)
> > +{
> > + struct kho_block_header_ser *ser;
> > + int err;
> > +
> > + if (WARN_ON(bs->incoming))
>
> WARN_ON_ONCE here too?
Sure
>
> > + return -EINVAL;
> > +
> > + if (count != bs->nblocks * kho_block_count_per_block(bs))
> > + return 0;
> > +
> > + ser = kho_block_alloc_ser(bs);
> > + if (IS_ERR(ser))
> > + return PTR_ERR(ser);
> > +
> > + err = kho_block_add(bs, ser);
> > + if (err) {
> > + kho_block_free_ser(bs, ser);
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * kho_block_shrink - Conditionally destroy the last block in a block set.
> > + * @bs: The block set.
> > + * @count: The current number of entries across all blocks.
> > + *
> > + * This function checks if the last block in the set is redundant based on the
> > + * total entry count and the capacity of the preceding blocks. If the entry
> > + * count can be accommodated by the blocks that come before the last one, the
> > + * last block is destroyed and removed from the set.
> > + */
> > +void kho_block_shrink(struct kho_block_set *bs, u64 count)
> > +{
> > + struct kho_block *last, *new_last;
> > +
> > + if (count > (bs->nblocks - 1) * kho_block_count_per_block(bs))
> > + return;
> > +
> > + if (list_empty(&bs->blocks))
> > + return;
> > +
> > + last = list_last_entry(&bs->blocks, struct kho_block, list);
> > + list_del(&last->list);
> > + bs->nblocks--;
> > + kho_block_free_ser(bs, last->ser);
> > + kfree(last);
> > +
> > + new_last = list_last_entry_or_null(&bs->blocks, struct kho_block, list);
> > + if (new_last)
> > + new_last->ser->next = 0;
> > + else
> > + bs->head_pa = 0;
> > +}
> > +
> > +/*
> > + * kho_cyclic_blocks_check - Check for cycles in a linked list of blocks.
> > + * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
> > + */
> > +static bool kho_cyclic_blocks_check(struct kho_block_set *bs)
> > +{
> > + struct kho_block_header_ser *fast;
> > + struct kho_block_header_ser *slow;
> > + int count = 0;
> > +
> > + fast = phys_to_virt(bs->head_pa);
> > + slow = fast;
> > +
> > + while (fast) {
> > + if (count++ >= KHO_MAX_BLOCKS) {
> > + pr_err("Linked list too long\n");
> > + return false;
> > + }
> > +
> > + if (!fast->next)
> > + break;
> > +
> > + fast = phys_to_virt(fast->next);
> > + if (!fast->next)
> > + break;
> > +
> > + fast = phys_to_virt(fast->next);
> > + slow = phys_to_virt(slow->next);
> > +
> > + if (slow == fast) {
> > + pr_err("Cyclic list detected\n");
>
> Heh, reminds me of the time I was practicing leetcode for interviews ;-)
:-)
>
> > + return false;
> > + }
> > + }
> > +
> > + return true;
> > +}
> > +
> > +/**
> > + * kho_block_restore - Restore a block set from a physical address.
> > + * @bs: The block set to restore.
> > + * @head_pa: Physical address of the first block header.
> > + *
> > + * Return: 0 on success, or a negative errno on failure.
> > + */
> > +int kho_block_restore(struct kho_block_set *bs, u64 head_pa)
> > +{
> > + struct kho_block_header_ser *ser;
> > + u64 next_pa = head_pa;
> > + int err;
> > +
> > + /* Restored block sets use size from the previous kernel */
> > + bs->incoming = true;
> > + if (!head_pa)
> > + return 0;
> > +
> > + bs->head_pa = head_pa;
> > + if (!kho_cyclic_blocks_check(bs)) {
> > + bs->head_pa = 0;
> > + return -EINVAL;
> > + }
> > +
> > + while (next_pa) {
> > + ser = phys_to_virt(next_pa);
> > + if (ser->count > kho_block_count_per_block(bs)) {
> > + pr_warn("Block contains too many entries: %llu\n",
> > + ser->count);
> > + err = -EINVAL;
> > + goto err_destroy;
> > + }
> > + err = kho_block_add(bs, ser);
> > + if (err)
> > + goto err_destroy;
> > + next_pa = ser->next;
> > + }
> > +
> > + return 0;
> > +
> > +err_destroy:
> > + kho_block_destroy(bs);
> > + return err;
> > +}
> > +
> > +/**
> > + * kho_block_destroy - Destroy all blocks in a block set.
> > + * @bs: The block set.
> > + */
> > +void kho_block_destroy(struct kho_block_set *bs)
> > +{
> > + u64 head_pa = bs->head_pa;
> > + struct kho_block *block;
> > +
> > + while (!list_empty(&bs->blocks)) {
> > + block = list_first_entry(&bs->blocks, struct kho_block, list);
> > + list_del(&block->list);
> > + kfree(block);
> > + }
>
> Nit:
>
> list_for_each_entry_safe(block, tmp, &bs->blocks, list) {
> list_del(&block->list);
> kfree(block);
> }
>
> is a bit more idiomatic (and IMO easier to read).
Sure
>
> > + bs->nblocks = 0;
> > + bs->head_pa = 0;
> > +
> > + while (head_pa) {
> > + struct kho_block_header_ser *ser = phys_to_virt(head_pa);
> > +
> > + head_pa = ser->next;
> > + kho_block_free_ser(bs, ser);
>
> Nit: also, can't you put this also in the previous loop? Something like:
>
> list_for_each_entry_safe(block, tmp, &bs->blocks, list) {
> list_del(&block->list);
> kho_block_free_ser(block->ser);
> kfree(block);
> }
We actually can't merge these into a single loop because of partial
restoration failures handling in kho_block_restore().
If kho_block_restore fails halfway through restoring a chain of blocks
(for example, if kho_block_add fails on block 3 of 5), we jump to the
err_destroy cleanup path which calls kho_block_destroy().
At this point:
- bs->blocks only contains the tracked blocks we successfully added
(blocks 1 and 2).
- bs->head_pa still points to the physical head of the entire 5-block
incoming chain.
But, this is a good place to add a comment.
> > + }
> > +}
> > +
> > +/**
> > + * kho_block_set_clear - Clear all serialized data in a block set.
> > + * @bs: The block set to clear.
> > + */
> > +void kho_block_set_clear(struct kho_block_set *bs)
> > +{
> > + struct kho_block *block;
> > +
> > + list_for_each_entry(block, &bs->blocks, list) {
> > + block->ser->count = 0;
> > + memset(block->ser + 1, 0, KHO_BLOCK_SIZE - sizeof(*block->ser));
> > + }
> > +}
> > +
> > +/**
> > + * kho_block_it_init - Initialize a block set iterator.
> > + * @it: The iterator to initialize.
> > + * @bs: The block set to iterate over.
> > + */
> > +void kho_block_it_init(struct kho_block_it *it, struct kho_block_set *bs)
> > +{
> > + it->bs = bs;
> > + it->block = list_first_entry_or_null(&bs->blocks, struct kho_block, list);
> > + it->i = 0;
> > +}
> > +
> > +/**
> > + * kho_block_it_next - Return the next entry slot in the block set.
> > + * @it: The block iterator.
> > + *
> > + * If the current block is full, it automatically advances to the next block
> > + * in the set.
> > + *
> > + * Return: A pointer to the next entry slot, or NULL if no more slots are
> > + * available.
> > + */
> > +void *kho_block_it_next(struct kho_block_it *it)
>
> The naming and documentation here are very confusing. This and
> kho_block_it_read() look pretty much identical, and their documentation
> also looks pretty much identical. There seems to be only one tiny
> difference: this function returns the slot while incrementing the block
> count.
>
> Can we do better something like kho_block_it_write_next(struct
> kho_block_it *it, void *entry) (size was specified when creating block
> set)? Yes, this results in a copy but does that matter that much?
>
> And if you really want to avoid copying, perhaps
> kho_block_it_add_entry()? Or something along the lines? To make it clear
> this is adding an entry to the block set.
>
> Also, make the intended usage clear in the documentation.
Sure, I will work on this. I also did not like the names, but could not
think of anything clearer.
>
> > +{
> > + if (!it->block)
> > + return NULL;
> > +
> > + if (it->i == kho_block_count_per_block(it->bs)) {
> > + it->block->ser->count = it->i;
> > + if (list_is_last(&it->block->list, &it->bs->blocks))
> > + return NULL;
> > + it->block = list_next_entry(it->block, list);
> > + it->i = 0;
> > + }
> > +
> > + return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
> > +}
> > +
> > +/**
> > + * kho_block_it_read - Return the next entry slot for reading.
> > + * @it: The block iterator.
> > + *
> > + * This function iterates through entries that were previously serialized,
> > + * respecting the count stored in each block's header.
> > + *
> > + * Return: A pointer to the next entry slot, or NULL if no more entries are
> > + * available.
> > + */
> > +void *kho_block_it_read(struct kho_block_it *it)
> > +{
> > + if (!it->block)
> > + return NULL;
> > +
> > + while (it->i == it->block->ser->count) {
>
> Hmm, the while loop suggests we can have blocks with zero count. Do you
> think we should detect those and error out instead? Since it doesn't
> really make sense to have a block with no entries.
This sounds reasonable.
>
> > + if (list_is_last(&it->block->list, &it->bs->blocks))
> > + return NULL;
> > + it->block = list_next_entry(it->block, list);
> > + it->i = 0;
> > + }
> > +
> > + return (void *)(it->block->ser + 1) + (it->i++ * it->bs->entry_size);
> > +}
> > +
> > +/**
> > + * kho_block_it_prev - Return the previous entry slot in the block set.
> > + * @it: The block iterator.
> > + *
> > + * If the current index is at the start of a block, it automatically moves to
> > + * the end of the previous block.
> > + *
> > + * Return: A pointer to the previous entry slot, or NULL if at the very
> > + * beginning of the block set.
> > + */
> > +void *kho_block_it_prev(struct kho_block_it *it)
> > +{
> > + if (!it->block)
> > + return NULL;
> > +
> > + if (it->i == 0) {
> > + if (list_is_first(&it->block->list, &it->bs->blocks))
> > + return NULL;
> > + it->block = list_prev_entry(it->block, list);
> > + it->i = kho_block_count_per_block(it->bs);
> > + }
> > +
> > + return (void *)(it->block->ser + 1) + (--it->i * it->bs->entry_size);
> > +}
> > +
> > +/**
> > + * kho_block_it_finalize - Finalize the current block by setting its entry count.
> > + * @it: The block iterator.
> > + */
> > +void kho_block_it_finalize(struct kho_block_it *it)
> > +{
> > + if (it->block)
> > + it->block->ser->count = it->i;
> > +}
>
> Doesn't kho_block_it_next() already do this when you add an entry? So
> this seems redundant.
It is not redundant because of how the final partially-fille block is handled.
kho_block_it_next() only writes the count into the block header when a block is completely full and it is advancing to the next one:
if (it->i == kho_block_count_per_block(it->bs)) {
it->block->ser->count = it->i;
...
But for the very last block in the set, it is usually only partially
filled (e.g., we write 10 entries into a block with a capacity of 64).
Since it->i never reaches the maximum capacity, kho_block_it_next()
never commits its count.
Pasha
^ permalink raw reply
* Re: [PATCH mm-unstable v18 10/14] mm/khugepaged: introduce collapse_allowable_orders helper function
From: David Hildenbrand (Arm) @ 2026-06-01 14:40 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: Nico Pache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
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, 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: <ah2UhZb9EYgYxj1F@lucifer>
On 6/1/26 16:35, Lorenzo Stoakes wrote:
> On Sun, May 31, 2026 at 10:18:16PM +0200, David Hildenbrand (Arm) wrote:
>> On 5/22/26 17:00, Nico Pache wrote:
>>> Add collapse_allowable_orders() to generalize THP order eligibility. The
>>> function determines which THP orders are permitted based on collapse
>>> context (khugepaged vs madv_collapse).
>>>
>>> This consolidates collapse configuration logic and provides a clean
>>> interface for future mTHP collapse support where the orders may be
>>> different.
>>
>> It would have been good to describe here that, for now, it only ever returns
>> PMDs, and that it will be extended next.
>>
>> Logically, this patch belongs to #12, not #11 ... so seeing it before #11 was a bit
>>
>> ... and there, it is clear that we don't even want to know the orders?
>>
>> So can we just call this function
>>
>> "collapse_possible" and make it return a boolean?
FWIW, I realized later that #11 has
enabled_orders = collapse_allowable_orders(vma, vma->vm_flags, tva_flags);
and forgot to delete that comment.
So we must have a variant (for #11) that returns the enabled orders.
>
> Yeah agreed.
>
> But I also don't love the naming, we now have thp_vma_allowable_orders(),
> __thp_vma_allowable_orders(), and then collapse_allowable_orders() and we also
> have 3 different ways of collapsing, one of which we call MADV_... COLLAPSE,
> and the other khugepaged + fault-in too for laughs.
>
> It's like a big circle of confusion.
>
> And of course we call THP collapse 'collapse' in general, so :)
>
> Anyway I'm fine with collapse_possible() so we can move on and then maybe
> cleanup later.
We could simply have
collapse_possible_orders()
and
collapse_possible()
the latter being a simple wrapper around collapse_possible_orders().
>
> Also - if I look at khugepaged.c I see thp_vma_allowable_orders() still used in
> hugepage_vma_revalidate().
>
> Wouldn't it be better then to do the abstraction once mTHP order checking is
> properly introduced and change this also and have _every_ order check be
> consistent in khugepaged.c?
That'd also be nice, if easily possible.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH v4 10/13] liveupdate: Remove limit on the number of files per session
From: Pasha Tatashin @ 2026-06-01 14:40 UTC (permalink / raw)
To: Pratyush Yadav
Cc: Pasha Tatashin, linux-kselftest, rppt, shuah, akpm, linux-mm,
skhan, linux-doc, linux-kernel, corbet, dmatlack, kexec, skhawaja,
graf
In-Reply-To: <2vxzbjdufirq.fsf@kernel.org>
On 06-01 16:16, Pratyush Yadav wrote:
> On Sat, May 30 2026, Pasha Tatashin wrote:
>
> > To remove the fixed limit on the number of preserved files per session,
> > transition the file metadata serialization from a single contiguous
> > memory block to a chain of linked blocks.
> >
> > Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> > ---
> > include/linux/kho/abi/luo.h | 13 +--
> > kernel/liveupdate/luo_file.c | 144 +++++++++++++++----------------
> > kernel/liveupdate/luo_internal.h | 6 +-
> > 3 files changed, 80 insertions(+), 83 deletions(-)
> >
> > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
> > index 79758d92ed5f..16df550ef143 100644
> > --- a/include/linux/kho/abi/luo.h
> > +++ b/include/linux/kho/abi/luo.h
> > @@ -35,8 +35,8 @@
> > *
> > * - struct luo_session_ser:
> > * Metadata for a single session, including its name and a physical pointer
> > - * to another preserved memory block containing an array of
> > - * `struct luo_file_ser` for all files in that session.
> > + * to the first `struct kho_block_header_ser` for all files in that session.
> > + * Multiple blocks are linked via the `next` field in the header.
> > *
> > * - struct luo_file_ser:
> > * Metadata for a single preserved file. Contains the `compatible` string to
> > @@ -65,7 +65,7 @@
> > * The LUO state is registered under this KHO entry name.
> > */
> > #define LUO_KHO_ENTRY_NAME "LUO"
> > -#define LUO_COMPAT_BASE "luo-v3"
> > +#define LUO_COMPAT_BASE "luo-v4"
> > #define LUO_ABI_COMPATIBLE LUO_COMPAT_BASE "-" KHO_BLOCK_ABI_COMPATIBLE
> > #define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
> >
> > @@ -103,9 +103,10 @@ struct luo_file_ser {
> >
> > /**
> > * struct luo_file_set_ser - Represents the serialized metadata for file set
> > - * @files: The physical address of a contiguous memory block that holds
> > - * the serialized state of files (array of luo_file_ser) in this file
> > - * set.
> > + * @files: The physical address of the first `struct kho_block_header_ser`.
> > + * This structure is the header for a block of memory containing
> > + * an array of `struct luo_file_ser` entries. Multiple blocks are
> > + * linked via the `next` field in the header.
> > * @count: The total number of files that were part of this session during
> > * serialization. Used for iteration and validation during
> > * restoration.
> > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> > index 9eec07a9e9fc..a445b1950ca7 100644
> > --- a/kernel/liveupdate/luo_file.c
> > +++ b/kernel/liveupdate/luo_file.c
> > @@ -118,11 +118,6 @@ static LIST_HEAD(luo_file_handler_list);
> > /* Keep track of files being preserved by LUO */
> > static DEFINE_XARRAY(luo_preserved_files);
> >
> > -/* 2 4K pages, give space for 128 files per file_set */
> > -#define LUO_FILE_PGCNT 2ul
> > -#define LUO_FILE_MAX \
> > - ((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser))
> > -
> > /**
> > * struct luo_file - Represents a single preserved file instance.
> > * @fh: Pointer to the &struct liveupdate_file_handler that manages
> > @@ -174,39 +169,6 @@ struct luo_file {
> > u64 token;
> > };
> >
> > -static int luo_alloc_files_mem(struct luo_file_set *file_set)
> > -{
> > - size_t size;
> > - void *mem;
> > -
> > - if (file_set->files)
> > - return 0;
> > -
> > - WARN_ON_ONCE(file_set->count);
> > -
> > - size = LUO_FILE_PGCNT << PAGE_SHIFT;
> > - mem = kho_alloc_preserve(size);
> > - if (IS_ERR(mem))
> > - return PTR_ERR(mem);
> > -
> > - file_set->files = mem;
> > -
> > - return 0;
> > -}
> > -
> > -static void luo_free_files_mem(struct luo_file_set *file_set)
> > -{
> > - /* If file_set has files, no need to free preservation memory */
> > - if (file_set->count)
> > - return;
> > -
> > - if (!file_set->files)
> > - return;
> > -
> > - kho_unpreserve_free(file_set->files);
> > - file_set->files = NULL;
> > -}
> > -
> > static unsigned long luo_get_id(struct liveupdate_file_handler *fh,
> > struct file *file)
> > {
> > @@ -276,16 +238,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> > if (luo_token_is_used(file_set, token))
> > return -EEXIST;
> >
> > - if (file_set->count == LUO_FILE_MAX)
> > - return -ENOSPC;
> > + err = kho_block_grow(&file_set->block_set, file_set->count);
> > + if (err)
> > + return err;
> >
> > file = fget(fd);
> > - if (!file)
> > - return -EBADF;
> > -
> > - err = luo_alloc_files_mem(file_set);
> > - if (err)
> > - goto err_fput;
> > + if (!file) {
> > + err = -EBADF;
> > + goto err_shrink;
> > + }
> >
> > err = -ENOENT;
> > down_read(&luo_register_rwlock);
> > @@ -300,7 +261,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> >
> > /* err is still -ENOENT if no handler was found */
> > if (err)
> > - goto err_free_files_mem;
> > + goto err_fput;
> >
> > err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
> > file, GFP_KERNEL);
> > @@ -343,10 +304,10 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
> > xa_erase(&luo_preserved_files, luo_get_id(fh, file));
> > err_module_put:
> > module_put(fh->ops->owner);
> > -err_free_files_mem:
> > - luo_free_files_mem(file_set);
> > err_fput:
> > fput(file);
> > +err_shrink:
> > + kho_block_shrink(&file_set->block_set, file_set->count);
> >
> > return err;
> > }
> > @@ -392,13 +353,14 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
> >
> > list_del(&luo_file->list);
> > file_set->count--;
> > + kho_block_shrink(&file_set->block_set, file_set->count);
> >
> > fput(luo_file->file);
> > mutex_destroy(&luo_file->mutex);
> > kfree(luo_file);
> > }
> >
> > - luo_free_files_mem(file_set);
> > + kho_block_destroy(&file_set->block_set);
> > }
> >
> > static int luo_file_freeze_one(struct luo_file_set *file_set,
> > @@ -454,7 +416,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> > luo_file_unfreeze_one(file_set, luo_file);
> > }
> >
> > - memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT);
> > + kho_block_set_clear(&file_set->block_set);
> > }
> >
> > /**
> > @@ -493,19 +455,23 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
> > int luo_file_freeze(struct luo_file_set *file_set,
> > struct luo_file_set_ser *file_set_ser)
> > {
> > - struct luo_file_ser *file_ser = file_set->files;
> > struct luo_file *luo_file;
> > + struct kho_block_it it;
> > int err;
> > - int i;
> >
> > if (!file_set->count)
> > return 0;
> >
> > - if (WARN_ON(!file_ser))
> > - return -EINVAL;
> > + kho_block_it_init(&it, &file_set->block_set);
> >
> > - i = 0;
> > list_for_each_entry(luo_file, &file_set->files_list, list) {
> > + struct luo_file_ser *file_ser = kho_block_it_next(&it);
> > +
> > + if (!file_ser) {
> > + err = -ENOSPC;
> > + goto err_unfreeze;
> > + }
>
> This should not fail normally, right? Since we pre-allocate the memory.
> Perhaps add a comment saying that?
>
> > +
> > err = luo_file_freeze_one(file_set, luo_file);
> > if (err < 0) {
> > pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
> > @@ -514,16 +480,21 @@ int luo_file_freeze(struct luo_file_set *file_set,
> > goto err_unfreeze;
> > }
> >
> > - strscpy(file_ser[i].compatible, luo_file->fh->compatible,
> > - sizeof(file_ser[i].compatible));
> > - file_ser[i].data = luo_file->serialized_data;
> > - file_ser[i].token = luo_file->token;
> > - i++;
> > + strscpy(file_ser->compatible, luo_file->fh->compatible,
> > + sizeof(file_ser->compatible));
> > + file_ser->data = luo_file->serialized_data;
> > + file_ser->token = luo_file->token;
> > }
> > + kho_block_it_finalize(&it);
> >
> > file_set_ser->count = file_set->count;
> > - if (file_set->files)
> > - file_set_ser->files = virt_to_phys(file_set->files);
> > + if (!list_empty(&file_set->block_set.blocks)) {
> > + struct kho_block *block;
> > +
> > + block = list_first_entry(&file_set->block_set.blocks,
> > + struct kho_block, list);
> > + file_set_ser->files = virt_to_phys(block->ser);
> > + }
>
> Please, add an API in KHO block to return the header physical address.
> Poking into the internals of the data structure like this is not a good
> idea.
SGTM
>
> I missed that patch 9 also does this. So please use that there too.
>
> >
> > return 0;
> >
> > @@ -741,14 +712,12 @@ int luo_file_finish(struct luo_file_set *file_set)
> > module_put(luo_file->fh->ops->owner);
> > list_del(&luo_file->list);
> > file_set->count--;
> > + kho_block_shrink(&file_set->block_set, file_set->count);
> > mutex_destroy(&luo_file->mutex);
> > kfree(luo_file);
> > }
> >
> > - if (file_set->files) {
> > - kho_restore_free(file_set->files);
> > - file_set->files = NULL;
> > - }
> > + kho_block_destroy(&file_set->block_set);
> >
> > return 0;
> > }
> > @@ -822,16 +791,18 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> > struct luo_file_set_ser *file_set_ser)
> > {
> > struct luo_file_ser *file_ser;
> > + struct kho_block_it it;
> > int err;
> > - u64 i;
> >
> > if (!file_set_ser->files) {
> > WARN_ON(file_set_ser->count);
> > return 0;
> > }
> >
> > - file_set->count = file_set_ser->count;
> > - file_set->files = phys_to_virt(file_set_ser->files);
> > + file_set->count = 0;
> > + err = kho_block_restore(&file_set->block_set, file_set_ser->files);
> > + if (err)
> > + return err;
> >
> > /*
> > * Note on error handling:
> > @@ -848,25 +819,50 @@ int luo_file_deserialize(struct luo_file_set *file_set,
> > * userspace to detect the failure and trigger a reboot, which will
> > * reliably reset devices and reclaim memory.
> > */
> > - file_ser = file_set->files;
> > - for (i = 0; i < file_set->count; i++) {
> > - err = luo_file_deserialize_one(file_set, &file_ser[i]);
> > + kho_block_it_init(&it, &file_set->block_set);
> > + while ((file_ser = kho_block_it_read(&it))) {
> > + err = luo_file_deserialize_one(file_set, file_ser);
> > if (err)
> > - return err;
> > + goto err_destroy_blocks;
> > + file_set->count++;
> > + }
> > +
> > + if (file_set->count != file_set_ser->count) {
> > + pr_warn("File count mismatch: expected %llu, found %llu\n",
> > + file_set_ser->count, file_set->count);
> > + err = -EINVAL;
> > + goto err_destroy_blocks;
> > }
> >
> > return 0;
> > +
> > +err_destroy_blocks:
> > + while (!list_empty(&file_set->files_list)) {
> > + struct luo_file *luo_file;
> > +
> > + luo_file = list_first_entry(&file_set->files_list,
> > + struct luo_file, list);
> > + list_del(&luo_file->list);
> > + module_put(luo_file->fh->ops->owner);
> > + mutex_destroy(&luo_file->mutex);
> > + kfree(luo_file);
> > + }
> > + file_set->count = 0;
> > + kho_block_destroy(&file_set->block_set);
> > + return err;
> > }
> >
> > void luo_file_set_init(struct luo_file_set *file_set)
> > {
> > INIT_LIST_HEAD(&file_set->files_list);
> > + kho_block_set_init(&file_set->block_set, sizeof(struct luo_file_ser));
> > }
> >
> > void luo_file_set_destroy(struct luo_file_set *file_set)
> > {
> > WARN_ON(file_set->count);
> > WARN_ON(!list_empty(&file_set->files_list));
> > + WARN_ON(!list_empty(&file_set->block_set.blocks));
>
> Here too.
Sure
>
> > }
> >
> > /**
> > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> > index ee18f9a11b91..64879ffe7378 100644
> > --- a/kernel/liveupdate/luo_internal.h
> > +++ b/kernel/liveupdate/luo_internal.h
> > @@ -10,6 +10,7 @@
> >
> > #include <linux/liveupdate.h>
> > #include <linux/uaccess.h>
> > +#include <linux/kho_block.h>
> >
> > struct luo_ucmd {
> > void __user *ubuffer;
> > @@ -44,14 +45,13 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd,
> > * struct luo_file_set - A set of files that belong to the same sessions.
> > * @files_list: An ordered list of files associated with this session, it is
> > * ordered by preservation time.
> > - * @files: The physically contiguous memory block that holds the serialized
> > - * state of files.
> > + * @block_set: The set of serialization blocks.
> > * @count: A counter tracking the number of files currently stored in the
> > * @files_list for this session.
> > */
> > struct luo_file_set {
> > struct list_head files_list;
> > - struct luo_file_ser *files;
> > + struct kho_block_set block_set;
> > u64 count;
> > };
>
> --
> Regards,
> Pratyush Yadav
^ permalink raw reply
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