* [PATCH v5 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device
From: tze.yee.ng @ 2026-07-15 6:28 UTC (permalink / raw)
To: Dinh Nguyen, linux-kernel, Guenter Roeck, Jonathan Corbet,
Shuah Khan, linux-hwmon, linux-doc
In-Reply-To: <cover.1784096224.git.tze.yee.ng@altera.com>
From: Tze Yee Ng <tze.yee.ng@altera.com>
Add asynchronous Stratix 10 service layer support for hardware monitor
temperature and voltage read commands in stratix10_svc_async_send() and
stratix10_svc_async_prepare_response().
Register a socfpga-hwmon platform device from the service layer driver
when hardware monitor support is enabled, similar to the RSU device.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com>
---
Changes in v5:
- No functional changes from v4
Changes in v3:
- No functional changes from v2
Changes in v2:
- Extend patch scope beyond async SMC support: register socfpga-hwmon
platform device from stratix10-svc when CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON
is enabled
- Follow RSU-style registration; RSU probe error handling is unchanged
- Add err_unregister_clients to unregister hwmon and RSU on populate failure
- Unregister hwmon platform device in stratix10-svc remove()
---
drivers/firmware/stratix10-svc.c | 46 ++++++++++++++++++--
include/linux/firmware/intel/stratix10-smc.h | 38 ++++++++++++++++
2 files changed, 81 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c
index 5e20057ee344..bb6c02cd1f81 100644
--- a/drivers/firmware/stratix10-svc.c
+++ b/drivers/firmware/stratix10-svc.c
@@ -45,6 +45,7 @@
/* stratix10 service layer clients */
#define STRATIX10_RSU "stratix10-rsu"
+#define SOCFPGA_HWMON "socfpga-hwmon"
/* Maximum number of SDM client IDs. */
#define MAX_SDM_CLIENT_IDS 16
@@ -104,9 +105,11 @@ struct stratix10_svc_chan;
/**
* struct stratix10_svc - svc private data
* @stratix10_svc_rsu: pointer to stratix10 RSU device
+ * @stratix10_svc_hwmon: pointer to stratix10 HWMON device
*/
struct stratix10_svc {
struct platform_device *stratix10_svc_rsu;
+ struct platform_device *stratix10_svc_hwmon;
};
/**
@@ -1329,6 +1332,14 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg,
args.a0 = INTEL_SIP_SMC_ASYNC_RSU_NOTIFY;
args.a2 = p_msg->arg[0];
break;
+ case COMMAND_HWMON_READTEMP:
+ args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READTEMP;
+ args.a2 = p_msg->arg[0];
+ break;
+ case COMMAND_HWMON_READVOLT:
+ args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READVOLT;
+ args.a2 = p_msg->arg[0];
+ break;
default:
dev_err(ctrl->dev, "Invalid command ,%d\n", p_msg->command);
ret = -EINVAL;
@@ -1422,6 +1433,10 @@ static int stratix10_svc_async_prepare_response(struct stratix10_svc_chan *chan,
*/
data->kaddr1 = (void *)&handle->res;
break;
+ case COMMAND_HWMON_READTEMP:
+ case COMMAND_HWMON_READVOLT:
+ data->kaddr1 = (void *)&handle->res.a2;
+ break;
default:
dev_alert(ctrl->dev, "Invalid command\n ,%d", p_msg->command);
@@ -2016,16 +2031,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
if (ret)
goto err_put_device;
+ if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) {
+ svc->stratix10_svc_hwmon =
+ platform_device_alloc(SOCFPGA_HWMON, 0);
+ if (!svc->stratix10_svc_hwmon) {
+ dev_err(dev, "failed to allocate %s device\n",
+ SOCFPGA_HWMON);
+ } else {
+ svc->stratix10_svc_hwmon->dev.parent = dev;
+
+ ret = platform_device_add(svc->stratix10_svc_hwmon);
+ if (ret) {
+ dev_err(dev, "failed to add %s device: %d\n",
+ SOCFPGA_HWMON, ret);
+ platform_device_put(svc->stratix10_svc_hwmon);
+ svc->stratix10_svc_hwmon = NULL;
+ }
+ }
+ }
+
ret = of_platform_default_populate(dev_of_node(dev), NULL, dev);
if (ret)
- goto err_unregister_rsu_dev;
+ goto err_unregister_clients;
pr_info("Intel Service Layer Driver Initialized\n");
return 0;
-err_unregister_rsu_dev:
- platform_device_unregister(svc->stratix10_svc_rsu);
+err_unregister_clients:
+ if (svc->stratix10_svc_hwmon)
+ platform_device_unregister(svc->stratix10_svc_hwmon);
+ if (svc->stratix10_svc_rsu)
+ platform_device_unregister(svc->stratix10_svc_rsu);
goto err_free_fifos;
err_put_device:
platform_device_put(svc->stratix10_svc_rsu);
@@ -2049,6 +2086,9 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev)
struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev);
struct stratix10_svc *svc = ctrl->svc;
+ if (svc->stratix10_svc_hwmon)
+ platform_device_unregister(svc->stratix10_svc_hwmon);
+
platform_device_unregister(svc->stratix10_svc_rsu);
stratix10_svc_async_exit(ctrl);
diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h
index 9224974fffc4..706176cc5c6a 100644
--- a/include/linux/firmware/intel/stratix10-smc.h
+++ b/include/linux/firmware/intel/stratix10-smc.h
@@ -701,6 +701,44 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE)
#define INTEL_SIP_SMC_ASYNC_POLL \
INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_POLL)
+/**
+ * Request INTEL_SIP_SMC_ASYNC_HWMON_READTEMP
+ * Async call to request temperature
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_ASYNC_HWMON_READTEMP
+ * a1 transaction job id
+ * a2 Temperature Channel
+ * a3-a17 not used
+ *
+ * Return status
+ * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED
+ * or INTEL_SIP_SMC_STATUS_BUSY
+ * a1-a17 not used
+ */
+#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP 0xE8
+#define INTEL_SIP_SMC_ASYNC_HWMON_READTEMP \
+ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP)
+
+/**
+ * Request INTEL_SIP_SMC_ASYNC_HWMON_READVOLT
+ * Async call to request voltage
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_ASYNC_HWMON_READVOLT
+ * a1 transaction job id
+ * a2 Voltage Channel
+ * a3-a17 not used
+ *
+ * Return status
+ * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED
+ * or INTEL_SIP_SMC_STATUS_BUSY
+ * a1-a17 not used
+ */
+#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT 0xE9
+#define INTEL_SIP_SMC_ASYNC_HWMON_READVOLT \
+ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT)
+
/**
* Request INTEL_SIP_SMC_ASYNC_RSU_GET_SPT
* Async call to get RSU SPT from SDM.
--
2.43.7
^ permalink raw reply related
* [PATCH v5 0/2] hwmon: add Altera SoC FPGA hardware monitoring support
From: tze.yee.ng @ 2026-07-15 6:28 UTC (permalink / raw)
To: Dinh Nguyen, linux-kernel, Guenter Roeck, Jonathan Corbet,
Shuah Khan, linux-hwmon, linux-doc
From: Tze Yee Ng <tze.yee.ng@altera.com>
This series adds hardware monitor support for Altera SoC FPGA devices.
Temperature and voltage sensors are accessed through the Stratix 10
service layer and Secure Device Manager (SDM).
Patch 1 adds async HWMON SMC support to stratix10-svc and registers the
socfpga-hwmon platform device.
Patch 2 adds the socfpga-hwmon driver, documentation, Kconfig, and
MAINTAINERS entry.
Changes in v5:
- Rebase on dinguyen/socfpga_svc_fixes_for_v7.2
- Address Sashiko review feedback on socfpga-hwmon (Patch 2):
- Poll async responses until HWMON_TIMEOUT instead of a fixed
3-iteration retry loop (~3 ms), fixing premature timeouts on
silicon
- Add MODULE_ALIAS("platform:socfpga-hwmon")
- No functional changes in Patch 1
Changes in v4:
- Address maintainer and review feedback on socfpga-hwmon (Patch 2):
- Register devm_add_action_or_reset() before
devm_hwmon_device_register_with_info() to fix devres teardown order
- Remove unreferenced completion and pre-poll
wait_for_completion_io_timeout() from async reads; poll directly
with a retry loop after async_send()
- No functional changes in Patch 1
Changes in v3:
- Address review feedback on socfpga-hwmon (Patch 2):
- Fix 16-bit Q8.8 temperature sign extension
- Drop unused async callback; pass NULL to stratix10_svc_async_send()
- Document and retain pre-poll wait (RSU pattern; firmware needs time
before async_poll())
- Align async poll retry behaviour with RSU
- Use uninterruptible wait_for_completion_timeout() for sync reads
- Handle -EINVAL and -EOPNOTSUPP when falling back to sync mode
- Defer SVC channel cleanup via devm_add_action_or_reset()
- No functional changes in Patch 1
Changes in v2:
- Drop altr,stratix10-hwmon DT binding and intel,stratix10-svc hwmon
child property
- Drop Stratix 10 SoCDK DTS hwmon node
- Register socfpga-hwmon from stratix10-svc (RSU-style)
- Replace DT channel parsing with hardcoded Stratix 10 and Agilex tables
- Rename driver/module to socfpga-hwmon
(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)
- Add Agilex channel support
- Fix SDM value conversion (Q8.8 degrees Celsius and Q16 volts to hwmon
millidegrees/millivolts)
- Improve sync-mode error handling via last_err
Previous version:
https://lore.kernel.org/all/20260709090153.21675-1-tze.yee.ng@altera.com/
Tze Yee Ng (2):
firmware: stratix10-svc: add async HWMON read commands and register
socfpga-hwmon device
hwmon: add Altera SoC FPGA hardware monitoring driver
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/socfpga-hwmon.rst | 34 ++
MAINTAINERS | 8 +
drivers/firmware/stratix10-svc.c | 46 +-
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/socfpga-hwmon.c | 579 +++++++++++++++++++
include/linux/firmware/intel/stratix10-smc.h | 38 ++
8 files changed, 714 insertions(+), 3 deletions(-)
create mode 100644 Documentation/hwmon/socfpga-hwmon.rst
create mode 100644 drivers/hwmon/socfpga-hwmon.c
--
2.43.7
^ permalink raw reply
* Re: [RFC PATCH 08/13] mm/kwatch: add hardware breakpoint backend
From: Jinchao Wang @ 2026-07-15 6:09 UTC (permalink / raw)
To: Steven Rostedt
Cc: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Masami Hiramatsu,
Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
Matthew Wilcox, linux-kernel, linux-mm, linux-trace-kernel,
linux-perf-users, linux-doc
In-Reply-To: <20260714171438.226faf7b@gandalf.local.home>
On 7/14/2026 5:14 PM, Steven Rostedt wrote:
> On Wed, 15 Jul 2026 02:32:06 +0800
> Jinchao Wang <wangjinchao600@gmail.com> wrote:
>
>> --- /dev/null
>> +++ b/include/trace/events/kwatch.h
>> @@ -0,0 +1,57 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +#undef TRACE_SYSTEM
>> +#define TRACE_SYSTEM kwatch
>> +
>> +#if !defined(_TRACE_KWATCH_H) || defined(TRACE_HEADER_MULTI_READ)
>> +#define _TRACE_KWATCH_H
>> +
>> +#include <linux/tracepoint.h>
>> +#include <linux/ptrace.h>
>> +
>> +#define KWATCH_STACK_DEPTH 8
>> +
>> +struct trace_seq;
>> +const char *kwatch_trace_print_stack(struct trace_seq *p,
>> + const unsigned long *stack,
>> + unsigned int nr);
>> +
>> +TRACE_EVENT(kwatch_hit,
>> + TP_PROTO(unsigned long ip, unsigned long sp, unsigned long addr,
>> + u64 time_ns,
>> + unsigned long *stack_entries, unsigned int stack_nr),
>> + TP_ARGS(ip, sp, addr, time_ns, stack_entries, stack_nr),
>> +
>> + TP_STRUCT__entry(
>> + __field(unsigned long, ip)
>> + __field(unsigned long, sp)
>> + __field(unsigned long, addr)
>> + __field(u64, time_ns)
>
> Move the time_ns to the first field, as unsigned long on 32 bit
> architectures is 4 bytes, and this will make 4 byte "hole" in the event.
Will fix in v2.>
>
>> + __field(unsigned int, stack_nr)
>
> Make stack_nr the last element for the same reason.
Will fix in v2.
>
>> + __array(unsigned long, stack, KWATCH_STACK_DEPTH)
>
> Make the above a dynamic array based on stack entries.
>
> __dynamic_array(unsigned long, stack, min_t(unsigned int, stack_nr,
> KWATCH_STACK_DEPTH);
Much better than always paying for the full depth - will convert to
__dynamic_array (and use __get_dynamic_array() in TP_fast_assign and
TP_printk as you showed) in v2.
Thank you for the review!
Thanks,
Jinchao
>
>
>> + ),
>> +
>> + TP_fast_assign(
>> + unsigned int i;
> unsigned long *stack = __get_dynamic_array(stack);
>> +
>> + __entry->ip = ip;
>> + __entry->sp = sp;
>> + __entry->addr = addr;
>> + __entry->time_ns = time_ns;
>> + __entry->stack_nr = min_t(unsigned int, stack_nr,
>> + KWATCH_STACK_DEPTH);
>> + for (i = 0; i < __entry->stack_nr; i++)
>> + __entry->stack[i] = stack_entries[i];
>
> stack[i] = stack_entries[i];
>
>> + ),
>> +
>> + TP_printk("KWatch HIT: time=%llu.%06lu ip=%pS addr=0x%lx%s",
>> + __entry->time_ns / 1000000000ULL,
>> + (unsigned long)((__entry->time_ns / 1000ULL) % 1000000ULL),
>> + (void *)__entry->ip, __entry->addr,
>> + kwatch_trace_print_stack(p, __entry->stack,
>
> kwatch_trace_print_stack(p, __get_dynamic_array(stack),
>
>> + __entry->stack_nr))
>> +);
>> +
>
> -- Steve
^ permalink raw reply
* Re: [PATCH 2/8] mm/khugepaged: extract young page check into collapse_is_young() helper
From: Nico Pache @ 2026-07-15 6:04 UTC (permalink / raw)
To: Baolin Wang
Cc: linux-doc, linux-kernel, linux-mm, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan
In-Reply-To: <62b2bd29-28d2-4440-a970-526dd5d5bbe0@linux.alibaba.com>
On Fri, Jul 10, 2026 at 1:41 AM Baolin Wang
<baolin.wang@linux.alibaba.com> wrote:
>
>
>
> On 7/6/26 11:44 PM, Nico Pache wrote:
> > The change deduplicates the "is this PTE young enough to count as
> > referenced" condition that was repeated in both
> > __collapse_huge_page_isolate() and collapse_scan_pmd(), extracting it into
> > a single inline helper function.
> >
> > Also move the comment and use it as the function header. While we are at
> > it, updated the comment to clarify that a young pte is a recently accessed
> > one.
> >
> > Signed-off-by: Nico Pache <npache@redhat.com>
> > ---
> > mm/khugepaged.c | 35 +++++++++++++++++++----------------
> > 1 file changed, 19 insertions(+), 16 deletions(-)
> >
> > diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> > index b3985b854e77..48b008a3c891 100644
> > --- a/mm/khugepaged.c
> > +++ b/mm/khugepaged.c
> > @@ -675,6 +675,23 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,
> > }
> > }
> >
> > +/*
> > + * collapse_is_young() - Check for enough young pte to justify collapsing
> > + *
> > + * If collapse was initiated by khugepaged, check that the page has been
> > + * recently accessed (young pte) to justify collapsing the page.
> > + *
> > + * Return: true if the page has been recently accessed (young pte).
> > + */
> > +static inline bool collapse_is_young(struct collapse_control *cc, pte_t pteval,
> > + struct folio *folio, struct vm_area_struct *vma, unsigned long addr)
> > +{
> > + return cc->is_khugepaged &&
> > + (pte_young(pteval) || folio_test_young(folio) ||
> > + folio_test_referenced(folio) ||
> > + mmu_notifier_test_young(vma->vm_mm, addr));
> > +}
>
> collapse_is_young() is somewhat confusing to me. Would using
> 'referenced' be a more appropriate name? collapse_folio_is_referenced()?
I went with collapse_is_referenced since it also checks the PTE not
just the folio.
>
> Also, it feels odd to put 'cc->is_khugepaged' in a helper whose purpose
> is to check whether a folio has been accessed. I think this helper
> should be more self-contained and focused solely on checking whether the
> folio was accessed.
While I don't wholeheartedly disagree, the point is to clean up the
code and abstract some of this logic away from the parent functions. I
prefer doing the khugepaged check inside this new helper because the
code is much cleaner.
Cheers,
-- Nico
>
> Just my 2 cents.
>
^ permalink raw reply
* Re: [PATCH v2 0/7] mm/khugepaged: several cleanups
From: Nico Pache @ 2026-07-15 5:59 UTC (permalink / raw)
To: Andrew Morton
Cc: linux-doc, linux-kernel, linux-mm, David Hildenbrand,
Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan
In-Reply-To: <20260714214121.090238f5f0f318f8d82cbaa2@linux-foundation.org>
On Tue, Jul 14, 2026 at 10:41 PM Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Tue, 14 Jul 2026 20:59:29 -0600 Nico Pache <npache@redhat.com> wrote:
>
> > The following changes stem from a number of reviews during my khugepaged
> > mTHP support series [1]. Some of these are minor code cleanups, issues or
> > reviews that we decided to deferred to a followup series, or in the case
> > of the more major patch of the series, changes [2] Lance Yang attempted
> > while my series was in-flight and we decided to wait till later to try.
> >
> > The first 3 patches introduce helper functions to increase code reuse and
> > readability. This includes a per-scan state clearing function, extracting
> > the young page check into a helper, and a count_collapse_event() function
> > to reduce a repetative pattern used across mTHP collapse.
> >
> > The 4th patch was the byproduct of me throwing Claude at all the
> > comments in khugepaged verifying and looking for any outdated info.
> >
> > The 5th patch is based on Lance Yang's commit series [2] trying to extract
> > the PTE state checking into a helper function. This required a bit of
> > rewriting due to differences after mTHP collapse was introduced. I also
> > took into account the changes requested during his patches review cycle.
> >
> > The remaining 2 patches were review points during my mTHP series that we
> > agreed can be deferred to a later series.
> >
> > Thank you to those whos reviews and work I leveraged to achieve these
> > cleanups.
>
> Sashiko seems to have a good point about [5/7]:
> https://sashiko.dev/#/patchset/20260715025941.1571316-1-npache@redhat.com
Ah whoops! When i sent the fixup for V1 I only did it in one location when it
should have been for both callers of collapse_check_pte(). Sorry about that.
Here is the fixup for the missing variable assignment
Thank you,
-- Nico
commit 52adeb9998fe84c1f997a3b3b6e98c93f9754c95
Author: Nico Pache <npache@redhat.com>
Date: Tue Jul 14 23:50:54 2026 -0600
fixup: always set the local folio after collapse_check_pte()
If we dont set the local folio to the result from collapse_check_pte()
we can end up with cases that the goto out will result in pointing to a
stale folio from the last successful PTE check.
Signed-off-by: Nico Pache <npache@redhat.com>
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 40125dcb4de9..e5e349d0662e 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1747,13 +1747,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
cc->progress++;
pte_check = collapse_check_pte(pteval, addr, &ctx);
+ folio = ctx.folio;
if (pte_check == PTE_CHECK_FAIL) {
result = ctx.result;
goto out_unmap;
}
if (pte_check == PTE_CHECK_CONTINUE)
continue;
- folio = ctx.folio;
/* Set bit for occupied pages */
__set_bit(i, cc->mthp_present_ptes);
>
^ permalink raw reply related
* [PATCH 3/3] tools/accounting: simplify 32-bit time_t overflow check in format_timespec()
From: wang.yaxin @ 2026-07-15 4:51 UTC (permalink / raw)
To: wang.yaxin
Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc,
xu.xin16
In-Reply-To: <20260715124740929HC7tDDb2SK3kRxbuPruHd@zte.com.cn>
From: Wang Yaxin <wang.yaxin@zte.com.cn>
Replace the Y2038 overflow guard in format_timespec() with a direct
narrowing truncation check ((long long)time_sec != ts->tv_sec), which
is both simpler and more robust across platforms. Also move the
time_sec assignment earlier to avoid duplication.
While at it, fix a minor alignment issue in delaytop.c by adding a
leading space to the output format string.
Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn>
---
tools/accounting/delaytop.c | 2 +-
tools/accounting/format_timespec.c | 12 +++++-------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c
index 1144ca325447..097b57887147 100644
--- a/tools/accounting/delaytop.c
+++ b/tools/accounting/delaytop.c
@@ -1099,7 +1099,7 @@ static void display_results(int psi_ret)
get_field_delay_values(&tasks[i], cfg.type_field, &avg_ms,
&max_ms, &max_ts);
- suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n",
+ suc &= BOOL_FPRINT(out, " %12.2f %12.2f %20s\n",
avg_ms, max_ms, format_timespec(&max_ts));
} else if (cfg.display_mode == MODE_MEMVERBOSE) {
suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE,
diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c
index 1dba50cac895..d7bfd307c00b 100644
--- a/tools/accounting/format_timespec.c
+++ b/tools/accounting/format_timespec.c
@@ -14,22 +14,20 @@ const char *format_timespec(const struct __kernel_timespec *ts)
{
static char buffer[32];
struct tm tm_info;
- time_t time_sec;
+ time_t time_sec = ts->tv_sec;
if (ts->tv_sec == 0 && ts->tv_nsec == 0)
return "N/A";
/*
* On 32-bit platforms time_t is 32-bit and cannot represent
- * dates beyond Y2038. The kernel timestamp is always 64-bit,
- * so reject values that would overflow.
+ * timestamps outside [INT32_MIN, INT32_MAX]. A 64-bit kernel
+ * timestamp that does not survive the narrowing truncation is
+ * rejected to avoid silent data corruption.
*/
- if (sizeof(time_t) < sizeof(ts->tv_sec) &&
- ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1))
+ if ((long long)time_sec != ts->tv_sec)
return "N/A";
- time_sec = ts->tv_sec;
-
if (!localtime_r(&time_sec, &tm_info))
return "N/A";
--
2.27.0
^ permalink raw reply related
* [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation
From: wang.yaxin @ 2026-07-15 4:50 UTC (permalink / raw)
To: wang.yaxin
Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc,
xu.xin16
In-Reply-To: <20260715124740929HC7tDDb2SK3kRxbuPruHd@zte.com.cn>
From: Wang Yaxin <wang.yaxin@zte.com.cn>
The same __kernel_timespec formatting logic existed independently in
both getdelays.c and delaytop.c with minor differences (strftime vs
snprintf, __kernel_time64_t vs time_t).
Create a shared format_timespec.c/h with a canonical implementation
(strftime + time_t), remove the static copies from both files, and
link both programs against the common object.
Also simplify the Makefile with a pattern rule for %.o and a static
pattern rule for the two programs that need format_timespec.o.
Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn>
---
tools/accounting/Makefile | 12 ++++++-
tools/accounting/delaytop.c | 39 ++---------------------
tools/accounting/format_timespec.c | 39 +++++++++++++++++++++++
tools/accounting/format_timespec.h | 9 ++++++
tools/accounting/getdelays.c | 32 ++-----------------
tools/include/uapi/linux/time_types.h | 46 +++++++++++++++++++++++++++
6 files changed, 110 insertions(+), 67 deletions(-)
create mode 100644 tools/accounting/format_timespec.c
create mode 100644 tools/accounting/format_timespec.h
create mode 100644 tools/include/uapi/linux/time_types.h
diff --git a/tools/accounting/Makefile b/tools/accounting/Makefile
index 007c0bb8cbbb..5224e03f5e94 100644
--- a/tools/accounting/Makefile
+++ b/tools/accounting/Makefile
@@ -3,8 +3,18 @@ CC := $(CROSS_COMPILE)gcc
CFLAGS := -I../include/uapi/
PROGS := getdelays procacct delaytop
+OBJS := format_timespec.o
all: $(PROGS)
+getdelays delaytop: %: %.o $(OBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+procacct: procacct.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
clean:
- rm -fr $(PROGS)
+ rm -fr $(PROGS) *.o
diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c
index f1d26ff98792..1144ca325447 100644
--- a/tools/accounting/delaytop.c
+++ b/tools/accounting/delaytop.c
@@ -44,6 +44,8 @@
#include <linux/cgroupstats.h>
#include <stddef.h>
+#include "format_timespec.h"
+
#define PSI_PATH "/proc/pressure"
#define PSI_CPU_PATH "/proc/pressure/cpu"
#define PSI_MEMORY_PATH "/proc/pressure/memory"
@@ -817,41 +819,6 @@ static double average_ms(unsigned long long total, unsigned long long count)
return (double)total / 1000000.0 / count;
}
-/*
- * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS)
- * Returns formatted string or "N/A" if timestamp is zero
- */
-static const char *format_kernel_timespec(struct __kernel_timespec *ts)
-{
- static char buffer[32];
- time_t time_sec;
- struct tm tm_info;
-
- /* Check if timestamp is zero (not set) */
- if (ts->tv_sec == 0 && ts->tv_nsec == 0)
- return "N/A";
-
- /* Avoid Y2038 truncation: check if timestamp fits in time_t on 32-bit platforms */
- if (sizeof(time_t) < sizeof(ts->tv_sec) &&
- ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1))
- return "N/A";
-
- time_sec = (time_t)ts->tv_sec;
-
- if (localtime_r(&time_sec, &tm_info) == NULL)
- return "N/A";
-
- snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d",
- tm_info.tm_year + 1900,
- tm_info.tm_mon + 1,
- tm_info.tm_mday,
- tm_info.tm_hour,
- tm_info.tm_min,
- tm_info.tm_sec);
-
- return buffer;
-}
-
/* Comparison function for sorting tasks */
static int compare_tasks(const void *a, const void *b)
{
@@ -1133,7 +1100,7 @@ static void display_results(int psi_ret)
&max_ms, &max_ts);
suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n",
- avg_ms, max_ms, format_kernel_timespec(&max_ts));
+ avg_ms, max_ms, format_timespec(&max_ts));
} else if (cfg.display_mode == MODE_MEMVERBOSE) {
suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE,
TASK_AVG(tasks[i], MEM),
diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c
new file mode 100644
index 000000000000..1dba50cac895
--- /dev/null
+++ b/tools/accounting/format_timespec.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Shared __kernel_timespec formatting for tools/accounting/
+ *
+ * Formats a __kernel_timespec to an ISO 8601 timestamp string
+ * (YYYY-MM-DDTHH:MM:SS). Returns "N/A" if the timestamp is zero
+ * or does not fit in time_t.
+ */
+#include <time.h>
+#include <linux/time_types.h>
+#include "format_timespec.h"
+
+const char *format_timespec(const struct __kernel_timespec *ts)
+{
+ static char buffer[32];
+ struct tm tm_info;
+ time_t time_sec;
+
+ if (ts->tv_sec == 0 && ts->tv_nsec == 0)
+ return "N/A";
+
+ /*
+ * On 32-bit platforms time_t is 32-bit and cannot represent
+ * dates beyond Y2038. The kernel timestamp is always 64-bit,
+ * so reject values that would overflow.
+ */
+ if (sizeof(time_t) < sizeof(ts->tv_sec) &&
+ ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1))
+ return "N/A";
+
+ time_sec = ts->tv_sec;
+
+ if (!localtime_r(&time_sec, &tm_info))
+ return "N/A";
+
+ strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info);
+
+ return buffer;
+}
diff --git a/tools/accounting/format_timespec.h b/tools/accounting/format_timespec.h
new file mode 100644
index 000000000000..960496ebf5c2
--- /dev/null
+++ b/tools/accounting/format_timespec.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef FORMAT_TIMESPEC_H
+#define FORMAT_TIMESPEC_H
+
+#include <linux/time_types.h>
+
+const char *format_timespec(const struct __kernel_timespec *ts);
+
+#endif
diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c
index 6ac30d4f96f7..d3193f670d89 100644
--- a/tools/accounting/getdelays.c
+++ b/tools/accounting/getdelays.c
@@ -30,6 +30,8 @@
#include <linux/taskstats.h>
#include <linux/cgroupstats.h>
+#include "format_timespec.h"
+
/*
* Generic macros for dealing with netlink sockets. Might be duplicated
* elsewhere. It is recommended that commercial grade applications use
@@ -221,36 +223,6 @@ static int get_family_id(int sd)
#define average_ms(t, c) (t / 1000000ULL / (c ? c : 1))
#define delay_ms(t) (t / 1000000ULL)
-/*
- * Format __kernel_timespec to human readable string (YYYY-MM-DD HH:MM:SS)
- * Returns formatted string or "N/A" if timestamp is zero
- */
-static const char *format_timespec(struct __kernel_timespec *ts)
-{
- static char buffer[32];
- struct tm tm_info;
- __kernel_time_t time_sec;
-
- /* Check if timestamp is zero (not set) */
- if (ts->tv_sec == 0 && ts->tv_nsec == 0)
- return "N/A";
-
- /* Avoid Y2038 truncation on 32-bit platforms */
- if (sizeof(time_sec) < sizeof(ts->tv_sec) &&
- ts->tv_sec > (__u64)((1ULL << (sizeof(time_sec) * 8 - 1)) - 1))
- return "N/A";
-
- time_sec = ts->tv_sec;
-
- /* Use thread-safe localtime_r */
- if (localtime_r(&time_sec, &tm_info) == NULL)
- return "N/A";
-
- strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info);
-
- return buffer;
-}
-
/*
* Version compatibility note:
* Field availability depends on taskstats version (t->version),
diff --git a/tools/include/uapi/linux/time_types.h b/tools/include/uapi/linux/time_types.h
new file mode 100644
index 000000000000..90347781af54
--- /dev/null
+++ b/tools/include/uapi/linux/time_types.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _TOOLS_UAPI_LINUX_TIME_TYPES_H
+#define _TOOLS_UAPI_LINUX_TIME_TYPES_H
+
+#include <linux/types.h>
+
+/*
+ * Copied from include/uapi/linux/time_types.h
+ *
+ * __kernel_time64_t is always 'long long' on all architectures.
+ * __kernel_long_t / __kernel_old_time_t is always 'long' on Linux.
+ * The concrete types are used here to avoid pulling in arch-specific
+ * kernel-private type definitions.
+ */
+
+struct __kernel_timespec {
+ long long tv_sec;
+ long long tv_nsec;
+};
+
+struct __kernel_itimerspec {
+ struct __kernel_timespec it_interval;
+ struct __kernel_timespec it_value;
+};
+
+struct __kernel_old_timeval {
+ long tv_sec;
+ long tv_usec;
+};
+
+struct __kernel_old_timespec {
+ long tv_sec;
+ long tv_nsec;
+};
+
+struct __kernel_old_itimerval {
+ struct __kernel_old_timeval it_interval;
+ struct __kernel_old_timeval it_value;
+};
+
+struct __kernel_sock_timeval {
+ __s64 tv_sec;
+ __s64 tv_usec;
+};
+
+#endif /* _TOOLS_UAPI_LINUX_TIME_TYPES_H */
--
2.27.0
^ permalink raw reply related
* [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum
From: wang.yaxin @ 2026-07-15 4:49 UTC (permalink / raw)
To: wang.yaxin
Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc,
xu.xin16
In-Reply-To: <20260715124740929HC7tDDb2SK3kRxbuPruHd@zte.com.cn>
From: Wang Yaxin <wang.yaxin@zte.com.cn>
Replace the 9 groups of (count, delay_total, delay_max, delay_max_ts)
named fields in struct task_info with a struct delay_metrics array
indexed by enum delay_type. This eliminates all unsafe pointer
arithmetic via offsetof() from compare_tasks(),
field_delay_max_and_ts(), and get_field_delay_values().
The struct field_desc now stores an enum delay_type index instead of
four separate unsigned long offset values.
Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn>
---
tools/accounting/delaytop.c | 220 +++++++++++++++---------------------
1 file changed, 88 insertions(+), 132 deletions(-)
diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c
index 1c40bb477320..f1d26ff98792 100644
--- a/tools/accounting/delaytop.c
+++ b/tools/accounting/delaytop.c
@@ -61,31 +61,51 @@
#define MAX_MSG_SIZE 1024
#define MAX_TASKS 1000
#define MAX_BUF_LEN 256
-#define SET_TASK_STAT(task_count, field) tasks[task_count].field = stats.field
#define BOOL_FPRINT(stream, fmt, ...) \
({ \
int ret = fprintf(stream, fmt, ##__VA_ARGS__); \
ret >= 0; \
})
-#define TASK_AVG(task, field) average_ms((task).field##_delay_total, (task).field##_count)
+#define TASK_AVG(task, type) \
+ average_ms((task).delays[DELAY_##type].delay_total, \
+ (task).delays[DELAY_##type].count)
#define PSI_LINE_FORMAT "%-12s %6.1f%%/%6.1f%%/%6.1f%%/%8llu(ms)\n"
#define DELAY_FMT_DEFAULT "%8.2f %8.2f %8.2f %8.2f\n"
#define DELAY_FMT_MEMVERBOSE "%8.2f %8.2f %8.2f %8.2f %8.2f %8.2f\n"
-#define SORT_FIELD(name, cmd, modes) \
- {#name, #cmd, \
- offsetof(struct task_info, name##_delay_total), \
- offsetof(struct task_info, name##_count), \
- offsetof(struct task_info, name##_delay_max), \
- offsetof(struct task_info, name##_delay_max_ts), \
- modes}
-#define SORT_FIELD_NO_MAX(name, cmd, modes) \
- {#name, #cmd, \
- offsetof(struct task_info, name##_delay_total), \
- offsetof(struct task_info, name##_count), \
- 0, \
- 0, \
- modes}
-#define END_FIELD {NULL, 0, 0, 0, 0, 0, 0}
+#define COPY_DELAY(task_idx, type, stats_prefix) \
+ do { \
+ tasks[task_idx].delays[DELAY_##type].count = \
+ stats.stats_prefix##_count; \
+ tasks[task_idx].delays[DELAY_##type].delay_total = \
+ stats.stats_prefix##_delay_total; \
+ tasks[task_idx].delays[DELAY_##type].delay_max = \
+ stats.stats_prefix##_delay_max; \
+ tasks[task_idx].delays[DELAY_##type].delay_max_ts = \
+ stats.stats_prefix##_delay_max_ts; \
+ } while (0)
+#define SORT_FIELD(name, cmd, type, modes, has_max) \
+ {#name, #cmd, type, modes, has_max}
+#define END_FIELD {NULL, 0, 0, 0, false}
+
+enum delay_type {
+ DELAY_CPU,
+ DELAY_BLKIO,
+ DELAY_SWAPIN,
+ DELAY_FREEPAGES,
+ DELAY_THRASHING,
+ DELAY_COMPACT,
+ DELAY_WPCOPY,
+ DELAY_IRQ,
+ DELAY_MEM,
+ NUM_DELAY_TYPES
+};
+
+struct delay_metrics {
+ unsigned long long count;
+ unsigned long long delay_total;
+ unsigned long long delay_max;
+ struct __kernel_timespec delay_max_ts;
+};
/* Display mode types */
#define MODE_TYPE_ALL (0xFFFFFFFF)
@@ -116,40 +136,7 @@ struct task_info {
int pid;
int tgid;
char command[TASK_COMM_LEN];
- unsigned long long cpu_count;
- unsigned long long cpu_delay_total;
- unsigned long long cpu_delay_max;
- struct __kernel_timespec cpu_delay_max_ts;
- unsigned long long blkio_count;
- unsigned long long blkio_delay_total;
- unsigned long long blkio_delay_max;
- struct __kernel_timespec blkio_delay_max_ts;
- unsigned long long swapin_count;
- unsigned long long swapin_delay_total;
- unsigned long long swapin_delay_max;
- struct __kernel_timespec swapin_delay_max_ts;
- unsigned long long freepages_count;
- unsigned long long freepages_delay_total;
- unsigned long long freepages_delay_max;
- struct __kernel_timespec freepages_delay_max_ts;
- unsigned long long thrashing_count;
- unsigned long long thrashing_delay_total;
- unsigned long long thrashing_delay_max;
- struct __kernel_timespec thrashing_delay_max_ts;
- unsigned long long compact_count;
- unsigned long long compact_delay_total;
- unsigned long long compact_delay_max;
- struct __kernel_timespec compact_delay_max_ts;
- unsigned long long wpcopy_count;
- unsigned long long wpcopy_delay_total;
- unsigned long long wpcopy_delay_max;
- struct __kernel_timespec wpcopy_delay_max_ts;
- unsigned long long irq_count;
- unsigned long long irq_delay_total;
- unsigned long long irq_delay_max;
- struct __kernel_timespec irq_delay_max_ts;
- unsigned long long mem_count;
- unsigned long long mem_delay_total;
+ struct delay_metrics delays[NUM_DELAY_TYPES];
};
/* Container statistics structure */
@@ -165,11 +152,9 @@ struct container_stats {
struct field_desc {
const char *name; /* Field name for cmdline argument */
const char *cmd_char; /* Interactive command */
- unsigned long total_offset; /* Offset of total delay in task_info */
- unsigned long count_offset; /* Offset of count in task_info */
- unsigned long max_offset; /* Offset of max delay in task_info */
- unsigned long max_ts_offset; /* Offset of max delay timestamp in task_info */
+ enum delay_type type; /* Index into task_info.delays[] */
size_t supported_modes; /* Supported display modes */
+ bool has_max; /* Whether this field has max/ts */
};
/* Program settings structure */
@@ -193,15 +178,15 @@ static int task_count;
static int running = 1;
static struct container_stats container_stats;
static const struct field_desc sort_fields[] = {
- SORT_FIELD(cpu, c, MODE_DEFAULT | MODE_TYPE),
- SORT_FIELD(blkio, i, MODE_DEFAULT | MODE_TYPE),
- SORT_FIELD(irq, q, MODE_DEFAULT | MODE_TYPE),
- SORT_FIELD_NO_MAX(mem, m, MODE_DEFAULT | MODE_MEMVERBOSE),
- SORT_FIELD(swapin, s, MODE_MEMVERBOSE | MODE_TYPE),
- SORT_FIELD(freepages, r, MODE_MEMVERBOSE | MODE_TYPE),
- SORT_FIELD(thrashing, t, MODE_MEMVERBOSE | MODE_TYPE),
- SORT_FIELD(compact, p, MODE_MEMVERBOSE | MODE_TYPE),
- SORT_FIELD(wpcopy, w, MODE_MEMVERBOSE | MODE_TYPE),
+ SORT_FIELD(cpu, c, DELAY_CPU, MODE_DEFAULT | MODE_TYPE, true),
+ SORT_FIELD(blkio, i, DELAY_BLKIO, MODE_DEFAULT | MODE_TYPE, true),
+ SORT_FIELD(irq, q, DELAY_IRQ, MODE_DEFAULT | MODE_TYPE, true),
+ SORT_FIELD(mem, m, DELAY_MEM, MODE_DEFAULT | MODE_MEMVERBOSE, false),
+ SORT_FIELD(swapin, s, DELAY_SWAPIN, MODE_MEMVERBOSE | MODE_TYPE, true),
+ SORT_FIELD(freepages, r, DELAY_FREEPAGES, MODE_MEMVERBOSE | MODE_TYPE, true),
+ SORT_FIELD(thrashing, t, DELAY_THRASHING, MODE_MEMVERBOSE | MODE_TYPE, true),
+ SORT_FIELD(compact, p, DELAY_COMPACT, MODE_MEMVERBOSE | MODE_TYPE, true),
+ SORT_FIELD(wpcopy, w, DELAY_WPCOPY, MODE_MEMVERBOSE | MODE_TYPE, true),
END_FIELD
};
static int sort_selected;
@@ -433,20 +418,20 @@ static void parse_args(int argc, char **argv)
/* Calculate average delay in milliseconds for overall memory */
static void set_mem_delay_total(struct task_info *t)
{
- t->mem_delay_total = t->swapin_delay_total +
- t->freepages_delay_total +
- t->thrashing_delay_total +
- t->compact_delay_total +
- t->wpcopy_delay_total;
+ t->delays[DELAY_MEM].delay_total = t->delays[DELAY_SWAPIN].delay_total +
+ t->delays[DELAY_FREEPAGES].delay_total +
+ t->delays[DELAY_THRASHING].delay_total +
+ t->delays[DELAY_COMPACT].delay_total +
+ t->delays[DELAY_WPCOPY].delay_total;
}
static void set_mem_count(struct task_info *t)
{
- t->mem_count = t->swapin_count +
- t->freepages_count +
- t->thrashing_count +
- t->compact_count +
- t->wpcopy_count;
+ t->delays[DELAY_MEM].count = t->delays[DELAY_SWAPIN].count +
+ t->delays[DELAY_FREEPAGES].count +
+ t->delays[DELAY_THRASHING].count +
+ t->delays[DELAY_COMPACT].count +
+ t->delays[DELAY_WPCOPY].count;
}
/* Create a raw netlink socket and bind */
@@ -758,38 +743,14 @@ static void fetch_and_fill_task_info(int pid, const char *comm)
strncpy(tasks[task_count].command, comm,
TASK_COMM_LEN - 1);
tasks[task_count].command[TASK_COMM_LEN - 1] = '\0';
- SET_TASK_STAT(task_count, cpu_count);
- SET_TASK_STAT(task_count, cpu_delay_total);
- SET_TASK_STAT(task_count, cpu_delay_max);
- SET_TASK_STAT(task_count, cpu_delay_max_ts);
- SET_TASK_STAT(task_count, blkio_count);
- SET_TASK_STAT(task_count, blkio_delay_total);
- SET_TASK_STAT(task_count, blkio_delay_max);
- SET_TASK_STAT(task_count, blkio_delay_max_ts);
- SET_TASK_STAT(task_count, swapin_count);
- SET_TASK_STAT(task_count, swapin_delay_total);
- SET_TASK_STAT(task_count, swapin_delay_max);
- SET_TASK_STAT(task_count, swapin_delay_max_ts);
- SET_TASK_STAT(task_count, freepages_count);
- SET_TASK_STAT(task_count, freepages_delay_total);
- SET_TASK_STAT(task_count, freepages_delay_max);
- SET_TASK_STAT(task_count, freepages_delay_max_ts);
- SET_TASK_STAT(task_count, thrashing_count);
- SET_TASK_STAT(task_count, thrashing_delay_total);
- SET_TASK_STAT(task_count, thrashing_delay_max);
- SET_TASK_STAT(task_count, thrashing_delay_max_ts);
- SET_TASK_STAT(task_count, compact_count);
- SET_TASK_STAT(task_count, compact_delay_total);
- SET_TASK_STAT(task_count, compact_delay_max);
- SET_TASK_STAT(task_count, compact_delay_max_ts);
- SET_TASK_STAT(task_count, wpcopy_count);
- SET_TASK_STAT(task_count, wpcopy_delay_total);
- SET_TASK_STAT(task_count, wpcopy_delay_max);
- SET_TASK_STAT(task_count, wpcopy_delay_max_ts);
- SET_TASK_STAT(task_count, irq_count);
- SET_TASK_STAT(task_count, irq_delay_total);
- SET_TASK_STAT(task_count, irq_delay_max);
- SET_TASK_STAT(task_count, irq_delay_max_ts);
+ COPY_DELAY(task_count, CPU, cpu);
+ COPY_DELAY(task_count, BLKIO, blkio);
+ COPY_DELAY(task_count, SWAPIN, swapin);
+ COPY_DELAY(task_count, FREEPAGES, freepages);
+ COPY_DELAY(task_count, THRASHING, thrashing);
+ COPY_DELAY(task_count, COMPACT, compact);
+ COPY_DELAY(task_count, WPCOPY, wpcopy);
+ COPY_DELAY(task_count, IRQ, irq);
set_mem_count(&tasks[task_count]);
set_mem_delay_total(&tasks[task_count]);
task_count++;
@@ -912,10 +873,10 @@ static int compare_tasks(const void *a, const void *b)
return 0;
}
- total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset);
- total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset);
- count1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->count_offset);
- count2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->count_offset);
+ total1 = t1->delays[cfg.sort_field->type].delay_total;
+ total2 = t2->delays[cfg.sort_field->type].delay_total;
+ count1 = t1->delays[cfg.sort_field->type].count;
+ count2 = t2->delays[cfg.sort_field->type].count;
avg1 = average_ms(total1, count1);
avg2 = average_ms(total2, count2);
@@ -929,22 +890,17 @@ static int compare_tasks(const void *a, const void *b)
static void field_delay_max_and_ts(const struct task_info *task, const struct field_desc *field,
unsigned long long *max_ns, struct __kernel_timespec *max_ts)
{
- if (!field || !field->max_offset) {
+ if (!field || !field->has_max) {
*max_ns = 0;
if (max_ts)
memset(max_ts, 0, sizeof(*max_ts));
return;
}
- *max_ns = *(unsigned long long *)((char *)task + field->max_offset);
+ *max_ns = task->delays[field->type].delay_max;
- if (max_ts) {
- if (field->max_ts_offset)
- *max_ts = *(struct __kernel_timespec *)((char *)task +
- field->max_ts_offset);
- else
- memset(max_ts, 0, sizeof(*max_ts));
- }
+ if (max_ts)
+ *max_ts = task->delays[field->type].delay_max_ts;
}
/* Get delay values for a specific field */
@@ -954,15 +910,15 @@ static void get_field_delay_values(const struct task_info *task, const struct fi
{
unsigned long long total, count, max;
- if (!field || !field->max_offset) {
+ if (!field) {
*avg_ms = 0;
*max_ms = 0;
memset(max_ts, 0, sizeof(*max_ts));
return;
}
- total = *(unsigned long long *)((char *)task + field->total_offset);
- count = *(unsigned long long *)((char *)task + field->count_offset);
+ total = task->delays[field->type].delay_total;
+ count = task->delays[field->type].count;
*avg_ms = average_ms(total, count);
field_delay_max_and_ts(task, field, &max, max_ts);
@@ -1180,18 +1136,18 @@ static void display_results(int psi_ret)
avg_ms, max_ms, format_kernel_timespec(&max_ts));
} else if (cfg.display_mode == MODE_MEMVERBOSE) {
suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE,
- TASK_AVG(tasks[i], mem),
- TASK_AVG(tasks[i], swapin),
- TASK_AVG(tasks[i], freepages),
- TASK_AVG(tasks[i], thrashing),
- TASK_AVG(tasks[i], compact),
- TASK_AVG(tasks[i], wpcopy));
+ TASK_AVG(tasks[i], MEM),
+ TASK_AVG(tasks[i], SWAPIN),
+ TASK_AVG(tasks[i], FREEPAGES),
+ TASK_AVG(tasks[i], THRASHING),
+ TASK_AVG(tasks[i], COMPACT),
+ TASK_AVG(tasks[i], WPCOPY));
} else {
suc &= BOOL_FPRINT(out, DELAY_FMT_DEFAULT,
- TASK_AVG(tasks[i], cpu),
- TASK_AVG(tasks[i], blkio),
- TASK_AVG(tasks[i], irq),
- TASK_AVG(tasks[i], mem));
+ TASK_AVG(tasks[i], CPU),
+ TASK_AVG(tasks[i], BLKIO),
+ TASK_AVG(tasks[i], IRQ),
+ TASK_AVG(tasks[i], MEM));
}
}
--
2.27.0
^ permalink raw reply related
* [PATCH v2 0/3] tools/accounting: refactor delay fields and share format_timespec()
From: wang.yaxin @ 2026-07-15 4:47 UTC (permalink / raw)
To: akpm, fan.yu9, yang.yang29
Cc: corbet, linux-kernel, linux-doc, xu.xin16, wang.yaxin
From: Wang Yaxin <wang.yaxin@zte.com.cn>
- Convert per-field delay members in struct task_info to an array indexed
by enum delay_type, eliminating offsetof() pointer arithmetic.
- Factor out a common format_timespec() implementation shared by getdelays
and delaytop, using strftime for cleaner timestamp formatting.
- Replace the complex sizeof/ULL/shift Y2038 guard with a direct narrowing
truncation check ((long long)time_sec != ts->tv_sec).
Change Log
==========
v1->v2:
Only update patch 2/3 according to the suggestion:
https://sashiko.dev/#/patchset/20260711173112482SCQEM08VED2PT1pxUYOXk@zte.com.cn
1. tools/include/uapi/linux/time_types.h: expand stub to full set of
6 structs from kernel UAPI to avoid shadowing system header
2. tools/accounting/Makefile: add $(LDFLAGS), $(LDLIBS), $(CPPFLAGS)
to restore compatibility with standard build variables
Wang Yaxin (3):
delaytop: refactor repetitive delay fields into array with enum
tools/accounting: factor out shared format_timespec() implementation
tools/accounting: simplify 32-bit time_t overflow check in
format_timespec()
tools/accounting/Makefile | 12 +-
tools/accounting/delaytop.c | 261 +++++++++-----------------
tools/accounting/format_timespec.c | 37 ++++
tools/accounting/format_timespec.h | 9 +
tools/accounting/getdelays.c | 32 +---
tools/include/uapi/linux/time_types.h | 46 +++++
6 files changed, 197 insertions(+), 200 deletions(-)
create mode 100644 tools/accounting/format_timespec.c
create mode 100644 tools/accounting/format_timespec.h
create mode 100644 tools/include/uapi/linux/time_types.h
--
2.27.0
^ permalink raw reply
* Re: [PATCH v2 0/7] mm/khugepaged: several cleanups
From: Andrew Morton @ 2026-07-15 4:41 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, David Hildenbrand,
Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
On Tue, 14 Jul 2026 20:59:29 -0600 Nico Pache <npache@redhat.com> wrote:
> The following changes stem from a number of reviews during my khugepaged
> mTHP support series [1]. Some of these are minor code cleanups, issues or
> reviews that we decided to deferred to a followup series, or in the case
> of the more major patch of the series, changes [2] Lance Yang attempted
> while my series was in-flight and we decided to wait till later to try.
>
> The first 3 patches introduce helper functions to increase code reuse and
> readability. This includes a per-scan state clearing function, extracting
> the young page check into a helper, and a count_collapse_event() function
> to reduce a repetative pattern used across mTHP collapse.
>
> The 4th patch was the byproduct of me throwing Claude at all the
> comments in khugepaged verifying and looking for any outdated info.
>
> The 5th patch is based on Lance Yang's commit series [2] trying to extract
> the PTE state checking into a helper function. This required a bit of
> rewriting due to differences after mTHP collapse was introduced. I also
> took into account the changes requested during his patches review cycle.
>
> The remaining 2 patches were review points during my mTHP series that we
> agreed can be deferred to a later series.
>
> Thank you to those whos reviews and work I leveraged to achieve these
> cleanups.
Sashiko seems to have a good point about [5/7]:
https://sashiko.dev/#/patchset/20260715025941.1571316-1-npache@redhat.com
^ permalink raw reply
* Re: [PATCH v4 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver
From: NG, TZE YEE @ 2026-07-15 3:44 UTC (permalink / raw)
To: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org,
Guenter Roeck, Jonathan Corbet, Shuah Khan,
linux-doc@vger.kernel.org
Cc: Dinh Nguyen
In-Reply-To: <20260709091502.2EB2A1F000E9@smtp.kernel.org>
On 9/7/2026 5:15 pm, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] The asynchronous read path times out prematurely and frees the SMC transaction ID while the Secure Device Manager (SDM) may still be processing the request, causing ID reuse and state confusion.
Agreed. The async poll loop was limited to three iterations (~3 ms),
while the sync path uses HWMON_TIMEOUT (2 s). That could call
stratix10_svc_async_done() and recycle the job ID before SDM finished.
In v5, we will poll until jiffies + HWMON_TIMEOUT before declaring
timeout and calling async_done(), matching the sync path timeout.
> - [Medium] Missing MODULE_ALIAS prevents automatic module loading.
Agreed. In v5, We will add:
MODULE_ALIAS("platform:socfpga-hwmon");
so the module auto-loads when stratix10-svc registers the platform
device, consistent with other parent-registered hwmon drivers (e.g.
raspberrypi-hwmon).
> - [Medium] Voltage readings lack SDM error code validation, leading to massive bogus voltage values being exported to userspace upon sensor error.
We checked the SDM specification: value-field error codes (ETEMP_*) are
defined for temperature only, not for voltage. Voltage failures are
reported through the SVC/SDM command status, which the driver already
checks:
Async: data.status in socfpga_hwmon_async_read() before using the value
Sync: data->status == BIT(SVC_STATUS_OK) in the receive_cb before
storing the value
> --
>
> --- Patch [2]: [PATCH v4 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver ---
> commit 356596525564df534eb70c8e10fde9c433f2dafd
> Author: Tze Yee Ng <tze.yee.ng@altera.com>
> Subject: hwmon: add Altera SoC FPGA hardware monitoring driver
>
> Adds a hardware monitor driver for Altera SoC FPGA devices using the
> Stratix 10 service layer. Sensor channels are selected based on the
> service layer compatible string.
>
>> diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c
>> new file mode 100644
>> index 0000000000000..1cd3a06fec42b
>> --- /dev/null
>> +++ b/drivers/hwmon/socfpga-hwmon.c
>
> [ ... ]
>
>> +static int socfpga_hwmon_async_read(struct device *dev,
>> + enum hwmon_sensor_types type,
>> + struct stratix10_svc_client_msg *msg)
>> +{
>> + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev);
>> + struct stratix10_svc_cb_data data = {};
>> + void *handle = NULL;
>> + int status, index, ret;
>
> [ ... ]
>
>> + ret = -ETIMEDOUT;
>> + for (index = 0; index < HWMON_ASYNC_MSG_RETRY; index++) {
>> + status = stratix10_svc_async_poll(priv->chan, handle, &data);
>
> [Severity: High]
> The synchronous path uses a 2000ms timeout, but this loop times out after just
> 3 iterations (with a 1ms sleep), allowing only ~3ms. Is this polling duration
> sufficient for the worst-case response time from the Secure Device Manager
> (SDM)?
> >> + if (status == -EAGAIN) {
>> + dev_dbg(dev, "Async message is still in progress\n");
>> + } else if (status < 0) {
>> + dev_alert(dev, "Failed to poll async message: %d\n", status);
>> + ret = -ETIMEDOUT;
>> + } else if (status == 0) {
>> + ret = 0;
>> + break;
>> + }
>> + msleep(HWMON_RETRY_SLEEP_MS);
>> + }
>> +
>> + if (ret) {
>> + dev_err(dev, "Failed to get async response\n");
>> + goto done;
>> + }
>> +
>> + if (data.status) {
>> + dev_err(dev, "%s returned 0x%x from SDM\n", __func__,
>> + data.status);
>> + ret = -EFAULT;
>> + goto done;
>> + }
>> +
>> + if (type == hwmon_temp)
>> + priv->temperature = (u32)*(unsigned long *)data.kaddr1;
>> + else
>> + priv->voltage = (u32)*(unsigned long *)data.kaddr1;
>> +
>> + ret = 0;
>> +
>> +done:
>> + stratix10_svc_async_done(priv->chan, handle);
>
> [Severity: High]
> If the async read times out, calling stratix10_svc_async_done() frees the
> transaction ID immediately.
>
> Can this cause the ID to be reused for a new request while the SDM is still
> processing the original delayed request, violating the SMC protocol and
> potentially causing corrupted data to be returned?
>
>> + return ret;
>> +}
>
> [ ... ]
>
>> +static int socfpga_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
>> + u32 attr, int chan, long *val)
>> +{
>> + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev);
>> + struct stratix10_svc_client_msg msg = {0};
>> + int ret;
>
> [ ... ]
>
>> + if (type == hwmon_temp)
>> + ret = socfpga_hwmon_parse_temp(val, priv->temperature);
>> + else
>> + /* Convert Q16 volts to millivolts for hwmon. */
>> + *val = (long)priv->voltage * SOCFPGA_HWMON_VOLT_MV_SCALE /
>> + SOCFPGA_HWMON_VOLT_FRAC_DIV;
>
> [Severity: Medium]
> The temperature reading path explicitly validates against generic SDM error
> codes (like ETEMP_INACTIVE or 0x80000000U).
>
> Since voltage readings lack this check, will a sensor error code here be
> blindly converted and exported as a massive bogus voltage value to userspace
> instead of returning a proper Linux error code?
>
>> + return ret;
>> +}
>
> [ ... ]
>
>> +static struct platform_driver socfpga_hwmon_driver = {
>> + .probe = socfpga_hwmon_probe,
>> + .driver = {
>> + .name = "socfpga-hwmon",
>> + },
>> +};
>
> [Severity: Medium]
> Since this driver binds to a platform device dynamically registered by the
> service layer (and lacks an id_table or of_match_table), is a MODULE_ALIAS
> declaration (like MODULE_ALIAS("platform:socfpga-hwmon")) needed here so that
> udev can automatically load the module when the device is registered?
>
>> +module_platform_driver(socfpga_hwmon_driver);
>> +
>> +MODULE_AUTHOR("Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>");
>> +MODULE_AUTHOR("Tze Yee Ng <tze.yee.ng@altera.com>");
>> +MODULE_DESCRIPTION("Altera SoC FPGA hardware monitoring driver");
>> +MODULE_LICENSE("GPL");
>
^ permalink raw reply
* Re: [PATCH v6 0/2] PM: dpm_watchdog: Improve DPM watchdog configurability
From: Tzung-Bi Shih @ 2026-07-15 3:38 UTC (permalink / raw)
To: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich
Cc: Shuah Khan, Pavel Machek, Len Brown, linux-doc, linux-kernel,
linux-pm, driver-core, tfiga, senozhatsky, Randy Dunlap
In-Reply-To: <20260708043317.2980098-1-tzungbi@kernel.org>
On Wed, Jul 08, 2026 at 04:33:15AM +0000, Tzung-Bi Shih wrote:
> This series improves the configurability of the DPM watchdog.
>
> Currently, the DPM watchdog is always enabled if compiled in. Also, the
> module parameters defined in drivers/base/power/main.c use the generic
> and non-descriptive "main" prefix.
>
> This series addresses these limitations.
>
> Patch 1 renames the module parameter prefix for
> drivers/base/power/main.c from "main" to "pm".
>
> Patch 2 introduces the "dpm_watchdog_enabled" module parameter to allow
> enabling/disabling the watchdog at boot time and runtime. It also adds
> CONFIG_DPM_WATCHDOG_ENABLED to set the default value of the module
> parameter at compile time.
>
> The primary motivation for this configurability revolves around Android
> GKI (Generic Kernel Image). We want to enable CONFIG_DPM_WATCHDOG in
> the GKI so the feature is available. However, because the GKI is shared
> across many different devices, we don't want to inadvertently affect
> devices that are unaware of this feature. This provides a way to
> compile it in, but keep it disabled by default for those devices via the
> kernel command line or module parameters.
>
> To maintain backward compatibility, CONFIG_DPM_WATCHDOG_ENABLED relies
> on `default y`. Previously, the DPM watchdog was always active if
> CONFIG_DPM_WATCHDOG was set. Defaulting this new option to 'y' ensures
> that the behavior remains unchanged for existing users and defconfigs
> when they upgrade.
> ---
> v6:
> - Change the prefix "pm_sleep" -> "pm".
>
> v5: https://lore.kernel.org/all/20260701045640.3130090-1-tzungbi@kernel.org
> - Rebase to v7.2-rc1.
> - Fix Signed-off-by lines.
Since there was no further feedback on v5, I'm sending out v6. Any feedback
would be appreciated.
^ permalink raw reply
* [PATCH v2 7/7] mm: Documentation: clarify where the mTHP stats live
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Baolin Wang, Lorenzo Stoakes, Andrew Morton,
David Hildenbrand, Zi Yan, Liam R. Howlett, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Usama Arif, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
The note about khugepaged counters references /proc/vmstat for the PMD
case, but never mentions where the mTHPs stats can be found
(i.e.: /sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/stats/)
Add a small addition to this section for clarity.
Also fix a missing period while we are at it.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Suggested-by: Lorenzo Stoakes <ljs@kernel.org>
Signed-off-by: Nico Pache <npache@redhat.com>
---
Documentation/admin-guide/mm/transhuge.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
index 16f37135ed80..b187d618452f 100644
--- a/Documentation/admin-guide/mm/transhuge.rst
+++ b/Documentation/admin-guide/mm/transhuge.rst
@@ -224,7 +224,7 @@ khugepaged will be automatically started when any THP size is enabled
(either of the per-size anon control or the top-level control are set
to "always" or "madvise"), and it'll be automatically shutdown when
all THP sizes are disabled (when both the per-size anon control and the
-top-level control are "never")
+top-level control are "never").
process THP controls
--------------------
@@ -301,7 +301,9 @@ being replaced by a PMD mapping, or (2) physical pages replaced by one
hugepage of various sizes (PMD-sized or mTHP). Each may happen independently,
or together, depending on the type of memory and the failures that occur.
As such, this value should be interpreted roughly as a sign of progress,
-and counters in /proc/vmstat consulted for more accurate accounting)::
+and counters in /proc/vmstat consulted for more accurate accounting.
+Per-order mTHP collapse statistics are also available under
+/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/stats/)::
/sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
--
2.54.0
^ permalink raw reply related
* [PATCH v2 1/7] mm/khugepaged: refactor per-scan state clearing into collapse_control_init_scan()
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Baolin Wang, Usama Arif, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
Extract the repeated clearing of node_load, alloc_nmask, and
mthp_present_ptes into a helper to reduce duplication in
collapse_scan_pmd() and collapse_scan_file(). Althought file scans do not
current use the bitmap, they will in the future, and clearing it now is
harmless.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Acked-by: Usama Arif <usama.arif@linux.dev>
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 55157567dc4c..6ec0812210b6 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -629,6 +629,13 @@ void __khugepaged_exit(struct mm_struct *mm)
}
}
+static void collapse_control_init_scan(struct collapse_control *cc)
+{
+ memset(cc->node_load, 0, sizeof(cc->node_load));
+ nodes_clear(cc->alloc_nmask);
+ bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
+}
+
static void release_pte_folio(struct folio *folio)
{
node_stat_mod_folio(folio,
@@ -1617,9 +1624,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out;
}
- bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
- memset(cc->node_load, 0, sizeof(cc->node_load));
- nodes_clear(cc->alloc_nmask);
+ collapse_control_init_scan(cc);
enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
@@ -2691,8 +2696,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
present = 0;
swap = 0;
- memset(cc->node_load, 0, sizeof(cc->node_load));
- nodes_clear(cc->alloc_nmask);
+ collapse_control_init_scan(cc);
rcu_read_lock();
xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) {
if (xas_retry(&xas, folio))
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6/7] mm/khugepaged: unmap pte before releasing vma write lock
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, David Hildenbrand, Andrew Morton, Lorenzo Stoakes,
Zi Yan, Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Usama Arif, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
We are currently dropping the anon_vma write lock before unmapping the
PTE. Although this is safe, due to us still holding the mmap_write_lock,
its safer and less confusing to switch the order of these two operations.
Suggested-by: David Hildenbrand <david@kernel.org>
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 655dbc90535a..40125dcb4de9 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -1546,10 +1546,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
result = SCAN_SUCCEED;
out_up_write:
- if (anon_vma_locked)
- anon_vma_unlock_write(vma->anon_vma);
if (pte)
pte_unmap(pte);
+ if (anon_vma_locked)
+ anon_vma_unlock_write(vma->anon_vma);
mmap_write_unlock(mm);
out_nolock:
if (folio)
--
2.54.0
^ permalink raw reply related
* [PATCH v2 5/7] mm/khugepaged: Refactor the PTE state checks into a helper
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, David Hildenbrand, Andrew Morton, Lorenzo Stoakes,
Zi Yan, Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Usama Arif, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
For anonymous collapse, the collapse_scan_pmd() and
__collapse_huge_page_isolate() functions share a large portion of their
logic. These functions both check the state of the PTEs and verify the
following:
- max_pte_* values are not exceeded
- uffd is not active
- lazyfree properties
- non-anonymous
Merge these checks into a helper collapse_check_pte() to reduce code
duplication. We also add a helper struct for this function called
pte_check_context which allows us to pass the required parameters in a
clean and elegant manner.
A helper function is also introduced pte_check_fail() to provide a clean
interface to set the pte_check_context failure results and return
PTE_CHECK_FAIL state. This helps reduce code duplications across the new
collapse_check_pte function.
Two slight modifications are done to the original functionality. We now
warn (instead of crash) if the anon test fails, and we leverage the
vm_normal_folio function instead of page->folio, this should be
functionally equivalent.
No other functional changes intended.
This patch is heavily based off work done by Lance Yang, but modified to
deal with conflicts and feedback received during the review cycle [1].
[1] https://lore.kernel.org/linux-mm/20251008043748.45554-1-lance.yang@linux.dev/
Suggested-by: David Hildenbrand <david@kernel.org>
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 298 +++++++++++++++++++++++++-----------------------
1 file changed, 157 insertions(+), 141 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index d785468ffb96..655dbc90535a 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -64,6 +64,12 @@ enum scan_result {
SCAN_PAGE_DIRTY_OR_WRITEBACK,
};
+enum pte_check_result {
+ PTE_CHECK_SUCCEED,
+ PTE_CHECK_FAIL,
+ PTE_CHECK_CONTINUE,
+};
+
#define CREATE_TRACE_POINTS
#include <trace/events/huge_memory.h>
@@ -118,6 +124,20 @@ struct collapse_control {
DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
};
+struct pte_check_context {
+ struct collapse_control *cc;
+ struct vm_area_struct *vma;
+ unsigned int order;
+ struct folio *folio;
+ int none_or_zero;
+ int shared;
+ int unmapped;
+ enum scan_result result;
+ unsigned int max_ptes_none;
+ unsigned int max_ptes_swap;
+ unsigned int max_ptes_shared;
+};
+
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
@@ -697,74 +717,131 @@ static void count_collapse_event(unsigned int order, enum vm_event_item vm_event
count_mthp_stat(order, mthp_event);
}
+/*
+ * pte_check_fail() - A simple helper to set the pte_check_context result and
+ * return PTE_CHECK_FAIL.
+ */
+static enum pte_check_result pte_check_fail(struct pte_check_context *ctx,
+ enum scan_result result)
+{
+ ctx->result = result;
+ return PTE_CHECK_FAIL;
+}
+
+/*
+ * collapse_check_pte() - Check if a PTE is suitable for collapse
+ *
+ * Check if a PTE is suitable for collapse based on the following criteria:
+ * - max_pte_* values are not exceeded
+ * - uffd is not active
+ * - lazyfree properties are not present
+ * - only anonymous pages are present
+ *
+ * a helper struct pte_check_context is used to pass and store relevant
+ * information between the collapse_check_pte() function and the caller.
+ *
+ * Return: PTE_CHECK_SUCCEED if the PTE is suitable for collapse,
+ * PTE_CHECK_FAIL if the PTE is not suitable for collapse,
+ * PTE_CHECK_CONTINUE if the scan should continue to check the next PTE.
+ */
+static enum pte_check_result collapse_check_pte(pte_t pteval,
+ unsigned long addr, struct pte_check_context *ctx)
+{
+ if (pte_none_or_zero(pteval)) {
+ if (++ctx->none_or_zero > ctx->max_ptes_none) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_NONE_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_NONE);
+ return pte_check_fail(ctx, SCAN_EXCEED_NONE_PTE);
+ }
+ return PTE_CHECK_CONTINUE;
+ }
+ if (!pte_present(pteval)) {
+ if (ctx->unmapped == -1)
+ return pte_check_fail(ctx, SCAN_PTE_NON_PRESENT);
+ if (++ctx->unmapped > ctx->max_ptes_swap) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_SWAP_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_SWAP);
+ return pte_check_fail(ctx, SCAN_EXCEED_SWAP_PTE);
+ }
+ if (pte_swp_uffd_any(pteval))
+ return pte_check_fail(ctx, SCAN_PTE_UFFD);
+ return PTE_CHECK_CONTINUE;
+ }
+ /*
+ * Don't collapse if any of the small PTEs are armed with uffd
+ * write protection. Marking the new huge pmd as write protected
+ * could bring userfault messages that fall outside of the
+ * registered range.
+ */
+ if (pte_uffd(pteval))
+ return pte_check_fail(ctx, SCAN_PTE_UFFD);
+
+ ctx->folio = vm_normal_folio(ctx->vma, addr, pteval);
+ if (unlikely(!ctx->folio) || unlikely(folio_is_zone_device(ctx->folio)))
+ return pte_check_fail(ctx, SCAN_PAGE_NULL);
+
+ /*
+ * If the vma has the VM_DROPPABLE flag, the collapse will
+ * preserve the lazyfree property without needing to skip.
+ */
+ if (ctx->cc->is_khugepaged && !(ctx->vma->vm_flags & VM_DROPPABLE) &&
+ folio_test_lazyfree(ctx->folio) && !pte_dirty(pteval))
+ return pte_check_fail(ctx, SCAN_PAGE_LAZYFREE);
+
+ if (!folio_test_anon(ctx->folio)) {
+ VM_WARN_ON_FOLIO(!folio_test_anon(ctx->folio), ctx->folio);
+ return pte_check_fail(ctx, SCAN_PAGE_ANON);
+ }
+
+ if (folio_maybe_mapped_shared(ctx->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.
+ */
+ if (++ctx->shared > ctx->max_ptes_shared) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_SHARED_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_SHARED);
+ return pte_check_fail(ctx, SCAN_EXCEED_SHARED_PTE);
+ }
+ }
+
+ return PTE_CHECK_SUCCEED;
+}
+
static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
unsigned int order, struct list_head *compound_pagelist)
{
- const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
- const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
const unsigned long nr_pages = 1UL << order;
- struct page *page = NULL;
struct folio *folio = NULL;
unsigned long addr = start_addr;
- pte_t *_pte;
- int none_or_zero = 0, shared = 0, referenced = 0;
+ pte_t *_pte, pteval;
+ int referenced = 0;
enum scan_result result = SCAN_FAIL;
+ enum pte_check_result pte_check;
+ struct pte_check_context ctx = {
+ .cc = cc,
+ .vma = vma,
+ .order = order,
+ .unmapped = -1, /* don't check swap PTEs */
+ .max_ptes_none = collapse_max_ptes_none(cc, vma, order),
+ .max_ptes_shared = collapse_max_ptes_shared(cc, order),
+ };
for (_pte = pte; _pte < pte + nr_pages;
_pte++, addr += PAGE_SIZE) {
- pte_t pteval = ptep_get(_pte);
- if (pte_none_or_zero(pteval)) {
- if (++none_or_zero > max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_collapse_event(order, THP_SCAN_EXCEED_NONE_PTE,
- MTHP_STAT_COLLAPSE_EXCEED_NONE);
- goto out;
- }
- continue;
- }
- if (!pte_present(pteval)) {
- result = SCAN_PTE_NON_PRESENT;
- goto out;
- }
- if (pte_uffd(pteval)) {
- result = SCAN_PTE_UFFD;
- goto out;
- }
- page = vm_normal_page(vma, addr, pteval);
- if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
- result = SCAN_PAGE_NULL;
- goto out;
- }
-
- folio = page_folio(page);
- VM_BUG_ON_FOLIO(!folio_test_anon(folio), folio);
-
- /*
- * If the vma has the VM_DROPPABLE flag, the collapse will
- * preserve the lazyfree property without needing to skip.
- */
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
- folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
- result = SCAN_PAGE_LAZYFREE;
+ pteval = ptep_get(_pte);
+ pte_check = collapse_check_pte(pteval, addr, &ctx);
+ folio = ctx.folio;
+ if (pte_check == PTE_CHECK_FAIL) {
+ result = ctx.result;
goto out;
}
+ if (pte_check == PTE_CHECK_CONTINUE)
+ continue;
- /* 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.
- */
- if (++shared > max_ptes_shared) {
- result = SCAN_EXCEED_SHARED_PTE;
- count_collapse_event(order, THP_SCAN_EXCEED_SHARED_PTE,
- MTHP_STAT_COLLAPSE_EXCEED_SHARED);
- goto out;
- }
- }
/*
* TODO: In some cases of partially-mapped folios, we'd actually
* want to collapse.
@@ -841,13 +918,13 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
- trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
+ trace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,
referenced, result, order);
return result;
}
out:
release_pte_pages(pte, _pte, compound_pagelist);
- trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
+ trace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,
referenced, result, order);
return result;
}
@@ -1459,7 +1536,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
pmd_populate(mm, pmd, pmd_pgtable(_pmd));
map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
- /*uffd_wp=*/ false);
+ /*uffd=*/ false);
if (pte_ptl != pmd_ptl)
spin_unlock(pte_ptl);
}
@@ -1613,24 +1690,30 @@ 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_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
- const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
- unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
- int none_or_zero = 0, shared = 0, referenced = 0;
- enum scan_result result = SCAN_FAIL;
- struct page *page = NULL;
struct folio *folio = NULL;
+ int referenced = 0;
+ enum scan_result result = SCAN_FAIL;
unsigned long addr;
unsigned long enabled_orders;
spinlock_t *ptl;
- int node = NUMA_NO_NODE, unmapped = 0;
+ int node = NUMA_NO_NODE;
+ enum pte_check_result pte_check;
VM_BUG_ON(start_addr & ~HPAGE_PMD_MASK);
+ struct pte_check_context ctx = {
+ .cc = cc,
+ .vma = vma,
+ .order = HPAGE_PMD_ORDER,
+ .max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER),
+ .max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER),
+ .max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER),
+ };
+
result = find_pmd_or_thp_or_none(mm, start_addr, &pmd);
if (result != SCAN_SUCCEED) {
cc->progress++;
@@ -1647,7 +1730,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
* is then checked again in mthp_collapse() for each attempted order.
*/
if (enabled_orders != BIT(HPAGE_PMD_ORDER))
- max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ ctx.max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
if (!pte) {
@@ -1663,81 +1746,14 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
cc->progress++;
- if (pte_none_or_zero(pteval)) {
- if (++none_or_zero > max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_NONE_PTE,
- MTHP_STAT_COLLAPSE_EXCEED_NONE);
- goto out_unmap;
- }
- continue;
- }
- if (!pte_present(pteval)) {
- if (++unmapped > max_ptes_swap) {
- result = SCAN_EXCEED_SWAP_PTE;
- count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SWAP_PTE,
- MTHP_STAT_COLLAPSE_EXCEED_SWAP);
- goto out_unmap;
- }
- /*
- * Always be strict with uffd-wp
- * enabled swap entries. Please see
- * comment below for pte_uffd().
- */
- if (pte_swp_uffd_any(pteval)) {
- result = SCAN_PTE_UFFD;
- goto out_unmap;
- }
- continue;
- }
- if (pte_uffd(pteval)) {
- /*
- * Don't collapse the page if any of the small
- * PTEs are armed with uffd write protection.
- * Here we can also mark the new huge pmd as
- * write protected if any of the small ones is
- * marked but that could bring unknown
- * userfault messages that falls outside of
- * the registered range. So, just be simple.
- */
- result = SCAN_PTE_UFFD;
- goto out_unmap;
- }
-
- page = vm_normal_page(vma, addr, pteval);
- if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
- result = SCAN_PAGE_NULL;
- goto out_unmap;
- }
- folio = page_folio(page);
-
- /*
- * If the vma has the VM_DROPPABLE flag, the collapse will
- * preserve the lazyfree property without needing to skip.
- */
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
- folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
- result = SCAN_PAGE_LAZYFREE;
- goto out_unmap;
- }
-
- if (!folio_test_anon(folio)) {
- result = SCAN_PAGE_ANON;
+ pte_check = collapse_check_pte(pteval, addr, &ctx);
+ if (pte_check == PTE_CHECK_FAIL) {
+ result = ctx.result;
goto out_unmap;
}
-
- /*
- * We treat a single page as shared if any part of the THP
- * is shared.
- */
- if (folio_maybe_mapped_shared(folio)) {
- if (++shared > max_ptes_shared) {
- result = SCAN_EXCEED_SHARED_PTE;
- count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SHARED_PTE,
- MTHP_STAT_COLLAPSE_EXCEED_SHARED);
- goto out_unmap;
- }
- }
+ if (pte_check == PTE_CHECK_CONTINUE)
+ continue;
+ folio = ctx.folio;
/* Set bit for occupied pages */
__set_bit(i, cc->mthp_present_ptes);
@@ -1779,7 +1795,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
}
if (cc->is_khugepaged &&
(!referenced ||
- (unmapped && referenced < HPAGE_PMD_NR / 2))) {
+ (ctx.unmapped && referenced < HPAGE_PMD_NR / 2))) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -1790,13 +1806,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
/* collapse_huge_page expects the lock to be dropped before calling */
mmap_read_unlock(mm);
result = mthp_collapse(mm, start_addr, referenced,
- unmapped, cc, enabled_orders);
+ ctx.unmapped, cc, enabled_orders);
/* mmap_lock was released above, set lock_dropped */
*lock_dropped = true;
}
out:
trace_mm_khugepaged_scan_pmd(mm, folio, referenced,
- none_or_zero, result, unmapped);
+ ctx.none_or_zero, result, ctx.unmapped);
return result;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 4/7] mm/khugepaged: fix outdated comments
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Usama Arif, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
Fix comment in collapse_scan_pmd() that still described the old
folio_mapcount() > folio_ref_count() check and a "512" false-positive
scenario. The code now uses folio_expected_ref_count() != folio_ref_count()
which doesn't suffer from the same limitation.
Fix comment in collapse_huge_page() that referenced ptep_clear_flush,
when the code actually uses pmdp_collapse_flush.
Fix comment in __collapse_huge_page_swapin() that referenced the old
function name khugepaged_scan_pmd, now collapse_scan_pmd.
Also clean up some simple typos and stale terminology (mmap_sem ->
mmap_lock, PG_lock -> folio lock, page -> folio, grammar).
We also clarify a comment regarding where the max_ptes_none check is
deferred to in mthp_collapse() from the original collapse_scan_pmd check.
Acked-by: Usama Arif <usama.arif@linux.dev>
Assisted-by: Cursor(claude-sonnet-4):4.6
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index f65bbe2051b3..d785468ffb96 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -620,7 +620,7 @@ void __khugepaged_exit(struct mm_struct *mm)
/*
* This is required to serialize against
* collapse_test_exit() (which is guaranteed to run
- * under mmap sem read mode). Stop here (after we return all
+ * under mmap_lock read mode). Stop here (after we return all
* pagetables will be destroyed) until khugepaged has finished
* working on the pagetables under the mmap_lock.
*/
@@ -789,7 +789,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
/*
* We can do it before folio_isolate_lru because the
- * folio can't be freed from under us. NOTE: PG_lock
+ * folio can't be freed from under us. NOTE: folio lock
* is needed to serialize against split_huge_page
* when invoked from the VM.
*/
@@ -816,7 +816,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
}
/*
- * Isolate the page to avoid collapsing an hugepage
+ * Isolate the folio to avoid collapsing a hugepage
* currently in use by the VM.
*/
if (!folio_isolate_lru(folio)) {
@@ -1101,7 +1101,7 @@ static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned l
return SCAN_VMA_CHECK;
/*
* Anon VMA expected, the address may be unmapped then
- * remapped to file after khugepaged reaquired the mmap_lock.
+ * remapped to file after khugepaged reacquired the mmap_lock.
*
* thp_vma_allowable_orders may return true for qualified file
* vmas.
@@ -1159,7 +1159,7 @@ 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.
+ * Only done if collapse_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
@@ -1351,8 +1351,8 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
mmap_read_unlock(mm);
/*
* Prevent all access to pagetables with the exception of
- * gup_fast later handled by the ptep_clear_flush and the VM
- * handled by the anon_vma lock + PG_lock.
+ * gup_fast later handled by the pmdp_collapse_flush and the VM
+ * handled by the anon_vma lock + folio lock.
*
* UFFDIO_MOVE is prevented to race as well thanks to the
* mmap_lock.
@@ -1643,7 +1643,8 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
- * scan all pages to populate the bitmap for mTHP collapse.
+ * scan all pages to populate the bitmap for mTHP collapse. The bitmap
+ * is then checked again in mthp_collapse() for each attempted order.
*/
if (enabled_orders != BIT(HPAGE_PMD_ORDER))
max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
@@ -1764,12 +1765,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
/*
* Check if the page has any GUP (or other external) pins.
*
- * Here the check may be racy:
- * it may see folio_mapcount() > folio_ref_count().
- * But such case is ephemeral we could always retry collapse
- * later. However it may report false positive if the page
- * has excessive GUP pins (i.e. 512). Anyway the same check
- * will be done again later the risk seems low.
+ * Here the check is racy, but such case is ephemeral and
+ * we could always retry collapse later. Anyway the same
+ * check will be done again later the risk seems low.
*/
if (folio_expected_ref_count(folio) != folio_ref_count(folio)) {
result = SCAN_PAGE_COUNT;
--
2.54.0
^ permalink raw reply related
* [PATCH v2 3/7] mm/khugepaged: introduce a count_collapse_event() helper
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Baolin Wang, Usama Arif, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Zi Yan, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
Provide a simple helper function to help reduce a often used, and
duplicate pattern across the khugepaged code.
When collapsing to a PMD we need to record a vm_event and the mTHP_stat
event. When doing mTHP collapse we only update the mTHP stat.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Acked-by: Usama Arif <usama.arif@linux.dev>
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index e92e2b928f17..f65bbe2051b3 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -689,6 +689,14 @@ static inline bool collapse_is_referenced(struct collapse_control *cc, pte_t pte
mmu_notifier_test_young(vma->vm_mm, addr));
}
+static void count_collapse_event(unsigned int order, enum vm_event_item vm_event,
+ enum mthp_stat_item mthp_event)
+{
+ if (is_pmd_order(order))
+ count_vm_event(vm_event);
+ count_mthp_stat(order, mthp_event);
+}
+
static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
unsigned int order, struct list_head *compound_pagelist)
@@ -709,9 +717,8 @@ 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;
- if (is_pmd_order(order))
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE);
+ count_collapse_event(order, THP_SCAN_EXCEED_NONE_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_NONE);
goto out;
}
continue;
@@ -753,9 +760,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
*/
if (++shared > max_ptes_shared) {
result = 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);
+ count_collapse_event(order, THP_SCAN_EXCEED_SHARED_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_SHARED);
goto out;
}
}
@@ -1264,15 +1270,12 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
folio = __folio_alloc(gfp, order, node, &cc->alloc_nmask);
if (!folio) {
*foliop = NULL;
- if (is_pmd_order(order))
- count_vm_event(THP_COLLAPSE_ALLOC_FAILED);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC_FAILED);
+ count_collapse_event(order, THP_COLLAPSE_ALLOC_FAILED,
+ MTHP_STAT_COLLAPSE_ALLOC_FAILED);
return SCAN_ALLOC_HUGE_PAGE_FAIL;
}
- if (is_pmd_order(order))
- count_vm_event(THP_COLLAPSE_ALLOC);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC);
+ count_collapse_event(order, THP_COLLAPSE_ALLOC, MTHP_STAT_COLLAPSE_ALLOC);
if (unlikely(mem_cgroup_charge(folio, mm, gfp))) {
folio_put(folio);
@@ -1662,8 +1665,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
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);
- count_mthp_stat(HPAGE_PMD_ORDER,
+ count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_NONE_PTE,
MTHP_STAT_COLLAPSE_EXCEED_NONE);
goto out_unmap;
}
@@ -1672,8 +1674,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
if (!pte_present(pteval)) {
if (++unmapped > max_ptes_swap) {
result = SCAN_EXCEED_SWAP_PTE;
- count_vm_event(THP_SCAN_EXCEED_SWAP_PTE);
- count_mthp_stat(HPAGE_PMD_ORDER,
+ count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SWAP_PTE,
MTHP_STAT_COLLAPSE_EXCEED_SWAP);
goto out_unmap;
}
@@ -1731,8 +1732,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
if (folio_maybe_mapped_shared(folio)) {
if (++shared > max_ptes_shared) {
result = SCAN_EXCEED_SHARED_PTE;
- count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
- count_mthp_stat(HPAGE_PMD_ORDER,
+ count_collapse_event(HPAGE_PMD_ORDER, THP_SCAN_EXCEED_SHARED_PTE,
MTHP_STAT_COLLAPSE_EXCEED_SHARED);
goto out_unmap;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 2/7] mm/khugepaged: extract young page check into collapse_is_referenced() helper
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Usama Arif, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Zi Yan, Baolin Wang, Liam R. Howlett,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
In-Reply-To: <20260715025941.1571316-1-npache@redhat.com>
This change deduplicates the "is this PTE/folio referenced enough to be
considered for a collapse" condition that was repeated in both
__collapse_huge_page_isolate() and collapse_scan_pmd(), extracting it into
a single inline helper function.
Also move the comment and use it as the function header. While we are at
it, updated the comment to clarify that a young pte is a recently accessed
one.
Acked-by: Usama Arif <usama.arif@linux.dev>
Signed-off-by: Nico Pache <npache@redhat.com>
---
mm/khugepaged.c | 35 +++++++++++++++++++----------------
1 file changed, 19 insertions(+), 16 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 6ec0812210b6..e92e2b928f17 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -672,6 +672,23 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,
}
}
+/*
+ * collapse_is_referenced() - Check for enough referenced PTEs to justify collapsing
+ *
+ * If collapse was initiated by khugepaged, check that the page has been
+ * recently accessed (young pte) to justify collapsing the page.
+ *
+ * Return: true if the page has been recently accessed.
+ */
+static inline bool collapse_is_referenced(struct collapse_control *cc, pte_t pteval,
+ struct folio *folio, struct vm_area_struct *vma, unsigned long addr)
+{
+ return cc->is_khugepaged &&
+ (pte_young(pteval) || folio_test_young(folio) ||
+ folio_test_referenced(folio) ||
+ mmu_notifier_test_young(vma->vm_mm, addr));
+}
+
static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
unsigned int order, struct list_head *compound_pagelist)
@@ -810,14 +827,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
if (folio_test_large(folio))
list_add_tail(&folio->lru, compound_pagelist);
next:
- /*
- * If collapse was initiated by khugepaged, check that there is
- * enough young pte to justify collapsing the page
- */
- if (cc->is_khugepaged &&
- (pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
- mmu_notifier_test_young(vma->vm_mm, addr)))
+ if (collapse_is_referenced(cc, pteval, folio, vma, addr))
referenced++;
}
@@ -1766,14 +1776,7 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
goto out_unmap;
}
- /*
- * If collapse was initiated by khugepaged, check that there is
- * enough young pte to justify collapsing the page
- */
- if (cc->is_khugepaged &&
- (pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
- mmu_notifier_test_young(vma->vm_mm, addr)))
+ if (collapse_is_referenced(cc, pteval, folio, vma, addr))
referenced++;
}
if (cc->is_khugepaged &&
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/7] mm/khugepaged: several cleanups
From: Nico Pache @ 2026-07-15 2:59 UTC (permalink / raw)
To: linux-doc, linux-kernel, linux-mm
Cc: Nico Pache, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Zi Yan, Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Usama Arif, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan
The following changes stem from a number of reviews during my khugepaged
mTHP support series [1]. Some of these are minor code cleanups, issues or
reviews that we decided to deferred to a followup series, or in the case
of the more major patch of the series, changes [2] Lance Yang attempted
while my series was in-flight and we decided to wait till later to try.
The first 3 patches introduce helper functions to increase code reuse and
readability. This includes a per-scan state clearing function, extracting
the young page check into a helper, and a count_collapse_event() function
to reduce a repetative pattern used across mTHP collapse.
The 4th patch was the byproduct of me throwing Claude at all the
comments in khugepaged verifying and looking for any outdated info.
The 5th patch is based on Lance Yang's commit series [2] trying to extract
the PTE state checking into a helper function. This required a bit of
rewriting due to differences after mTHP collapse was introduced. I also
took into account the changes requested during his patches review cycle.
The remaining 2 patches were review points during my mTHP series that we
agreed can be deferred to a later series.
Thank you to those whos reviews and work I leveraged to achieve these
cleanups.
V2 Changes:
- Add Acks/RB tags
- rename collapse_is_young() to collapse_is_referenced()
- delete unncessary comment for collapse_control_init_scan()
- merge fixup from V1 (fixes stale folio reference) into patch 5
- keep original ordering for checks in patch 5
- merge patch 7 into patch 4
- conflict resolution from uffd_rwp changes
V1: https://lore.kernel.org/all/20260706154500.39178-1-npache@redhat.com/
[1] - https://lore.kernel.org/all/20260605161422.213817-1-npache@redhat.com/
[2] - https://lore.kernel.org/all/20251008043748.45554-1-lance.yang@linux.dev/
Nico Pache (7):
mm/khugepaged: refactor per-scan state clearing into
collapse_control_init_scan()
mm/khugepaged: extract young page check into collapse_is_referenced()
helper
mm/khugepaged: introduce a count_collapse_event() helper
mm/khugepaged: fix outdated comments
mm/khugepaged: Refactor the PTE state checks into a helper
mm/khugepaged: unmap pte before releasing vma write lock
mm: Documentation: clarify where the mTHP stats live
Documentation/admin-guide/mm/transhuge.rst | 6 +-
mm/khugepaged.c | 399 +++++++++++----------
2 files changed, 214 insertions(+), 191 deletions(-)
base-commit: bdc38bfc1262e3d1432afadd2aa2ffd83d139dbb
--
2.54.0
^ permalink raw reply
* Re: [RFC PATCH 1/7] iommu: Add group lookup by ID
From: Zhanpeng Zhang @ 2026-07-15 2:55 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach, will,
robin.murphy, fustini, pjw, aou, alex, Dave.Martin, james.morse,
babu.moger, corbet, shuah, kevin.tian, cuiyunhui, yuanzhu, iommu,
linux-riscv, linux-kernel, linux-doc, linux-kselftest, x86
In-Reply-To: <20260714140336.GA3716926@ziepe.ca>
Hi Jason,
On 7/14/26 10:03 PM, Jason Gunthorpe wrote:
> On Tue, Jul 14, 2026 at 09:06:51PM +0800, Zhanpeng Zhang wrote:
>> Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
>> the numeric ID used in /sys/kernel/iommu_groups.
>>
>> An ID lookup must keep the group object alive without also keeping an
>> otherwise empty group active. Embed the devices kobject in struct
>> iommu_group so its address remains valid until the parent group is
>> released, and return a reference on the parent kobject to ID lookup
>> callers. Add iommu_group_put_by_id() to release that reference and
>> iommu_group_is_active() to detect when the devices kobject has become
>> inactive.
>>
>> Serialize lookup against group teardown with iommu_group_kset_mutex and
>> only return groups whose devices kobject still has a live reference.
>> This prevents a concurrent lookup from dereferencing a stale child
>> kobject while allowing external users to discard bindings to empty
>> groups.
>>
>> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
>> ---
>> drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
>> include/linux/iommu.h | 17 +++++++
>> 2 files changed, 113 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
>> index e8f13dcebbde..da269d10f6bf 100644
>> --- a/drivers/iommu/iommu.c
>> +++ b/drivers/iommu/iommu.c
>> @@ -40,6 +40,7 @@
>> #include "iommu-priv.h"
>>
>> static struct kset *iommu_group_kset;
>> +static DEFINE_MUTEX(iommu_group_kset_mutex);
>> static DEFINE_IDA(iommu_group_ida);
>
> I think it would be better to change the ida to an xarray than to use
> a string search on a kset..
>
> Jason
Agreed. Using an XArray as both the ID allocator and the group lookup
table avoids the string-based kset lookup and the additional mutex.
I will refactor it in the next revision.
Thanks,
Zhanpeng
^ permalink raw reply
* Re: [RFC PATCH 1/7] iommu: Add group lookup by ID
From: Zhanpeng Zhang @ 2026-07-15 2:54 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach, will,
robin.murphy, fustini, pjw, aou, alex, Dave.Martin, james.morse,
babu.moger, corbet, shuah, kevin.tian, cuiyunhui, yuanzhu, iommu,
linux-riscv, linux-kernel, linux-doc, linux-kselftest, x86
In-Reply-To: <20260714140336.GA3716926@ziepe.ca>
Hi Jason,
On 7/14/26 10:03 PM, Jason Gunthorpe wrote:
> On Tue, Jul 14, 2026 at 09:06:51PM +0800, Zhanpeng Zhang wrote:
>> Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
>> the numeric ID used in /sys/kernel/iommu_groups.
>>
>> An ID lookup must keep the group object alive without also keeping an
>> otherwise empty group active. Embed the devices kobject in struct
>> iommu_group so its address remains valid until the parent group is
>> released, and return a reference on the parent kobject to ID lookup
>> callers. Add iommu_group_put_by_id() to release that reference and
>> iommu_group_is_active() to detect when the devices kobject has become
>> inactive.
>>
>> Serialize lookup against group teardown with iommu_group_kset_mutex and
>> only return groups whose devices kobject still has a live reference.
>> This prevents a concurrent lookup from dereferencing a stale child
>> kobject while allowing external users to discard bindings to empty
>> groups.
>>
>> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
>> ---
>> drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
>> include/linux/iommu.h | 17 +++++++
>> 2 files changed, 113 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
>> index e8f13dcebbde..da269d10f6bf 100644
>> --- a/drivers/iommu/iommu.c
>> +++ b/drivers/iommu/iommu.c
>> @@ -40,6 +40,7 @@
>> #include "iommu-priv.h"
>>
>> static struct kset *iommu_group_kset;
>> +static DEFINE_MUTEX(iommu_group_kset_mutex);
>> static DEFINE_IDA(iommu_group_ida);
>
> I think it would be better to change the ida to an xarray than to use
> a string search on a kset..
>
> Jason
Agreed. Using an XArray as both the ID allocator and the group lookup
table avoids the string-based kset lookup and the additional mutex.
I will refactor it in the next revision.
Thanks,
Zhanpeng
^ permalink raw reply
* Re: [PATCH 1/2] mm/zswap: Fix global shrinker when memory cgroup is disabled
From: Andrew Morton @ 2026-07-15 2:31 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Hao Jia, tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, linux-mm,
linux-kernel, linux-doc, Hao Jia, stable
In-Reply-To: <CAO9r8zM5nzDqNcx5UoDgGexvR6jf8MmJV9SomM4AS7n-rZ2o5Q@mail.gmail.com>
On Tue, 14 Jul 2026 09:52:59 -0700 Yosry Ahmed <yosry@kernel.org> wrote:
> > When memory cgroup is disabled, mem_cgroup_iter() always returns NULL.
> > Therefore, the global shrinker shrink_worker() always takes the !memcg
> > branch. After MAX_RECLAIM_RETRIES empty walks, the worker simply gives up,
> > so it fails to write back anything.
> >
> > Therefore, when memory cgroup is disabled, fall through with the !memcg
> > branch and shrink the root memcg directly.
> >
> > With memcg disabled, shrink_memcg() only returns -ENOENT when the root
> > LRU is empty, which means the total pages are already below thr. The
> > loop then safely bails out via the zswap_total_pages() <= thr check.
> > For any other return value from shrink_memcg(), the loop is guaranteed
> > to terminate, either after MAX_RECLAIM_RETRIES failures or once the
> > threshold is met.
> >
> > Fixes: a65b0e7607cc ("zswap: make shrinking memcg-aware")
> > Cc: stable@vger.kernel.org
> > Suggested-by: Nhat Pham <nphamcs@gmail.com>
> > Acked-by: Nhat Pham <nphamcs@gmail.com>
> > Acked-by: Yosry Ahmed <yosry@kernel.org>
> > Reported-by: Yosry Ahmed <yosry@kernel.org>
> > Closes: https://lore.kernel.org/all/CAO9r8zPVzMKFbCixxD-qgtRrkFxWVrHiZZeLc=eyTPKPVQgX4g@mail.gmail.com
> > Signed-off-by: Hao Jia <jiahao1@lixiang.com>
>
> Patch 2 doesn't really depend on this one, right?
>
> If that's the case I think this can (and should be) picked up
> separately as a hotfix. Andrew, WDYT?
Please update the changelog to clearly describe the userspace-visible
effects of the bug, thanks.
Also, AI review has flagged several possible issues, all appear to be
serious:
https://sashiko.dev/#/patchset/20260714081510.16895-1-jiahao.kernel@gmail.com
^ permalink raw reply
* Re: What's cooking in zh_CN (Jul 2026, #02)
From: Dongliang Mu @ 2026-07-15 1:55 UTC (permalink / raw)
To: Doehyun Baek, Weijie Yuan
Cc: linux-doc, Alex Shi, Yanteng Si, Ben Guo, Gary Guo, Yan Zhu,
Jiandong Qiu, chengyaqiang, Haoyang Liu
In-Reply-To: <CAN-j9Uo2f4dmWo8bMkdtXg7g6uayK_XZatkGs5iKv6-dNZ_Y-g@mail.gmail.com>
On 7/15/26 1:56 AM, Doehyun Baek wrote:
>> Btw, for example, my patch (Weijie Yuan · docs/zh_CN: add docs-next
> checkout workaround) is actually directly discarded after we reached
> a consensus during our communication (with Dongliang). But it's
> obvious that we didn't say it explicitly. So your website can't
> recognize it automaticly right now. Perhaps we can think about how
> to deal with this situation later.
>
> Yeah, this is a downside of an automated approach: it can miss details
> that are only implicit in the discussion. I see roughly three ways to
> handle such cases:
>
> 1. Allow authors to mark a patch explicitly by replying with a
> recognized phrase, such as `Patch-status: withdrawn`.
This is better.
Or similar to syzbot, we can provide an option in the webpage to
directly mark patchset as invalid.
> 2. Use natural-language reasoning, perhaps with an LLM, to infer the
> outcome from the discussion. I leaned against it due to cost and
> complexity.
> 3. Leave the patch pending and let it move to “Cold” automatically
> after 30 days.
Better together with 1. We may forget to reply a mark patch when busy.
>
> I think either the first or the third option makes sense in this situation.
>
> Thanks,
> Doehyun
^ permalink raw reply
* Re: What's cooking in zh_CN (Jul 2026, #02)
From: Alex Shi @ 2026-07-15 1:52 UTC (permalink / raw)
To: Doehyun Baek, Weijie Yuan
Cc: linux-doc, Alex Shi, Yanteng Si, Dongliang Mu, Ben Guo, Gary Guo,
Yan Zhu, Jiandong Qiu, chengyaqiang, Haoyang Liu
In-Reply-To: <CAN-j9UoUHQ2i4H+9G-XK_mOfKKyE9K9-mwUgPc+4yOVfiizgmA@mail.gmail.com>
On 2026/7/15 01:05, Doehyun Baek wrote:
> Hi Weijie,
>
> Thanks for putting together the “What’s cooking” reports!
> I "cooked" up a small website this evening that attempts to automate them:
>
> https://doehyunbaek.github.io/cook-linux-zhcn/
> <https://doehyunbaek.github.io/cook-linux-zhcn/>
Nice work!
Maybe add a build testing result for each of patches, like 'make
htmldocs -s', although build should pass before send out patches, but it
often be omitted.
> Every hour, a GitHub Actions workflow scans recent `docs/zh_CN` patches
> on the linux-doc mailing list, groups rerolls, and compares their
> subjects with Alex’s `docs-next` tree to determine whether they have
> been applied. Pending series with no update for more than 30 days are
> classified as “Cold.”
>
> This is still an experimental prototype, and its heuristics may have
> bugs, particularly when threads or patch subjects change. The source is
> available here:
>
> https://github.com/doehyunbaek/cook-linux-zhcn
> <https://github.com/doehyunbaek/cook-linux-zhcn>
> Issues, suggestions, and pull requests are welcome!
>
> Thanks,
> Doehyun
^ 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