* [PATCH v16 14/20] unwind_user: Flexible FP/RA recovery rules
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
To enable support for SFrame V3 flexible FDEs with a subsequent patch,
add support for the following flexible frame pointer (FP) and return
address (RA) recovery rules:
FP/RA = *(CFA + offset)
FP/RA = register + offset
FP/RA = *(register + offset)
Note that FP/RA recovery rules that use arbitrary register contents are
only valid when in the topmost frame, as their contents are otherwise
unknown.
This also enables unwinding of user space for architectures, such as
s390, that may save the frame pointer (FP) and/or return address (RA) in
other registers, for instance when in a leaf function.
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- Define dbg_once().
- unwind_user_get_reg(): Use pr_debug_once() instead of WARN_ON_ONCE()
to prevent user-triggered warning/panic. (Sashiko AI)
- unwind_user_next_common(): Handle UNWIND_USER_RULE_CFA_OFFSET for RA
and FP to use dbg_once() instead of WARN_ON_ONCE() to prevent user-
triggered warning/panic. (Sashiko AI)
Changes in v14:
- Improve comment on why UNWIND_USER_RULE_CFA_OFFSET is not implemented.
(Mark Rutland)
arch/x86/include/asm/unwind_user.h | 21 +++++++--
include/linux/unwind_user.h | 10 +++++
include/linux/unwind_user_types.h | 23 +++++++++-
kernel/unwind/sframe.c | 16 ++++++-
kernel/unwind/user.c | 70 +++++++++++++++++++++++++++---
5 files changed, 125 insertions(+), 15 deletions(-)
diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h
index 2dfb5ef11e36..9c3417be4283 100644
--- a/arch/x86/include/asm/unwind_user.h
+++ b/arch/x86/include/asm/unwind_user.h
@@ -21,15 +21,26 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
#define ARCH_INIT_USER_FP_FRAME(ws) \
.cfa_off = 2*(ws), \
- .ra_off = -1*(ws), \
- .fp_off = -2*(ws), \
+ .ra = { \
+ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .offset = -1*(ws), \
+ }, \
+ .fp = { \
+ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .offset = -2*(ws), \
+ }, \
.use_fp = true, \
.outermost = false,
#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \
.cfa_off = 1*(ws), \
- .ra_off = -1*(ws), \
- .fp_off = 0, \
+ .ra = { \
+ .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .offset = -1*(ws), \
+ }, \
+ .fp = { \
+ .rule = UNWIND_USER_RULE_RETAIN,\
+ }, \
.use_fp = false, \
.outermost = false,
@@ -41,4 +52,6 @@ static inline bool unwind_user_at_function_start(struct pt_regs *regs)
#endif /* CONFIG_HAVE_UNWIND_USER_FP */
+#include <asm-generic/unwind_user.h>
+
#endif /* _ASM_X86_UNWIND_USER_H */
diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h
index 7bf58f23aa64..6aca38f89ddd 100644
--- a/include/linux/unwind_user.h
+++ b/include/linux/unwind_user.h
@@ -33,6 +33,16 @@ static inline int unwind_user_get_ra_reg(unsigned long *val)
#define unwind_user_get_ra_reg unwind_user_get_ra_reg
#endif
+#ifndef unwind_user_get_reg
+static inline int unwind_user_get_reg(unsigned long *val, unsigned int regnum)
+{
+ pr_debug_once("%s (%d): unwind_user_get_reg(%u) not implemented\n",
+ current->comm, current->pid, regnum);
+ return -EINVAL;
+}
+#define unwind_user_get_reg unwind_user_get_reg
+#endif
+
int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries);
#endif /* _LINUX_UNWIND_USER_H */
diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 616cc5ee4586..0d02714a1b5d 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -27,10 +27,29 @@ struct unwind_stacktrace {
unsigned long *entries;
};
+#define UNWIND_USER_RULE_DEREF BIT(31)
+
+enum unwind_user_rule {
+ UNWIND_USER_RULE_RETAIN, /* entity = entity */
+ UNWIND_USER_RULE_CFA_OFFSET, /* entity = CFA + offset */
+ UNWIND_USER_RULE_REG_OFFSET, /* entity = register + offset */
+ /* DEREF variants */
+ UNWIND_USER_RULE_CFA_OFFSET_DEREF = /* entity = *(CFA + offset) */
+ UNWIND_USER_RULE_CFA_OFFSET | UNWIND_USER_RULE_DEREF,
+ UNWIND_USER_RULE_REG_OFFSET_DEREF = /* entity = *(register + offset) */
+ UNWIND_USER_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF,
+};
+
+struct unwind_user_rule_data {
+ enum unwind_user_rule rule;
+ s32 offset;
+ unsigned int regnum;
+};
+
struct unwind_user_frame {
s32 cfa_off;
- s32 ra_off;
- s32 fp_off;
+ struct unwind_user_rule_data ra;
+ struct unwind_user_rule_data fp;
bool use_fp;
bool outermost;
};
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index d573c2529926..29a874a67f32 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -285,6 +285,18 @@ static __always_inline int __read_fre(struct sframe_section *sec,
return -EFAULT;
}
+static __always_inline void
+sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
+ s32 offset)
+{
+ if (offset) {
+ rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF;
+ rule_data->offset = offset;
+ } else {
+ rule_data->rule = UNWIND_USER_RULE_RETAIN;
+ }
+}
+
static __always_inline int __find_fre(struct sframe_section *sec,
struct sframe_fde_internal *fde,
unsigned long ip,
@@ -335,8 +347,8 @@ static __always_inline int __find_fre(struct sframe_section *sec,
fre = prev_fre;
frame->cfa_off = fre->cfa_off;
- frame->ra_off = fre->ra_off;
- frame->fp_off = fre->fp_off;
+ sframe_init_rule_data(&frame->ra, fre->ra_off);
+ sframe_init_rule_data(&frame->fp, fre->fp_off);
frame->use_fp = SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP;
frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info);
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index afa7c6f6d9b4..c6a2abac78e0 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -12,6 +12,17 @@
#include <linux/uaccess.h>
#include <linux/sframe.h>
+#ifdef CONFIG_DYNAMIC_DEBUG
+
+#define dbg_once(fmt, ...) \
+ pr_debug_once("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+
+#else /* !CONFIG_DYNAMIC_DEBUG */
+
+#define dbg_once(args...) no_printk(args)
+
+#endif /* !CONFIG_DYNAMIC_DEBUG */
+
#define for_each_user_frame(state) \
for (unwind_user_start(state); !(state)->done; unwind_user_next(state))
@@ -64,22 +75,67 @@ static int unwind_user_next_common(struct unwind_user_state *state,
return -EINVAL;
/* Get the Return Address (RA) */
- if (frame->ra_off) {
- if (get_user_word(&ra, cfa, frame->ra_off, state->ws))
- return -EINVAL;
- } else {
+ switch (frame->ra.rule) {
+ case UNWIND_USER_RULE_RETAIN:
if (!state->topmost || unwind_user_get_ra_reg(&ra))
return -EINVAL;
+ break;
+ case UNWIND_USER_RULE_CFA_OFFSET:
+ /*
+ * RA = CFA + offset does not make sense.
+ * A return address cannot legitimately be a stack address.
+ */
+ dbg_once("UNWIND_USER_RULE_CFA_OFFSET invalid for RA\n");
+ return -EINVAL;
+ case UNWIND_USER_RULE_CFA_OFFSET_DEREF:
+ ra = cfa + frame->ra.offset;
+ break;
+ case UNWIND_USER_RULE_REG_OFFSET:
+ case UNWIND_USER_RULE_REG_OFFSET_DEREF:
+ if (!state->topmost || unwind_user_get_reg(&ra, frame->ra.regnum))
+ return -EINVAL;
+ ra += frame->ra.offset;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
}
+ if (frame->ra.rule & UNWIND_USER_RULE_DEREF &&
+ get_user_word(&ra, ra, 0, state->ws))
+ return -EINVAL;
/* Get the Frame Pointer (FP) */
- if (frame->fp_off && get_user_word(&fp, cfa, frame->fp_off, state->ws))
+ switch (frame->fp.rule) {
+ case UNWIND_USER_RULE_RETAIN:
+ fp = state->fp;
+ break;
+ case UNWIND_USER_RULE_CFA_OFFSET:
+ /*
+ * FP = CFA + offset is currently not used for FP
+ * (e.g. SFrame cannot represent this rule).
+ */
+ dbg_once("UNWIND_USER_RULE_CFA_OFFSET unsupported for FP\n");
+ return -EINVAL;
+ case UNWIND_USER_RULE_CFA_OFFSET_DEREF:
+ fp = cfa + frame->fp.offset;
+ break;
+ case UNWIND_USER_RULE_REG_OFFSET:
+ case UNWIND_USER_RULE_REG_OFFSET_DEREF:
+ if (!state->topmost || unwind_user_get_reg(&fp, frame->fp.regnum))
+ return -EINVAL;
+ fp += frame->fp.offset;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+ if (frame->fp.rule & UNWIND_USER_RULE_DEREF &&
+ get_user_word(&fp, fp, 0, state->ws))
return -EINVAL;
state->ip = ra;
state->sp = cfa;
- if (frame->fp_off)
- state->fp = fp;
+ state->fp = fp;
state->topmost = false;
return 0;
}
--
2.51.0
^ permalink raw reply related
* [PATCH v16 04/20] x86/uaccess: Add unsafe_copy_from_user() implementation
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
Add an x86 implementation of unsafe_copy_from_user() similar to the
existing unsafe_copy_to_user().
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- unsafe_copy_from_user(): Use const void *__src. (Sashiko AI)
arch/x86/include/asm/uaccess.h | 39 +++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index 3a0dd3c2b233..235886106f31 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -598,7 +598,7 @@ _label: \
* We want the unsafe accessors to always be inlined and use
* the error labels - thus the macro games.
*/
-#define unsafe_copy_loop(dst, src, len, type, label) \
+#define unsafe_copy_to_user_loop(dst, src, len, type, label) \
while (len >= sizeof(type)) { \
unsafe_put_user(*(type *)(src),(type __user *)(dst),label); \
dst += sizeof(type); \
@@ -606,15 +606,34 @@ _label: \
len -= sizeof(type); \
}
-#define unsafe_copy_to_user(_dst,_src,_len,label) \
-do { \
- char __user *__ucu_dst = (_dst); \
- const char *__ucu_src = (_src); \
- size_t __ucu_len = (_len); \
- unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u64, label); \
- unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u32, label); \
- unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u16, label); \
- unsafe_copy_loop(__ucu_dst, __ucu_src, __ucu_len, u8, label); \
+#define unsafe_copy_to_user(_dst, _src, _len, label) \
+do { \
+ void __user *__dst = (_dst); \
+ const void *__src = (_src); \
+ size_t __len = (_len); \
+ unsafe_copy_to_user_loop(__dst, __src, __len, u64, label); \
+ unsafe_copy_to_user_loop(__dst, __src, __len, u32, label); \
+ unsafe_copy_to_user_loop(__dst, __src, __len, u16, label); \
+ unsafe_copy_to_user_loop(__dst, __src, __len, u8, label); \
+} while (0)
+
+#define unsafe_copy_from_user_loop(dst, src, len, type, label) \
+ while (len >= sizeof(type)) { \
+ unsafe_get_user(*(type *)(dst), (type __user *)(src), label); \
+ dst += sizeof(type); \
+ src += sizeof(type); \
+ len -= sizeof(type); \
+ }
+
+#define unsafe_copy_from_user(_dst, _src, _len, label) \
+do { \
+ void *__dst = (_dst); \
+ const void __user *__src = (_src); \
+ size_t __len = (_len); \
+ unsafe_copy_from_user_loop(__dst, __src, __len, u64, label); \
+ unsafe_copy_from_user_loop(__dst, __src, __len, u32, label); \
+ unsafe_copy_from_user_loop(__dst, __src, __len, u16, label); \
+ unsafe_copy_from_user_loop(__dst, __src, __len, u8, label); \
} while (0)
#ifdef CONFIG_CC_HAS_ASM_GOTO_OUTPUT
--
2.51.0
^ permalink raw reply related
* [PATCH v16 05/20] unwind_user/sframe: Add support for reading .sframe contents
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
In preparation for using sframe to unwind user space stacks, add an
sframe_find() interface for finding the sframe information associated
with a given text address.
For performance, use user_read_access_begin() and the corresponding
unsafe_*() accessors. Note that use of pr_debug() in uaccess-enabled
regions would break noinstr validation, so there aren't any debug
messages yet. That will be added in a subsequent commit.
Link: https://lore.kernel.org/all/77c0d1ec143bf2a53d66c4ecb190e7e0a576fbfd.1737511963.git.jpoimboe@kernel.org/
Link: https://lore.kernel.org/all/b35ca3a3-8de5-4d32-8d30-d4e562f6b0de@linux.ibm.com/
[ Jens Remus: Add initial support for SFrame V3 (limited to default
FDEs). Add support for PC-relative FDE function start offset. Simplify
logic by using an internal FDE representation. Rename struct sframe_fre
to sframe_fre_internal to align with struct sframe_fde_internal.
Cleanup includes. Fix checkpatch errors "spaces required around that
':'". ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v16:
- __read_fre(): Compute FRE size before mutating dataword_count.
(Sashiko AI)
- __read_fre(): Convert user read access to scope-based cleanup.
(Sashiko AI)
- Move SRCU definitions to patch "unwind_user/sframe: Store .sframe
section data in per-mm maple tree". (Sashiko AI)
Changes in v15:
- __read_fde():
- Validate FDE repetition size for PCTYPE_MASK FDEs to be non-zero to
prevent division by zero. (Sashiko AI)
- Validate FDE PC type for supported values (i.e. PCTYPE_INC or
PCTYPE_MASK).
- Validate FDE function end against text end.
- Validate FDE's number of FREs to be less or equal to FDE's function
size, as each FRE must cover at least one byte. (Indu)
- __read_fre(): Validate FRE function offset against FDE repetition size
for PCTYPE_MASK.
- Change type of struct sframe_fde_internal field fres_num to the one of
struct sframe_fda_v3 field fres_num.
- Normalize error code usage (.sframe is removed for all but ENOENT):
ENOENT: No sframe or no FDE for IP found
(FDE found but no FRE found is EINVAL)
EFAULT: Bad address
EINVAL: Invalid input or sframe
- Build-time checks for config options:
- 64BIT: SFrame V3 only supports 64-bit architectures.
- HAVE_EFFICIENT_UNALIGNED_ACCESS: Unaligned access to 16/32-bit
SFrame FRE fields and datawords using unsafe_get_user(). (Steven)
- Reword my changelog in commit message.
Changes in v14:
- Fix FDE function start address check in __read_fde().
- Adjust to rename of SFRAME_FDE_TYPE_*.
Changes in v13:
- Update to SFrame V3:
- Adjust to SFRAME_V3_*() macros and macro/define renames.
- Adjust to struct sframe_fde_v3 rename.
- Adjust to s64 FDE function start offset.
- Rename local variables fde_type to fde_pctype.
- Add and maintain struct sframe_fde_internal field u8 info2.
- Adjust to FDE split into function descriptor entry
(struct sframe_fde_v3) and attributes (struct sframe_fde_v3).
- Rename offset_count/offset_size to dataword_count/dataword_count.
- Limit __read_fre() to SFrame V3 regular FDEs (FDE_TYPE_REGULAR). A
subsequent patch will add support for flexible FDEs (FDE_TYPE_FLEX).
- Rename struct sframe_fde_internal field func_start_addr to func_addr.
- Add support u64/s64 in UNSAFE_GET_USER_INC() for s64 FDE function
start offset.
- Reduce indentation of assignments to fre.
- Reword commit message (my changes).
include/linux/sframe.h | 3 +
kernel/unwind/sframe.c | 362 ++++++++++++++++++++++++++++++++++-
kernel/unwind/sframe_debug.h | 35 ++++
3 files changed, 396 insertions(+), 4 deletions(-)
create mode 100644 kernel/unwind/sframe_debug.h
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 38047760e252..9a72209696f9 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -4,6 +4,7 @@
#include <linux/mm_types.h>
#include <linux/srcu.h>
+#include <linux/unwind_user_types.h>
#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
@@ -30,6 +31,7 @@ extern void sframe_free_mm(struct mm_struct *mm);
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end);
extern int sframe_remove_section(unsigned long sframe_addr);
+extern int sframe_find(unsigned long ip, struct unwind_user_frame *frame);
static inline bool current_has_sframe(void)
{
@@ -48,6 +50,7 @@ static inline int sframe_add_section(unsigned long sframe_start, unsigned long s
return -ENOSYS;
}
static inline int sframe_remove_section(unsigned long sframe_addr) { return -ENOSYS; }
+static inline int sframe_find(unsigned long ip, struct unwind_user_frame *frame) { return -ENOSYS; }
static inline bool current_has_sframe(void) { return false; }
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 46dba3cb016d..2de29c836f6b 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -15,12 +15,355 @@
#include <linux/unwind_user_types.h>
#include "sframe.h"
-
-#define dbg(fmt, ...) \
- pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+#include "sframe_debug.h"
+
+struct sframe_fde_internal {
+ unsigned long func_addr;
+ u32 func_size;
+ u32 fda_off;
+ u32 fres_off;
+ u16 fres_num;
+ u8 info;
+ u8 info2;
+ u8 rep_size;
+};
+
+struct sframe_fre_internal {
+ unsigned int size;
+ u32 ip_off;
+ s32 cfa_off;
+ s32 ra_off;
+ s32 fp_off;
+ u8 info;
+};
DEFINE_STATIC_SRCU(sframe_srcu);
+static __always_inline unsigned char fre_type_to_size(unsigned char fre_type)
+{
+ if (fre_type > 2)
+ return 0;
+ return 1 << fre_type;
+}
+
+static __always_inline unsigned char dataword_size_enum_to_size(unsigned char dataword_size)
+{
+ if (dataword_size > 2)
+ return 0;
+ return 1 << dataword_size;
+}
+
+static __always_inline int __read_fde(struct sframe_section *sec,
+ unsigned int fde_num,
+ struct sframe_fde_internal *fde)
+{
+ unsigned long fde_addr, fda_addr, func_start, func_end;
+ struct sframe_fde_v3 _fde;
+ struct sframe_fda_v3 _fda;
+ unsigned char fde_pctype;
+
+ fde_addr = sec->fdes_start + (fde_num * sizeof(struct sframe_fde_v3));
+ unsafe_copy_from_user(&_fde, (void __user *)fde_addr,
+ sizeof(struct sframe_fde_v3), Efault);
+
+ func_start = fde_addr + _fde.func_start_off;
+ func_end = func_start + _fde.func_size;
+ if (func_start < sec->text_start || func_end > sec->text_end)
+ return -EFAULT;
+
+ fda_addr = sec->fres_start + _fde.fres_off;
+ if (fda_addr + sizeof(struct sframe_fda_v3) > sec->fres_end)
+ return -EFAULT;
+ unsafe_copy_from_user(&_fda, (void __user *)fda_addr,
+ sizeof(struct sframe_fda_v3), Efault);
+
+ fde_pctype = SFRAME_V3_FDE_PCTYPE(_fda.info);
+ if (fde_pctype != SFRAME_FDE_PCTYPE_INC &&
+ fde_pctype != SFRAME_FDE_PCTYPE_MASK)
+ return -EINVAL;
+ if (fde_pctype == SFRAME_FDE_PCTYPE_MASK && !_fda.rep_size)
+ return -EINVAL;
+ if (_fda.fres_num > _fde.func_size)
+ return -EINVAL;
+
+ fde->func_addr = func_start;
+ fde->func_size = _fde.func_size;
+ fde->fda_off = _fde.fres_off;
+ fde->fres_off = _fde.fres_off + sizeof(struct sframe_fda_v3);
+ fde->fres_num = _fda.fres_num;
+ fde->info = _fda.info;
+ fde->info2 = _fda.info2;
+ fde->rep_size = _fda.rep_size;
+
+ return 0;
+
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int __find_fde(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
+{
+ unsigned long func_addr_low = 0, func_addr_high = ULONG_MAX;
+ struct sframe_fde_v3 __user *first, *low, *high, *found = NULL;
+ int ret;
+
+ first = (void __user *)sec->fdes_start;
+ low = first;
+ high = first + sec->num_fdes - 1;
+
+ while (low <= high) {
+ struct sframe_fde_v3 __user *mid;
+ s64 func_off;
+ unsigned long func_addr;
+
+ mid = low + ((high - low) / 2);
+
+ unsafe_get_user(func_off, (s64 __user *)mid, Efault);
+ func_addr = (unsigned long)mid + func_off;
+
+ if (ip >= func_addr) {
+ if (func_addr < func_addr_low)
+ return -EINVAL;
+
+ func_addr_low = func_addr;
+
+ found = mid;
+ low = mid + 1;
+ } else {
+ if (func_addr > func_addr_high)
+ return -EINVAL;
+
+ func_addr_high = func_addr;
+
+ high = mid - 1;
+ }
+ }
+
+ if (!found)
+ return -ENOENT;
+
+ ret = __read_fde(sec, found - first, fde);
+ if (ret)
+ return ret;
+
+ /* make sure it's not in a gap */
+ if (ip < fde->func_addr || ip >= fde->func_addr + fde->func_size)
+ return -ENOENT;
+
+ return 0;
+
+Efault:
+ return -EFAULT;
+}
+
+#define ____UNSAFE_GET_USER_INC(to, from, type, label) \
+({ \
+ type __to; \
+ unsafe_get_user(__to, (type __user *)from, label); \
+ from += sizeof(__to); \
+ to = __to; \
+})
+
+#define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s) \
+({ \
+ switch (size) { \
+ case 1: \
+ ____UNSAFE_GET_USER_INC(to, from, u_or_s##8, label); \
+ break; \
+ case 2: \
+ ____UNSAFE_GET_USER_INC(to, from, u_or_s##16, label); \
+ break; \
+ case 4: \
+ ____UNSAFE_GET_USER_INC(to, from, u_or_s##32, label); \
+ break; \
+ default: \
+ return -EFAULT; \
+ } \
+})
+
+#define UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label) \
+ __UNSAFE_GET_USER_INC(to, from, size, label, u)
+
+#define UNSAFE_GET_USER_SIGNED_INC(to, from, size, label) \
+ __UNSAFE_GET_USER_INC(to, from, size, label, s)
+
+#define UNSAFE_GET_USER_INC(to, from, size, label) \
+ _Generic(to, \
+ u8 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
+ u16 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
+ u32 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
+ u64 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
+ s8 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
+ s16 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
+ s32 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
+ s64 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
+
+static __always_inline int __read_fre(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long fre_addr,
+ struct sframe_fre_internal *fre)
+{
+ unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2);
+ unsigned char fde_pctype = SFRAME_V3_FDE_PCTYPE(fde->info);
+ unsigned char fre_type = SFRAME_V3_FDE_FRE_TYPE(fde->info);
+ unsigned char dataword_count, dataword_size;
+ s32 cfa_off, ra_off, fp_off;
+ unsigned long cur = fre_addr;
+ unsigned char addr_size;
+ unsigned int fre_size;
+ u32 ip_off;
+ u8 info;
+
+ addr_size = fre_type_to_size(fre_type);
+ if (!addr_size)
+ return -EINVAL;
+
+ if (fre_addr + addr_size + 1 > sec->fres_end)
+ return -EFAULT;
+
+ UNSAFE_GET_USER_INC(ip_off, cur, addr_size, Efault);
+ if ((fde_pctype == SFRAME_FDE_PCTYPE_INC && ip_off >= fde->func_size) ||
+ (fde_pctype == SFRAME_FDE_PCTYPE_MASK && ip_off >= fde->rep_size))
+ return -EINVAL;
+
+ UNSAFE_GET_USER_INC(info, cur, 1, Efault);
+ dataword_count = SFRAME_V3_FRE_DATAWORD_COUNT(info);
+ dataword_size = dataword_size_enum_to_size(SFRAME_V3_FRE_DATAWORD_SIZE(info));
+ if (!dataword_count || !dataword_size)
+ return -EINVAL;
+ fre_size = addr_size + 1 + (dataword_count * dataword_size);
+
+ if (cur + (dataword_count * dataword_size) > sec->fres_end)
+ return -EFAULT;
+
+ /* TODO: Support for flexible FDEs not implemented yet. */
+ if (fde_type != SFRAME_FDE_TYPE_DEFAULT)
+ return -EINVAL;
+
+ UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+ dataword_count--;
+
+ ra_off = sec->ra_off;
+ if (!ra_off) {
+ if (!dataword_count--)
+ return -EINVAL;
+
+ UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+ }
+
+ fp_off = sec->fp_off;
+ if (!fp_off && dataword_count) {
+ dataword_count--;
+ UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ }
+
+ if (dataword_count)
+ return -EINVAL;
+
+ fre->size = fre_size;
+ fre->ip_off = ip_off;
+ fre->cfa_off = cfa_off;
+ fre->ra_off = ra_off;
+ fre->fp_off = fp_off;
+ fre->info = info;
+
+ return 0;
+
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int __find_fre(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long ip,
+ struct unwind_user_frame *frame)
+{
+ unsigned char fde_pctype = SFRAME_V3_FDE_PCTYPE(fde->info);
+ struct sframe_fre_internal *fre, *prev_fre = NULL;
+ struct sframe_fre_internal fres[2];
+ unsigned long fre_addr;
+ bool which = false;
+ unsigned int i;
+ u32 ip_off;
+
+ ip_off = ip - fde->func_addr;
+
+ if (fde_pctype == SFRAME_FDE_PCTYPE_MASK)
+ ip_off %= fde->rep_size;
+
+ fre_addr = sec->fres_start + fde->fres_off;
+
+ for (i = 0; i < fde->fres_num; i++) {
+ int ret;
+
+ /*
+ * Alternate between the two fre_addr[] entries for 'fre' and
+ * 'prev_fre'.
+ */
+ fre = which ? fres : fres + 1;
+ which = !which;
+
+ ret = __read_fre(sec, fde, fre_addr, fre);
+ if (ret)
+ return ret;
+
+ fre_addr += fre->size;
+
+ if (prev_fre && fre->ip_off <= prev_fre->ip_off)
+ return -EINVAL;
+
+ if (fre->ip_off > ip_off)
+ break;
+
+ prev_fre = fre;
+ }
+
+ if (!prev_fre)
+ return -EINVAL;
+ fre = prev_fre;
+
+ frame->cfa_off = fre->cfa_off;
+ frame->ra_off = fre->ra_off;
+ frame->fp_off = fre->fp_off;
+ frame->use_fp = SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP;
+
+ return 0;
+}
+
+int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
+{
+ struct mm_struct *mm = current->mm;
+ struct sframe_section *sec;
+ struct sframe_fde_internal fde;
+ void __user *sframe_start;
+ int ret;
+
+ if (!mm)
+ return -EINVAL;
+
+ guard(srcu)(&sframe_srcu);
+
+ sec = mtree_load(&mm->sframe_mt, ip);
+ if (!sec)
+ return -ENOENT;
+
+ sframe_start = (void __user *)sec->sframe_start;
+ scoped_user_read_access_size(sframe_start,
+ sec->sframe_end - sec->sframe_start,
+ Efault) {
+ ret = __find_fde(sec, ip, &fde);
+ if (!ret)
+ ret = __find_fre(sec, &fde, ip, frame);
+ }
+
+ return ret;
+
+Efault:
+ return -EFAULT;
+}
+
static void free_section(struct sframe_section *sec)
{
kfree(sec);
@@ -32,6 +375,15 @@ static int sframe_read_header(struct sframe_section *sec)
struct sframe_header shdr;
unsigned int num_fdes;
+ /* SFrame V3 is only supported on 64-bit architectures */
+ BUILD_BUG_ON(!IS_ENABLED(CONFIG_64BIT));
+
+ /*
+ * Unaligned access to 16/32-bit SFrame FRE fields and datawords
+ * using unsafe_get_user() via UNSAFE_GET_USER_INC()
+ */
+ BUILD_BUG_ON(!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS));
+
if (copy_from_user(&shdr, (void __user *)sec->sframe_start, sizeof(shdr))) {
dbg("header usercopy failed\n");
return -EFAULT;
@@ -122,8 +474,10 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
sec->text_end = text_end;
ret = sframe_read_header(sec);
- if (ret)
+ if (ret) {
+ dbg_print_header(sec);
goto err_free;
+ }
ret = mtree_insert_range(sframe_mt, sec->text_start, sec->text_end - 1,
sec, GFP_KERNEL_ACCOUNT);
diff --git a/kernel/unwind/sframe_debug.h b/kernel/unwind/sframe_debug.h
new file mode 100644
index 000000000000..36352124cde8
--- /dev/null
+++ b/kernel/unwind/sframe_debug.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SFRAME_DEBUG_H
+#define _SFRAME_DEBUG_H
+
+#include <linux/sframe.h>
+#include "sframe.h"
+
+#ifdef CONFIG_DYNAMIC_DEBUG
+
+#define dbg(fmt, ...) \
+ pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+
+static __always_inline void dbg_print_header(struct sframe_section *sec)
+{
+ unsigned long fdes_end;
+
+ fdes_end = sec->fdes_start + (sec->num_fdes * sizeof(struct sframe_fde_v3));
+
+ dbg("SEC: sframe:0x%lx-0x%lx text:0x%lx-0x%lx "
+ "fdes:0x%lx-0x%lx fres:0x%lx-0x%lx "
+ "ra_off:%d fp_off:%d\n",
+ sec->sframe_start, sec->sframe_end, sec->text_start, sec->text_end,
+ sec->fdes_start, fdes_end, sec->fres_start, sec->fres_end,
+ sec->ra_off, sec->fp_off);
+}
+
+#else /* !CONFIG_DYNAMIC_DEBUG */
+
+#define dbg(args...) no_printk(args)
+
+static inline void dbg_print_header(struct sframe_section *sec) {}
+
+#endif /* !CONFIG_DYNAMIC_DEBUG */
+
+#endif /* _SFRAME_DEBUG_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 09/20] unwind_user/sframe: Add support for outermost frame indication
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
SFrame may represent an undefined return address (RA) as SFrame FRE
without any offsets as indication for an outermost frame.
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
kernel/unwind/sframe.c | 15 ++++++++++++++-
kernel/unwind/sframe.h | 1 +
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 2de29c836f6b..41ece3ca62a1 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -231,7 +231,7 @@ static __always_inline int __read_fre(struct sframe_section *sec,
UNSAFE_GET_USER_INC(info, cur, 1, Efault);
dataword_count = SFRAME_V3_FRE_DATAWORD_COUNT(info);
dataword_size = dataword_size_enum_to_size(SFRAME_V3_FRE_DATAWORD_SIZE(info));
- if (!dataword_count || !dataword_size)
+ if (!dataword_size)
return -EINVAL;
fre_size = addr_size + 1 + (dataword_count * dataword_size);
@@ -242,6 +242,17 @@ static __always_inline int __read_fre(struct sframe_section *sec,
if (fde_type != SFRAME_FDE_TYPE_DEFAULT)
return -EINVAL;
+ if (!dataword_count) {
+ /*
+ * A FRE without data words indicates RA undefined /
+ * outermost frame.
+ */
+ cfa_off = 0;
+ ra_off = 0;
+ fp_off = 0;
+ goto done;
+ }
+
UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
dataword_count--;
@@ -262,6 +273,7 @@ static __always_inline int __read_fre(struct sframe_section *sec,
if (dataword_count)
return -EINVAL;
+done:
fre->size = fre_size;
fre->ip_off = ip_off;
fre->cfa_off = cfa_off;
@@ -328,6 +340,7 @@ static __always_inline int __find_fre(struct sframe_section *sec,
frame->ra_off = fre->ra_off;
frame->fp_off = fre->fp_off;
frame->use_fp = SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP;
+ frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info);
return 0;
}
diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h
index fc2908e92c7b..ed111fd0d702 100644
--- a/kernel/unwind/sframe.h
+++ b/kernel/unwind/sframe.h
@@ -77,5 +77,6 @@ struct sframe_fda_v3 {
#define SFRAME_V3_FRE_DATAWORD_COUNT(info) (((info) >> 1) & 0xf)
#define SFRAME_V3_FRE_DATAWORD_SIZE(info) (((info) >> 5) & 0x3)
#define SFRAME_V3_AARCH64_FRE_MANGLED_RA_P(info) (((info) >> 7) & 0x1)
+#define SFRAME_V3_FRE_RA_UNDEFINED_P(info) (SFRAME_V3_FRE_DATAWORD_COUNT(info) == 0)
#endif /* _SFRAME_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 18/20] unwind_user/sframe: Duplicate registered .sframe section data on clone/fork
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
When duplicating a process' virtual memory mappings also duplicate all
of its registered .sframe sections stored in the per-mm maple tree to
enable stacktracing using sframe of the child process.
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v16:
- dup_mmap(): Drop unnecessary CONFIG_HAVE_UNWIND_USER_SFRAME #ifdefs.
(Sashiko AI)
- dup_mmap(): Call sframe_dup_mm() prior to arch_dup_mmap(), so that
comes last.
Changes in v15:
- New patch.
include/linux/sframe.h | 5 ++++
kernel/unwind/sframe.c | 48 ++++++++++++++++++++++++++++++++++++
kernel/unwind/sframe_debug.h | 7 ++++++
mm/mmap.c | 5 ++++
4 files changed, 65 insertions(+)
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index b79c5ec09229..91889b4fe3dd 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -28,6 +28,7 @@ struct sframe_section {
};
#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+extern int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm);
extern void sframe_free_mm(struct mm_struct *mm);
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
@@ -45,6 +46,10 @@ static inline bool current_has_sframe(void)
#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
#define INIT_MM_SFRAME
+static inline int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+ return 0;
+}
static inline void sframe_free_mm(struct mm_struct *mm) {}
static inline int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 7f439600b0f0..db88d993dff1 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -875,6 +875,54 @@ int sframe_remove_section(unsigned long sframe_start)
return 0;
}
+static void __sframe_dup_section(struct sframe_section *sec, struct sframe_section *oldsec)
+{
+ sec->sframe_start = oldsec->sframe_start;
+ sec->sframe_end = oldsec->sframe_end;
+ sec->text_start = oldsec->text_start;
+ sec->text_end = oldsec->text_end;
+
+ sec->fdes_start = oldsec->fdes_start;
+ sec->fres_start = oldsec->fres_start;
+ sec->fres_end = oldsec->fres_end;
+ sec->num_fdes = oldsec->num_fdes;
+
+ sec->ra_off = oldsec->ra_off;
+ sec->fp_off = oldsec->fp_off;
+
+ dbg_dup(sec, oldsec);
+}
+
+int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+ struct sframe_section *sec, *oldsec;
+ unsigned long index = 0;
+ int ret;
+
+ guard(srcu)(&sframe_srcu);
+
+ mt_for_each(&oldmm->sframe_mt, oldsec, index, ULONG_MAX) {
+ sec = kzalloc(sizeof(*sec), GFP_KERNEL_ACCOUNT);
+ if (!sec)
+ return -ENOMEM;
+
+ __sframe_dup_section(sec, oldsec);
+
+ ret = mtree_insert_range(&mm->sframe_mt,
+ sec->text_start,
+ sec->text_end - 1,
+ sec, GFP_KERNEL_ACCOUNT);
+ if (ret)
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ free_section(sec);
+ return ret;
+}
+
void sframe_free_mm(struct mm_struct *mm)
{
struct sframe_section *sec;
diff --git a/kernel/unwind/sframe_debug.h b/kernel/unwind/sframe_debug.h
index a63e75cccc70..2503972155e8 100644
--- a/kernel/unwind/sframe_debug.h
+++ b/kernel/unwind/sframe_debug.h
@@ -48,6 +48,12 @@ static inline void dbg_init(struct sframe_section *sec)
sec->filename = kstrdup("(anonymous)", GFP_KERNEL_ACCOUNT);
}
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section *oldsec)
+{
+ if (oldsec->filename)
+ sec->filename = kstrdup(oldsec->filename, GFP_KERNEL_ACCOUNT);
+}
+
static inline void dbg_free(struct sframe_section *sec)
{
kfree(sec->filename);
@@ -61,6 +67,7 @@ static inline void dbg_free(struct sframe_section *sec)
static inline void dbg_print_header(struct sframe_section *sec) {}
static inline void dbg_init(struct sframe_section *sec) {}
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section *oldsec) {}
static inline void dbg_free(struct sframe_section *sec) {}
#endif /* !CONFIG_DYNAMIC_DEBUG */
diff --git a/mm/mmap.c b/mm/mmap.c
index 5754d1c36462..8715be691488 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -48,6 +48,7 @@
#include <linux/sched/mm.h>
#include <linux/ksm.h>
#include <linux/memfd.h>
+#include <linux/sframe.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
@@ -1844,6 +1845,9 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
goto loop_out;
}
}
+ retval = sframe_dup_mm(mm, oldmm);
+ if (retval)
+ goto loop_out;
/* a new mm has just been created */
retval = arch_dup_mmap(oldmm, mm);
loop_out:
@@ -1893,6 +1897,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
vm_unacct_memory(charge);
}
__mt_destroy(&mm->mm_mt);
+ sframe_free_mm(mm);
/*
* The mm_struct is going to exit, but the locks will be dropped
* first. Set the mm_struct as unstable is advisable as it is
--
2.51.0
^ permalink raw reply related
* [PATCH v16 03/20] unwind_user/sframe: Store .sframe section data in per-mm maple tree
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
Associate an .sframe section with its mm by adding it to a per-mm maple
tree which is indexed by the corresponding text address range. A single
.sframe section can be associated with multiple text ranges.
[ Jens Remus: Minor cleanups. Reword commit subject/message. ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v16:
- Move SRCU definitions from patch "unwind_user/sframe: Add support for
reading .sframe contents" here. (Sashiko AI)
Changes in v15:
- Fix text section end passed to mtree_insert_range() to be inclusive.
(Sashiko AI)
- sframe_remove_section(): Add guard(srcu) to guard access to
sec->sframe_start. This also guards access to sec->filename
in __sframe_remove_section(). (Sashiko AI)
- Use GFP_KERNEL_ACCOUNT instead of GFP_KERNEL (see
memory-allocation.rst, section "Get Free Page flags"). (Sashiko AI)
arch/x86/include/asm/mmu.h | 2 +-
include/linux/mm_types.h | 3 ++
include/linux/sframe.h | 18 ++++++++++
kernel/fork.c | 10 ++++++
kernel/unwind/sframe.c | 68 ++++++++++++++++++++++++++++++++++++--
mm/init-mm.c | 2 ++
6 files changed, 99 insertions(+), 4 deletions(-)
diff --git a/arch/x86/include/asm/mmu.h b/arch/x86/include/asm/mmu.h
index 0fe9c569d171..227a32899a59 100644
--- a/arch/x86/include/asm/mmu.h
+++ b/arch/x86/include/asm/mmu.h
@@ -87,7 +87,7 @@ typedef struct {
.context = { \
.ctx_id = 1, \
.lock = __MUTEX_INITIALIZER(mm.context.lock), \
- }
+ },
void leave_mm(void);
#define leave_mm leave_mm
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index a308e2c23b82..c1505356b6fc 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1424,6 +1424,9 @@ struct mm_struct {
#ifdef CONFIG_MM_ID
mm_id_t mm_id;
#endif /* CONFIG_MM_ID */
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+ struct maple_tree sframe_mt;
+#endif
} __randomize_layout;
/*
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 0642595534f9..38047760e252 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -2,9 +2,14 @@
#ifndef _LINUX_SFRAME_H
#define _LINUX_SFRAME_H
+#include <linux/mm_types.h>
+#include <linux/srcu.h>
+
#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
struct sframe_section {
+ struct rcu_head rcu;
+
unsigned long sframe_start;
unsigned long sframe_end;
unsigned long text_start;
@@ -19,18 +24,31 @@ struct sframe_section {
signed char fp_off;
};
+#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+extern void sframe_free_mm(struct mm_struct *mm);
+
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end);
extern int sframe_remove_section(unsigned long sframe_addr);
+static inline bool current_has_sframe(void)
+{
+ struct mm_struct *mm = current->mm;
+
+ return mm && !mtree_empty(&mm->sframe_mt);
+}
+
#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
+#define INIT_MM_SFRAME
+static inline void sframe_free_mm(struct mm_struct *mm) {}
static inline int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end)
{
return -ENOSYS;
}
static inline int sframe_remove_section(unsigned long sframe_addr) { return -ENOSYS; }
+static inline bool current_has_sframe(void) { return false; }
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
diff --git a/kernel/fork.c b/kernel/fork.c
index 5f3fdfdb14c7..8d8195561c95 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -110,6 +110,7 @@
#include <linux/tick.h>
#include <linux/unwind_deferred.h>
#include <linux/pgalloc.h>
+#include <linux/sframe.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
@@ -735,6 +736,7 @@ void __mmdrop(struct mm_struct *mm)
mm_pasid_drop(mm);
mm_destroy_cid(mm);
percpu_counter_destroy_many(mm->rss_stat, NR_MM_COUNTERS);
+ sframe_free_mm(mm);
free_mm(mm);
}
@@ -1072,6 +1074,13 @@ static void mmap_init_lock(struct mm_struct *mm)
#endif
}
+static void mm_init_sframe(struct mm_struct *mm)
+{
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+ mt_init(&mm->sframe_mt);
+#endif
+}
+
static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
struct user_namespace *user_ns)
{
@@ -1100,6 +1109,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
mm->pmd_huge_pte = NULL;
#endif
mm_init_uprobes_state(mm);
+ mm_init_sframe(mm);
hugetlb_count_init(mm);
mm_flags_clear_all(mm);
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index d24e9d4f8bef..46dba3cb016d 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -19,6 +19,8 @@
#define dbg(fmt, ...) \
pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+DEFINE_STATIC_SRCU(sframe_srcu);
+
static void free_section(struct sframe_section *sec)
{
kfree(sec);
@@ -81,6 +83,7 @@ static int sframe_read_header(struct sframe_section *sec)
int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end)
{
+ struct maple_tree *sframe_mt = ¤t->mm->sframe_mt;
struct vm_area_struct *sframe_vma, *text_vma;
struct mm_struct *mm = current->mm;
struct sframe_section *sec;
@@ -122,15 +125,74 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
if (ret)
goto err_free;
- /* TODO nowhere to store it yet - just free it and return an error */
- ret = -ENOSYS;
+ ret = mtree_insert_range(sframe_mt, sec->text_start, sec->text_end - 1,
+ sec, GFP_KERNEL_ACCOUNT);
+ if (ret) {
+ dbg("mtree_insert_range failed: text=%lx-%lx\n",
+ sec->text_start, sec->text_end);
+ goto err_free;
+ }
+
+ return 0;
err_free:
free_section(sec);
return ret;
}
+static void sframe_free_srcu(struct rcu_head *rcu)
+{
+ struct sframe_section *sec = container_of(rcu, struct sframe_section, rcu);
+
+ free_section(sec);
+}
+
+static int __sframe_remove_section(struct mm_struct *mm,
+ struct sframe_section *sec)
+{
+ if (!mtree_erase(&mm->sframe_mt, sec->text_start)) {
+ dbg("mtree_erase failed: text=%lx\n", sec->text_start);
+ return -EINVAL;
+ }
+
+ call_srcu(&sframe_srcu, &sec->rcu, sframe_free_srcu);
+
+ return 0;
+}
+
int sframe_remove_section(unsigned long sframe_start)
{
- return -ENOSYS;
+ struct mm_struct *mm = current->mm;
+ struct sframe_section *sec;
+ unsigned long index = 0;
+ bool found = false;
+ int ret = 0;
+
+ guard(srcu)(&sframe_srcu);
+
+ mt_for_each(&mm->sframe_mt, sec, index, ULONG_MAX) {
+ if (sec->sframe_start == sframe_start) {
+ found = true;
+ ret |= __sframe_remove_section(mm, sec);
+ }
+ }
+
+ if (!found || ret)
+ return -EINVAL;
+
+ return 0;
+}
+
+void sframe_free_mm(struct mm_struct *mm)
+{
+ struct sframe_section *sec;
+ unsigned long index = 0;
+
+ if (!mm)
+ return;
+
+ mt_for_each(&mm->sframe_mt, sec, index, ULONG_MAX)
+ free_section(sec);
+
+ mtree_destroy(&mm->sframe_mt);
}
diff --git a/mm/init-mm.c b/mm/init-mm.c
index c5556bb9d5f0..77909139162e 100644
--- a/mm/init-mm.c
+++ b/mm/init-mm.c
@@ -11,6 +11,7 @@
#include <linux/atomic.h>
#include <linux/user_namespace.h>
#include <linux/iommu.h>
+#include <linux/sframe.h>
#include <asm/mmu.h>
#ifndef INIT_MM_CONTEXT
@@ -49,6 +50,7 @@ struct mm_struct init_mm = {
#endif
.flexible_array = MM_STRUCT_FLEXIBLE_ARRAY_INIT,
INIT_MM_CONTEXT(init_mm)
+ INIT_MM_SFRAME
};
void setup_initial_init_mm(void *start_code, void *end_code,
--
2.51.0
^ permalink raw reply related
* [PATCH v16 00/20] unwind_deferred: Implement sframe handling
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
This is the implementation of parsing the SFrame V3 stack trace information
from an .sframe section in an ELF file. It's a continuation of Josh's and
Steve's work that can be found here:
https://lore.kernel.org/all/cover.1737511963.git.jpoimboe@kernel.org/
https://lore.kernel.org/all/20250827201548.448472904@kernel.org/
Currently the only way to get a user space stack trace from a stack
walk (and not just copying large amount of user stack into the kernel
ring buffer) is to use frame pointers. This has a few issues. The biggest
one is that compiling frame pointers into every application and library
has been shown to cause performance overhead.
Another issue is that the format of the frames may not always be consistent
between different compilers and some architectures (s390) has no defined
format to do a reliable stack walk. The only way to perform user space
profiling on these architectures is to copy the user stack into the kernel
buffer.
SFrame [1] is now supported in binutils (x86-64, ARM64, and s390). There is
discussions going on about supporting SFrame in LLVM. SFrame acts more like
ORC, and lives in the ELF executable file as its own section. Like ORC it
has two tables where the first table is sorted by instruction pointers (IP)
and using the current IP and finding it's entry in the first table, it will
take you to the second table which will tell you where the return address
of the current function is located and then you can use that address to
look it up in the first table to find the return address of that function,
and so on. This performs a user space stack walk.
Now because the .sframe section lives in the ELF file it needs to be faulted
into memory when it is used. This means that walking the user space stack
requires being in a faultable context. As profilers like perf request a stack
trace in interrupt or NMI context, it cannot do the walking when it is
requested. Instead it must be deferred until it is safe to fault in user
space. One place this is known to be safe is when the task is about to return
back to user space.
This series makes the deferred unwind user code implement SFrame format V3
and enables it on x86-64.
[1]: https://sourceware.org/binutils/wiki/sframe
This series applies on top of v7.1-rc4 tag:
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git v7.1-rc4
The to be stack-traced user space programs (and libraries) need to be
built with the recent SFrame stack trace information format V3, as
generated by binutils 2.46+ with assembler option --gsframe-3.
Namhyung Kim's related perf tools deferred callchain support can be used
for testing ("perf record --call-graph fp,defer" and "perf report/script").
Changes in v16 (see patch notes for details):
- Address Sashiko AI review feedback.
- Move SRCU definitions between patches.
- __read_fre(): Convert user read access to scope-based cleanup.
- sframe_validate_section(): Allow for a FDE[0] function start address
of zero.
- sframe_validate_section(): Replace alternation between two FREs with
simpler logic used for FDE and use a prev_ip_off.
- dup_mmap(): Drop unnecessary CONFIG_HAVE_UNWIND_USER_SFRAME #ifdefs.
- dup_mmap(): Call sframe_dup_mm() prior to arch_dup_mmap().
Changes in v15 (see patch notes for details):
- Rebase on v7.1-rc4.
- New patch to duplicate registered .sframe section data on clone/fork.
- Address Sashiko AI review feedback:
- Fix sframe end passed to mtree_insert_range().
- Fix outermost frame (FRE without datawords) handling.
- Use GFP_KERNEL_ACCOUNT instead of GFP_KERNEL.
- Improve text/sframe section start/end validation.
- Always use guard(srcu) when accessing struct sframe_section fields.
- Validate FDE repetition size for PCTYPE_MASK FDEs to be non-zero to
prevent division by zero.
- Only add sframe for text that is PT_LOAD in addition to PF_X.
- Use pr_debug_once() instead of WARN_ON_ONCE() to prevent user-
triggered warning/panic.
- Add support for SP/FP-based CFA recovery rules with
dereferencing.
- Reject FRE control word with reserved_p=1.
- x86-64: Fail unwind_user_get_reg() if !user_64bit_mode().
- Validate FDE PC type for supported values (i.e. PCTYPE_INC or
PCTYPE_MASK).
- Validate FDE function end against text end.
- Validate FDE's number of FREs to be less or equal to FDE's function
size, as each FRE must cover at least one byte. (Indu)
- Validate FRE function offset against FDE repetition size
for PCTYPE_MASK.
- Change type of struct sframe_fde_internal field fres_num to the one of
struct sframe_fda_v3 field fres_num.
- Return RC of sframe_init_[cfa_]rule_data() if bad RC.
- Normalize error code usage (.sframe is removed for all but ENOENT):
ENOENT: No sframe or no FDE for IP found
(FDE found but no FRE found is EINVAL)
EFAULT: Bad address
EINVAL: Invalid input or sframe
- Build-time checks for config options:
- 64BIT: SFrame V3 only supports 64-bit architectures.
- HAVE_EFFICIENT_UNALIGNED_ACCESS: Unaligned access to 16/32-bit
SFrame FRE fields and datawords using unsafe_get_user(). (Steven)
- Add pr_debug_once() when restoring CFA/FP/RA from an unsupported
register number.
Changes in v14 (see patch notes for details):
- Rebase on v7.1-rc2.
- Correct SFRAME_V3_FDE_TYPE_MASK value.
- Fix FDE function start address check in __read_fde().
- Rename SFrame V3 definitions accoring to final specification. (Indu)
- Improve comments on why UNWIND_USER_RULE_CFA_OFFSET is not
implemented. (Mark Rutland)
- Add/update/improve sframe debug messages.
- Add generic and arch-specific unwind_user.h to MAINTAINERS.
- Add arch-specific unwind_user_sframe.h to MAINTAINERS.
Changes in v13 (see patch notes for details):
- Add support for SFrame V3, including its new flexible FDEs. SFrame V2
is not supported.
Changes in v12 (see patch notes for details):
- Adjust to Peter's latest undwind user enhancements.
- Simplify logic by using an internal SFrame FDE representation, whose
FDE function start address field is an address instead of a PC-relative
offset (from FDE).
- Rename struct sframe_fre to sframe_fre_internal to align with
struct sframe_fde_internal.
- Remove unused pt_regs from unwind_user_next_common() and its
callers. (Peter)
- Simplify unwind_user_next_sframe(). (Peter)
- Fix a few checkpatch errors and warnings.
- Minor cleanups (e.g. move includes, fix indentation).
Changes in v11:
- Support for SFrame V2 PC-relative FDE function start address.
- Support for SFrame V2 representing RA undefined as indication for
outermost frames.
Patch 1 (new in v14), as a preparatory cleanup, adds the generic and
arch-specific unwind_user.h to MAINTAINERS.
Patches 2, 5, 12, and 19 have been updated to exclusively support the
latest SFrame V3 stack trace information format, that is generated by
binutils 2.46+. Old SFrame V2 sections get rejected with dynamic debug
message "bad/unsupported sframe header".
Patches 8 and 9 add support to unwind user (sframe) for outermost frames.
Patches 13-16 add support to unwind user (sframe) for the new SFrame V3
flexible FDEs.
Patch 17 improves the performance of searching the SFrame FRE for an IP.
Patch 18 (new in v15) duplicates registered .sframe section data on
clone/fork from the parent to the child process.
Patch 20 is for test purposes only and will get replaced by a new
syscall, that Steven is working on:
[RFC][PATCH] unwind: Add stacktrace_setup system call
https://lore.kernel.org/all/20260429114355.6c712e6a@gandalf.local.home/
Regards,
Jens
Jens Remus (9):
unwind_user: Add generic and arch-specific headers to MAINTAINERS
unwind_user: Stop when reaching an outermost frame
unwind_user/sframe: Add support for outermost frame indication
unwind_user: Enable archs that pass RA in a register
unwind_user: Flexible FP/RA recovery rules
unwind_user: Flexible CFA recovery rules
unwind_user/sframe: Add support for SFrame V3 flexible FDEs
unwind_user/sframe: Separate reading of FRE from reading of FRE data
words
unwind_user/sframe: Duplicate registered .sframe section data on
clone/fork
Josh Poimboeuf (11):
unwind_user/sframe: Add support for reading .sframe headers
unwind_user/sframe: Store .sframe section data in per-mm maple tree
x86/uaccess: Add unsafe_copy_from_user() implementation
unwind_user/sframe: Add support for reading .sframe contents
unwind_user/sframe: Detect .sframe sections in executables
unwind_user/sframe: Wire up unwind_user to sframe
unwind_user/sframe: Remove .sframe section on detected corruption
unwind_user/sframe: Show file name in debug output
unwind_user/sframe: Add .sframe validation option
unwind_user/sframe/x86: Enable sframe unwinding on x86
unwind_user/sframe: Add prctl() interface for registering .sframe
sections
MAINTAINERS | 4 +
arch/Kconfig | 23 +
arch/x86/Kconfig | 1 +
arch/x86/include/asm/mmu.h | 2 +-
arch/x86/include/asm/uaccess.h | 39 +-
arch/x86/include/asm/unwind_user.h | 74 +-
arch/x86/include/asm/unwind_user_sframe.h | 12 +
fs/binfmt_elf.c | 48 +-
include/linux/mm_types.h | 3 +
include/linux/sframe.h | 65 ++
include/linux/unwind_user.h | 20 +
include/linux/unwind_user_types.h | 50 +-
include/uapi/linux/elf.h | 1 +
include/uapi/linux/prctl.h | 4 +
kernel/fork.c | 10 +
kernel/sys.c | 9 +
kernel/unwind/Makefile | 3 +-
kernel/unwind/sframe.c | 938 ++++++++++++++++++++++
kernel/unwind/sframe.h | 88 ++
kernel/unwind/sframe_debug.h | 75 ++
kernel/unwind/user.c | 133 ++-
mm/init-mm.c | 2 +
mm/mmap.c | 5 +
23 files changed, 1571 insertions(+), 38 deletions(-)
create mode 100644 arch/x86/include/asm/unwind_user_sframe.h
create mode 100644 include/linux/sframe.h
create mode 100644 kernel/unwind/sframe.c
create mode 100644 kernel/unwind/sframe.h
create mode 100644 kernel/unwind/sframe_debug.h
--
2.51.0
^ permalink raw reply
* [PATCH v16 19/20] unwind_user/sframe/x86: Enable sframe unwinding on x86
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
The x86 SFrame V3 implementation works fairly well, starting with
binutils 2.46. Enable it.
[ Jens Remus: Reword commit message for SFrame V3, starting with
binutils 2.46. ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- unwind_user_get_reg(): Fail if !user_64bit_mode(). (Sashiko AI)
- unwind_user_get_reg(): Simplify guarding using CONFIG_X86_64.
- unwind_user_get_reg(): Add pr_debug_once() if unsupported register
number.
Changes in v14:
- Drop superfluous empty line in unwind_user_get_reg().
Changes in v13:
- Naive implementation of unwind_user_get_reg() to support SFrame V3
flexible FDEs (e.g. used to represent DRAP pattern).
- Define SFRAME_REG_SP and SFRAME_REG_FP to the respective x86-64
DWARF register numbers.
- Reword commit message for SFrame V3 and (upcoming) binutils 2.46.
arch/x86/Kconfig | 1 +
arch/x86/include/asm/unwind_user.h | 39 +++++++++++++++++++++++
arch/x86/include/asm/unwind_user_sframe.h | 12 +++++++
3 files changed, 52 insertions(+)
create mode 100644 arch/x86/include/asm/unwind_user_sframe.h
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index f3f7cb01d69d..51286dfdb5f4 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -302,6 +302,7 @@ config X86
select HAVE_UACCESS_VALIDATION if HAVE_OBJTOOL
select HAVE_UNSTABLE_SCHED_CLOCK
select HAVE_UNWIND_USER_FP if X86_64
+ select HAVE_UNWIND_USER_SFRAME if X86_64
select HAVE_USER_RETURN_NOTIFIER
select HAVE_GENERIC_VDSO
select VDSO_GETRANDOM if X86_64
diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h
index f38f7c5ff1de..942475235431 100644
--- a/arch/x86/include/asm/unwind_user.h
+++ b/arch/x86/include/asm/unwind_user.h
@@ -15,6 +15,45 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
return user_64bit_mode(regs) ? 8 : 4;
}
+#ifdef CONFIG_X86_64
+
+static inline int unwind_user_get_reg(unsigned long *val, unsigned int regnum)
+{
+ struct pt_regs *regs = task_pt_regs(current);
+
+ /* SFrame only supports x86-64 */
+ if (!user_64bit_mode(regs))
+ return -EINVAL;
+
+ switch (regnum) {
+ /* DWARF register numbers 0..15 */
+ case 0: *val = regs->ax; break;
+ case 1: *val = regs->dx; break;
+ case 2: *val = regs->cx; break;
+ case 3: *val = regs->bx; break;
+ case 4: *val = regs->si; break;
+ case 5: *val = regs->di; break;
+ case 6: *val = regs->bp; break;
+ case 7: *val = regs->sp; break;
+ case 8: *val = regs->r8; break;
+ case 9: *val = regs->r9; break;
+ case 10: *val = regs->r10; break;
+ case 11: *val = regs->r11; break;
+ case 12: *val = regs->r12; break;
+ case 13: *val = regs->r13; break;
+ case 14: *val = regs->r14; break;
+ case 15: *val = regs->r15; break;
+ default:
+ pr_debug_once("%s (%d): unwind_user_get_reg(%u): unsupported register number\n",
+ current->comm, current->pid, regnum);
+ return -EINVAL;
+ }
+ return 0;
+}
+#define unwind_user_get_reg unwind_user_get_reg
+
+#endif /* CONFIG_X86_64 */
+
#endif /* CONFIG_UNWIND_USER */
#ifdef CONFIG_HAVE_UNWIND_USER_FP
diff --git a/arch/x86/include/asm/unwind_user_sframe.h b/arch/x86/include/asm/unwind_user_sframe.h
new file mode 100644
index 000000000000..d828ae1a4aac
--- /dev/null
+++ b/arch/x86/include/asm/unwind_user_sframe.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_X86_UNWIND_USER_SFRAME_H
+#define _ASM_X86_UNWIND_USER_SFRAME_H
+
+#ifdef CONFIG_X86_64
+
+#define SFRAME_REG_SP 7
+#define SFRAME_REG_FP 6
+
+#endif
+
+#endif /* _ASM_X86_UNWIND_USER_SFRAME_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 15/20] unwind_user: Flexible CFA recovery rules
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
To enable support for SFrame V3 flexible FDEs with a subsequent patch,
add support for the following flexible Canonical Frame Address (CFA)
recovery rules:
CFA = SP + offset
CFA = *(SP + offset)
CFA = FP + offset
CFA = *(FP + offset)
CFA = register + offset
CFA = *(register + offset)
Note that CFA recovery rules that use arbitrary register contents are
only valid when in the topmost frame, as their contents are otherwise
unknown.
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- enum unwind_user_cfa_rule, unwind_user_next_common(): Add support for
SP/FP-based CFA recovery rules with dereferencing. (Sashiko AI)
arch/x86/include/asm/unwind_user.h | 12 ++++++++----
include/linux/unwind_user_types.h | 22 ++++++++++++++++++++--
kernel/unwind/sframe.c | 15 +++++++++++++--
kernel/unwind/user.c | 24 ++++++++++++++++++++----
4 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h
index 9c3417be4283..f38f7c5ff1de 100644
--- a/arch/x86/include/asm/unwind_user.h
+++ b/arch/x86/include/asm/unwind_user.h
@@ -20,7 +20,10 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
#ifdef CONFIG_HAVE_UNWIND_USER_FP
#define ARCH_INIT_USER_FP_FRAME(ws) \
- .cfa_off = 2*(ws), \
+ .cfa = { \
+ .rule = UNWIND_USER_CFA_RULE_FP_OFFSET,\
+ .offset = 2*(ws), \
+ }, \
.ra = { \
.rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
.offset = -1*(ws), \
@@ -29,11 +32,13 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
.rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
.offset = -2*(ws), \
}, \
- .use_fp = true, \
.outermost = false,
#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \
- .cfa_off = 1*(ws), \
+ .cfa = { \
+ .rule = UNWIND_USER_CFA_RULE_SP_OFFSET,\
+ .offset = 1*(ws), \
+ }, \
.ra = { \
.rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
.offset = -1*(ws), \
@@ -41,7 +46,6 @@ static inline int unwind_user_word_size(struct pt_regs *regs)
.fp = { \
.rule = UNWIND_USER_RULE_RETAIN,\
}, \
- .use_fp = false, \
.outermost = false,
static inline bool unwind_user_at_function_start(struct pt_regs *regs)
diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 0d02714a1b5d..c18be5b7d586 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -29,6 +29,25 @@ struct unwind_stacktrace {
#define UNWIND_USER_RULE_DEREF BIT(31)
+enum unwind_user_cfa_rule {
+ UNWIND_USER_CFA_RULE_SP_OFFSET, /* CFA = SP + offset */
+ UNWIND_USER_CFA_RULE_FP_OFFSET, /* CFA = FP + offset */
+ UNWIND_USER_CFA_RULE_REG_OFFSET, /* CFA = register + offset */
+ /* DEREF variants */
+ UNWIND_USER_CFA_RULE_SP_OFFSET_DEREF = /* CFA = *(SP + offset) */
+ UNWIND_USER_CFA_RULE_SP_OFFSET | UNWIND_USER_RULE_DEREF,
+ UNWIND_USER_CFA_RULE_FP_OFFSET_DEREF = /* CFA = *(FP + offset) */
+ UNWIND_USER_CFA_RULE_FP_OFFSET | UNWIND_USER_RULE_DEREF,
+ UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF = /* CFA = *(register + offset) */
+ UNWIND_USER_CFA_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF,
+};
+
+struct unwind_user_cfa_rule_data {
+ enum unwind_user_cfa_rule rule;
+ s32 offset;
+ unsigned int regnum;
+};
+
enum unwind_user_rule {
UNWIND_USER_RULE_RETAIN, /* entity = entity */
UNWIND_USER_RULE_CFA_OFFSET, /* entity = CFA + offset */
@@ -47,10 +66,9 @@ struct unwind_user_rule_data {
};
struct unwind_user_frame {
- s32 cfa_off;
+ struct unwind_user_cfa_rule_data cfa;
struct unwind_user_rule_data ra;
struct unwind_user_rule_data fp;
- bool use_fp;
bool outermost;
};
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 29a874a67f32..daa97d8b0231 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -285,6 +285,18 @@ static __always_inline int __read_fre(struct sframe_section *sec,
return -EFAULT;
}
+static __always_inline void
+sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
+ unsigned char fre_info,
+ s32 offset)
+{
+ if (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre_info) == SFRAME_BASE_REG_FP)
+ cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
+ else
+ cfa_rule_data->rule = UNWIND_USER_CFA_RULE_SP_OFFSET;
+ cfa_rule_data->offset = offset;
+}
+
static __always_inline void
sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
s32 offset)
@@ -346,10 +358,9 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return -EINVAL;
fre = prev_fre;
- frame->cfa_off = fre->cfa_off;
+ sframe_init_cfa_rule_data(&frame->cfa, fre->info, fre->cfa_off);
sframe_init_rule_data(&frame->ra, fre->ra_off);
sframe_init_rule_data(&frame->fp, fre->fp_off);
- frame->use_fp = SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP;
frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info);
return 0;
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index c6a2abac78e0..447061b10613 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -53,14 +53,30 @@ static int unwind_user_next_common(struct unwind_user_state *state,
}
/* Get the Canonical Frame Address (CFA) */
- if (frame->use_fp) {
+ switch (frame->cfa.rule) {
+ case UNWIND_USER_CFA_RULE_SP_OFFSET:
+ case UNWIND_USER_CFA_RULE_SP_OFFSET_DEREF:
+ cfa = state->sp;
+ break;
+ case UNWIND_USER_CFA_RULE_FP_OFFSET:
+ case UNWIND_USER_CFA_RULE_FP_OFFSET_DEREF:
if (state->fp < state->sp)
return -EINVAL;
cfa = state->fp;
- } else {
- cfa = state->sp;
+ break;
+ case UNWIND_USER_CFA_RULE_REG_OFFSET:
+ case UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF:
+ if (!state->topmost || unwind_user_get_reg(&cfa, frame->cfa.regnum))
+ return -EINVAL;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
}
- cfa += frame->cfa_off;
+ cfa += frame->cfa.offset;
+ if (frame->cfa.rule & UNWIND_USER_RULE_DEREF &&
+ get_user_word(&cfa, cfa, 0, state->ws))
+ return -EINVAL;
/*
* Make sure that stack is not going in wrong direction. Allow SP
--
2.51.0
^ permalink raw reply related
* [PATCH v16 12/20] unwind_user/sframe: Add .sframe validation option
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
Add a debug feature to validate all .sframe sections when first loading
the file rather than on demand.
[ Jens Remus: Add support for SFrame V3. Add support for PC-relative
FDE function start offset. Adjust to rename of struct sframe_fre to
sframe_fre_internal. Use %#x/%#lx format specifiers. ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v16:
- sframe_validate_section(): Allow for a FDE[0] function start address
of zero. (Sashiko AI)
- sframe_validate_section(): Replace alternation between two FREs with
simpler logic used for FDE and use a prev_ip_off.
Changes in v15:
- sframe_validate_section(): Fix format specifier for number of FREs
in debug message. (Sashiko AI)
- Normalize error code usage (.sframe is removed for all but ENOENT):
ENOENT: No sframe or no FDE for IP found
(FDE found but no FRE is EINVAL)
EFAULT: Bad address
EINVAL: Invalid input or sframe
Changes in v14:
- Add debug message if safe_read_fde() fails.
- Update function names in debug messages.
- Uppercase terms FDE and FRE in debug messages.
Changes in v13:
- Update to SFrame V3:
- Print struct sframe_fde_internal fields fda_off and info2 in debug
message.
- Adjust to rename of struct sframe_fde_internal field func_start_addr
to func_addr.
- Use format strings "%#x" and "%#lx" instead of "0x%x" and "0x%lx".
- Reword commit message (my changes).
arch/Kconfig | 19 +++++++++
kernel/unwind/sframe.c | 92 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 111 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index 37549832bd1f..132249d342a3 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -490,6 +490,25 @@ config HAVE_UNWIND_USER_SFRAME
bool
select UNWIND_USER
+config SFRAME_VALIDATION
+ bool "Enable .sframe section debugging"
+ depends on HAVE_UNWIND_USER_SFRAME
+ depends on DYNAMIC_DEBUG
+ help
+ When adding an .sframe section for a task, validate the entire
+ section immediately rather than on demand.
+
+ This is a debug feature which is helpful for rooting out .sframe
+ section issues. If the .sframe section is corrupt, it will fail to
+ load immediately, with more information provided in dynamic printks.
+
+ This has a significant page cache footprint due to its reading of the
+ entire .sframe section for every loaded executable and shared
+ library. Also, it's done for all processes, even those which don't
+ get stack traced by the kernel. Not recommended for general use.
+
+ If unsure, say N.
+
config HAVE_PERF_REGS
bool
help
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 2cfa274cd8dc..e6d66ae8e7ac 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -384,6 +384,94 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
goto end;
}
+#ifdef CONFIG_SFRAME_VALIDATION
+
+static int safe_read_fde(struct sframe_section *sec,
+ unsigned int fde_num, struct sframe_fde_internal *fde)
+{
+ int ret;
+
+ if (!user_read_access_begin((void __user *)sec->sframe_start,
+ sec->sframe_end - sec->sframe_start))
+ return -EFAULT;
+ ret = __read_fde(sec, fde_num, fde);
+ user_read_access_end();
+ return ret;
+}
+
+static int safe_read_fre(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long fre_addr,
+ struct sframe_fre_internal *fre)
+{
+ int ret;
+
+ if (!user_read_access_begin((void __user *)sec->sframe_start,
+ sec->sframe_end - sec->sframe_start))
+ return -EFAULT;
+ ret = __read_fre(sec, fde, fre_addr, fre);
+ user_read_access_end();
+ return ret;
+}
+
+static int sframe_validate_section(struct sframe_section *sec)
+{
+ struct sframe_fde_internal fde;
+ unsigned long prev_func_addr;
+ unsigned int i;
+
+ for (i = 0; i < sec->num_fdes; i++) {
+ struct sframe_fre_internal fre;
+ unsigned long fre_addr;
+ u32 prev_ip_off;
+ unsigned int j;
+ int ret;
+
+ ret = safe_read_fde(sec, i, &fde);
+ if (ret) {
+ dbg_sec("safe_read_fde(%u) failed\n", i);
+ return ret;
+ }
+
+ if (i && fde.func_addr <= prev_func_addr) {
+ dbg_sec("FDE %u not sorted\n", i);
+ return -EINVAL;
+ }
+ prev_func_addr = fde.func_addr;
+
+ fre_addr = sec->fres_start + fde.fres_off;
+ for (j = 0; j < fde.fres_num; j++) {
+ ret = safe_read_fre(sec, &fde, fre_addr, &fre);
+ if (ret) {
+ dbg_sec("FDE %u: safe_read_fre(%u) failed\n", i, j);
+ dbg_sec("FDE: func_addr:%#lx func_size:%#x fda_off:%#x fres_off:%#x fres_num:%u info:%u info2:%u rep_size:%u\n",
+ fde.func_addr, fde.func_size,
+ fde.fda_off,
+ fde.fres_off, fde.fres_num,
+ fde.info, fde.info2,
+ fde.rep_size);
+ return ret;
+ }
+
+ if (j && fre.ip_off <= prev_ip_off) {
+ dbg_sec("FDE %u: FRE %u not sorted\n", i, j);
+ return -EINVAL;
+ }
+ prev_ip_off = fre.ip_off;
+
+ fre_addr += fre.size;
+ }
+ }
+
+ return 0;
+}
+
+#else /* !CONFIG_SFRAME_VALIDATION */
+
+static int sframe_validate_section(struct sframe_section *sec) { return 0; }
+
+#endif /* !CONFIG_SFRAME_VALIDATION */
+
static void free_section(struct sframe_section *sec)
{
dbg_free(sec);
@@ -502,6 +590,10 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
goto err_free;
}
+ ret = sframe_validate_section(sec);
+ if (ret)
+ goto err_free;
+
ret = mtree_insert_range(sframe_mt, sec->text_start, sec->text_end - 1,
sec, GFP_KERNEL_ACCOUNT);
if (ret) {
--
2.51.0
^ permalink raw reply related
* [PATCH v16 10/20] unwind_user/sframe: Remove .sframe section on detected corruption
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
To avoid continued attempted use of a bad .sframe section, remove it
on demand when the first sign of corruption is detected.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- sframe_find(): Align to normalized error code usage and remove .sframe
for all but ENOENT. Also remove if user_read_access_begin() fails.
kernel/unwind/sframe.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 41ece3ca62a1..e0eb2adf5a07 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -371,10 +371,15 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
ret = __find_fre(sec, &fde, ip, frame);
}
+end:
+ if (ret && ret != -ENOENT)
+ WARN_ON_ONCE(sframe_remove_section(sec->sframe_start));
+
return ret;
Efault:
- return -EFAULT;
+ ret = -EFAULT;
+ goto end;
}
static void free_section(struct sframe_section *sec)
--
2.51.0
^ permalink raw reply related
* [PATCH v16 02/20] unwind_user/sframe: Add support for reading .sframe headers
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
In preparation for unwinding user space stacks with sframe, add basic
sframe compile infrastructure and support for reading the .sframe
section header.
sframe_add_section() reads the header and unconditionally returns an
error, so it's not very useful yet. A subsequent patch will improve
that.
Link: https://lore.kernel.org/all/f27e8463783febfa0dabb0432a3dd6be8ad98412.1737511963.git.jpoimboe@kernel.org/
[ Jens Remus: Add support for SFrame V3. Add support for PC-relative
FDE function start offset. Cleanup includes and indentation. ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- Improve text/sframe section start/end validation. (Sashiko AI)
- Use GFP_KERNEL_ACCOUNT instead of GFP_KERNEL (see
memory-allocation.rst, section "Get Free Page flags"). (Sashiko AI)
Changes in v14:
- Rename SFRAME_FDE_TYPE_REGULAR to SFRAME_FDE_TYPE_DEFAULT to match
SFrame V3 specification. (Indu)
- Correct SFRAME_V3_FDE_TYPE_MASK value.
Changes in v13:
- Update to SFrame V3:
- Add and use SFRAME_VERSION_3 definition.
- Add helper macros to access SFrame V3 FDE type.
- Rename SFRAME_FUNC_*() macros to SFRAME_FDE_*().
- Rename SFRAME_FDE_TYPE_PC* defines to SFRAME_FDE_PCTYPE_* and
SFRAME_FUNC_FDE_TYPE() macro to SFRAME_V3_FDE_PCTYPE().
- Reword OFFSET to DATAWORD in SFRAME_FRE_OFFSET_{COUNT|SIZE}()
macros.
- Rename version-specific SFRAME_*() macros to SFRAME_V3_*().
- Update struct sframe_fde and rename to sframe_fde_v3:
- Change field start_addr from s32 to s64 and rename to
func_start_off.
- Change field fres_num from u32 to u16.
- New field u8 info2.
- Remove u16 padding field.
- Split FDE into function descriptor entry (struct sframe_fde_v3) and
attributes (struct sframe_fde_v3).
- Rename macro parameter "data" to "info" to hint at fde/fre info
word and wrap it in parenthesis.
- Group SFRAME_* definitions so that related ones are together.
- Reword commit message (my changes).
MAINTAINERS | 1 +
arch/Kconfig | 3 +
include/linux/sframe.h | 37 +++++++++++
kernel/unwind/Makefile | 3 +-
kernel/unwind/sframe.c | 136 +++++++++++++++++++++++++++++++++++++++++
kernel/unwind/sframe.h | 81 ++++++++++++++++++++++++
6 files changed, 260 insertions(+), 1 deletion(-)
create mode 100644 include/linux/sframe.h
create mode 100644 kernel/unwind/sframe.c
create mode 100644 kernel/unwind/sframe.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7434e9d7b33f..a9b42b67a88d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27876,6 +27876,7 @@ M: Steven Rostedt <rostedt@goodmis.org>
S: Maintained
F: arch/*/include/asm/unwind_user.h
F: include/asm-generic/unwind_user.h
+F: include/linux/sframe.h
F: include/linux/unwind*.h
F: kernel/unwind/
diff --git a/arch/Kconfig b/arch/Kconfig
index e86880045158..94b2d5e8e529 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -486,6 +486,9 @@ config HAVE_UNWIND_USER_FP
bool
select UNWIND_USER
+config HAVE_UNWIND_USER_SFRAME
+ bool
+
config HAVE_PERF_REGS
bool
help
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
new file mode 100644
index 000000000000..0642595534f9
--- /dev/null
+++ b/include/linux/sframe.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SFRAME_H
+#define _LINUX_SFRAME_H
+
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+struct sframe_section {
+ unsigned long sframe_start;
+ unsigned long sframe_end;
+ unsigned long text_start;
+ unsigned long text_end;
+
+ unsigned long fdes_start;
+ unsigned long fres_start;
+ unsigned long fres_end;
+ unsigned int num_fdes;
+
+ signed char ra_off;
+ signed char fp_off;
+};
+
+extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
+ unsigned long text_start, unsigned long text_end);
+extern int sframe_remove_section(unsigned long sframe_addr);
+
+#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
+
+static inline int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
+ unsigned long text_start, unsigned long text_end)
+{
+ return -ENOSYS;
+}
+static inline int sframe_remove_section(unsigned long sframe_addr) { return -ENOSYS; }
+
+#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
+
+#endif /* _LINUX_SFRAME_H */
diff --git a/kernel/unwind/Makefile b/kernel/unwind/Makefile
index eae37bea54fd..146038165865 100644
--- a/kernel/unwind/Makefile
+++ b/kernel/unwind/Makefile
@@ -1 +1,2 @@
- obj-$(CONFIG_UNWIND_USER) += user.o deferred.o
+ obj-$(CONFIG_UNWIND_USER) += user.o deferred.o
+ obj-$(CONFIG_HAVE_UNWIND_USER_SFRAME) += sframe.o
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
new file mode 100644
index 000000000000..d24e9d4f8bef
--- /dev/null
+++ b/kernel/unwind/sframe.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Userspace sframe access functions
+ */
+
+#define pr_fmt(fmt) "sframe: " fmt
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+#include <linux/uaccess.h>
+#include <linux/mm.h>
+#include <linux/string_helpers.h>
+#include <linux/sframe.h>
+#include <linux/unwind_user_types.h>
+
+#include "sframe.h"
+
+#define dbg(fmt, ...) \
+ pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+
+static void free_section(struct sframe_section *sec)
+{
+ kfree(sec);
+}
+
+static int sframe_read_header(struct sframe_section *sec)
+{
+ unsigned long header_end, fdes_start, fdes_end, fres_start, fres_end;
+ struct sframe_header shdr;
+ unsigned int num_fdes;
+
+ if (copy_from_user(&shdr, (void __user *)sec->sframe_start, sizeof(shdr))) {
+ dbg("header usercopy failed\n");
+ return -EFAULT;
+ }
+
+ if (shdr.preamble.magic != SFRAME_MAGIC ||
+ shdr.preamble.version != SFRAME_VERSION_3 ||
+ !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||
+ !(shdr.preamble.flags & SFRAME_F_FDE_FUNC_START_PCREL) ||
+ shdr.auxhdr_len) {
+ dbg("bad/unsupported sframe header\n");
+ return -EINVAL;
+ }
+
+ if (!shdr.num_fdes || !shdr.num_fres) {
+ dbg("no fde/fre entries\n");
+ return -EINVAL;
+ }
+
+ header_end = sec->sframe_start + SFRAME_HEADER_SIZE(shdr);
+ if (header_end >= sec->sframe_end) {
+ dbg("header doesn't fit in section\n");
+ return -EINVAL;
+ }
+
+ num_fdes = shdr.num_fdes;
+ fdes_start = header_end + shdr.fdes_off;
+ fdes_end = fdes_start + (num_fdes * sizeof(struct sframe_fde_v3));
+
+ fres_start = header_end + shdr.fres_off;
+ fres_end = fres_start + shdr.fre_len;
+
+ if (fres_start < fdes_end || fres_end > sec->sframe_end) {
+ dbg("inconsistent fde/fre offsets\n");
+ return -EINVAL;
+ }
+
+ sec->num_fdes = num_fdes;
+ sec->fdes_start = fdes_start;
+ sec->fres_start = fres_start;
+ sec->fres_end = fres_end;
+
+ sec->ra_off = shdr.cfa_fixed_ra_offset;
+ sec->fp_off = shdr.cfa_fixed_fp_offset;
+
+ return 0;
+}
+
+int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
+ unsigned long text_start, unsigned long text_end)
+{
+ struct vm_area_struct *sframe_vma, *text_vma;
+ struct mm_struct *mm = current->mm;
+ struct sframe_section *sec;
+ int ret;
+
+ if (sframe_start >= sframe_end || text_start >= text_end) {
+ dbg("invalid sframe/text address\n");
+ return -EINVAL;
+ }
+
+ scoped_guard(mmap_read_lock, mm) {
+ sframe_vma = vma_lookup(mm, sframe_start);
+ if (!sframe_vma || sframe_end > sframe_vma->vm_end) {
+ dbg("bad sframe address (0x%lx - 0x%lx)\n",
+ sframe_start, sframe_end);
+ return -EINVAL;
+ }
+
+ text_vma = vma_lookup(mm, text_start);
+ if (!text_vma ||
+ !(text_vma->vm_flags & VM_EXEC) ||
+ text_end > text_vma->vm_end) {
+ dbg("bad text address (0x%lx - 0x%lx)\n",
+ text_start, text_end);
+ return -EINVAL;
+ }
+ }
+
+ sec = kzalloc(sizeof(*sec), GFP_KERNEL_ACCOUNT);
+ if (!sec)
+ return -ENOMEM;
+
+ sec->sframe_start = sframe_start;
+ sec->sframe_end = sframe_end;
+ sec->text_start = text_start;
+ sec->text_end = text_end;
+
+ ret = sframe_read_header(sec);
+ if (ret)
+ goto err_free;
+
+ /* TODO nowhere to store it yet - just free it and return an error */
+ ret = -ENOSYS;
+
+err_free:
+ free_section(sec);
+ return ret;
+}
+
+int sframe_remove_section(unsigned long sframe_start)
+{
+ return -ENOSYS;
+}
diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h
new file mode 100644
index 000000000000..fc2908e92c7b
--- /dev/null
+++ b/kernel/unwind/sframe.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * From https://www.sourceware.org/binutils/docs/sframe-spec.html
+ */
+#ifndef _SFRAME_H
+#define _SFRAME_H
+
+#include <linux/types.h>
+
+#define SFRAME_VERSION_1 1
+#define SFRAME_VERSION_2 2
+#define SFRAME_VERSION_3 3
+#define SFRAME_MAGIC 0xdee2
+
+#define SFRAME_F_FDE_SORTED 0x1
+#define SFRAME_F_FRAME_POINTER 0x2
+#define SFRAME_F_FDE_FUNC_START_PCREL 0x4
+
+#define SFRAME_ABI_AARCH64_ENDIAN_BIG 1
+#define SFRAME_ABI_AARCH64_ENDIAN_LITTLE 2
+#define SFRAME_ABI_AMD64_ENDIAN_LITTLE 3
+
+struct sframe_preamble {
+ u16 magic;
+ u8 version;
+ u8 flags;
+} __packed;
+
+struct sframe_header {
+ struct sframe_preamble preamble;
+ u8 abi_arch;
+ s8 cfa_fixed_fp_offset;
+ s8 cfa_fixed_ra_offset;
+ u8 auxhdr_len;
+ u32 num_fdes;
+ u32 num_fres;
+ u32 fre_len;
+ u32 fdes_off;
+ u32 fres_off;
+} __packed;
+
+#define SFRAME_HEADER_SIZE(header) \
+ ((sizeof(struct sframe_header) + (header).auxhdr_len))
+
+struct sframe_fde_v3 {
+ s64 func_start_off;
+ u32 func_size;
+ u32 fres_off;
+} __packed;
+
+struct sframe_fda_v3 {
+ u16 fres_num;
+ u8 info;
+ u8 info2;
+ u8 rep_size;
+} __packed;
+
+#define SFRAME_FDE_PCTYPE_INC 0
+#define SFRAME_FDE_PCTYPE_MASK 1
+
+#define SFRAME_AARCH64_PAUTH_KEY_A 0
+#define SFRAME_AARCH64_PAUTH_KEY_B 1
+
+#define SFRAME_V3_FDE_FRE_TYPE(info) ((info) & 0xf)
+#define SFRAME_V3_FDE_PCTYPE(info) (((info) >> 4) & 0x1)
+#define SFRAME_V3_AARCH64_FDE_PAUTH_KEY(info) (((info) >> 5) & 0x1)
+
+#define SFRAME_FDE_TYPE_DEFAULT 0
+
+#define SFRAME_V3_FDE_TYPE_MASK 0x1f
+#define SFRAME_V3_FDE_TYPE(info2) ((info2) & SFRAME_V3_FDE_TYPE_MASK)
+
+#define SFRAME_BASE_REG_FP 0
+#define SFRAME_BASE_REG_SP 1
+
+#define SFRAME_V3_FRE_CFA_BASE_REG_ID(info) ((info) & 0x1)
+#define SFRAME_V3_FRE_DATAWORD_COUNT(info) (((info) >> 1) & 0xf)
+#define SFRAME_V3_FRE_DATAWORD_SIZE(info) (((info) >> 5) & 0x3)
+#define SFRAME_V3_AARCH64_FRE_MANGLED_RA_P(info) (((info) >> 7) & 0x1)
+
+#endif /* _SFRAME_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 11/20] unwind_user/sframe: Show file name in debug output
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
When debugging sframe issues, the error messages aren't all that helpful
without knowing what file a corresponding .sframe section belongs to.
Prefix debug output strings with the file name.
[ Jens Remus: Fix checkpatch error "space prohibited before that close
parenthesis ')'". ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- Use GFP_KERNEL_ACCOUNT instead of GFP_KERNEL (see
memory-allocation.rst, section "Get Free Page flags"). (Sashiko AI)
Changes in v14:
- Uppercase terms FDE and FRE in debug messages.
include/linux/sframe.h | 4 +++-
kernel/unwind/sframe.c | 23 ++++++++++--------
kernel/unwind/sframe_debug.h | 45 +++++++++++++++++++++++++++++++-----
3 files changed, 56 insertions(+), 16 deletions(-)
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 9a72209696f9..b79c5ec09229 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -10,7 +10,9 @@
struct sframe_section {
struct rcu_head rcu;
-
+#ifdef CONFIG_DYNAMIC_DEBUG
+ const char *filename;
+#endif
unsigned long sframe_start;
unsigned long sframe_end;
unsigned long text_start;
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index e0eb2adf5a07..2cfa274cd8dc 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -372,8 +372,10 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
}
end:
- if (ret && ret != -ENOENT)
+ if (ret && ret != -ENOENT) {
+ dbg_sec("removing bad .sframe section\n");
WARN_ON_ONCE(sframe_remove_section(sec->sframe_start));
+ }
return ret;
@@ -384,6 +386,7 @@ int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
static void free_section(struct sframe_section *sec)
{
+ dbg_free(sec);
kfree(sec);
}
@@ -403,7 +406,7 @@ static int sframe_read_header(struct sframe_section *sec)
BUILD_BUG_ON(!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS));
if (copy_from_user(&shdr, (void __user *)sec->sframe_start, sizeof(shdr))) {
- dbg("header usercopy failed\n");
+ dbg_sec("header usercopy failed\n");
return -EFAULT;
}
@@ -412,18 +415,18 @@ static int sframe_read_header(struct sframe_section *sec)
!(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||
!(shdr.preamble.flags & SFRAME_F_FDE_FUNC_START_PCREL) ||
shdr.auxhdr_len) {
- dbg("bad/unsupported sframe header\n");
+ dbg_sec("bad/unsupported sframe header\n");
return -EINVAL;
}
if (!shdr.num_fdes || !shdr.num_fres) {
- dbg("no fde/fre entries\n");
+ dbg_sec("no FDE/FRE entries\n");
return -EINVAL;
}
header_end = sec->sframe_start + SFRAME_HEADER_SIZE(shdr);
if (header_end >= sec->sframe_end) {
- dbg("header doesn't fit in section\n");
+ dbg_sec("header doesn't fit in section\n");
return -EINVAL;
}
@@ -435,7 +438,7 @@ static int sframe_read_header(struct sframe_section *sec)
fres_end = fres_start + shdr.fre_len;
if (fres_start < fdes_end || fres_end > sec->sframe_end) {
- dbg("inconsistent fde/fre offsets\n");
+ dbg_sec("inconsistent FDE/FRE offsets\n");
return -EINVAL;
}
@@ -491,6 +494,8 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
sec->text_start = text_start;
sec->text_end = text_end;
+ dbg_init(sec);
+
ret = sframe_read_header(sec);
if (ret) {
dbg_print_header(sec);
@@ -500,8 +505,8 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
ret = mtree_insert_range(sframe_mt, sec->text_start, sec->text_end - 1,
sec, GFP_KERNEL_ACCOUNT);
if (ret) {
- dbg("mtree_insert_range failed: text=%lx-%lx\n",
- sec->text_start, sec->text_end);
+ dbg_sec("mtree_insert_range failed: text=%lx-%lx\n",
+ sec->text_start, sec->text_end);
goto err_free;
}
@@ -523,7 +528,7 @@ static int __sframe_remove_section(struct mm_struct *mm,
struct sframe_section *sec)
{
if (!mtree_erase(&mm->sframe_mt, sec->text_start)) {
- dbg("mtree_erase failed: text=%lx\n", sec->text_start);
+ dbg_sec("mtree_erase failed: text=%lx\n", sec->text_start);
return -EINVAL;
}
diff --git a/kernel/unwind/sframe_debug.h b/kernel/unwind/sframe_debug.h
index 36352124cde8..a63e75cccc70 100644
--- a/kernel/unwind/sframe_debug.h
+++ b/kernel/unwind/sframe_debug.h
@@ -10,26 +10,59 @@
#define dbg(fmt, ...) \
pr_debug("%s (%d): " fmt, current->comm, current->pid, ##__VA_ARGS__)
+#define dbg_sec(fmt, ...) \
+ dbg("%s: " fmt, sec->filename, ##__VA_ARGS__)
+
static __always_inline void dbg_print_header(struct sframe_section *sec)
{
unsigned long fdes_end;
fdes_end = sec->fdes_start + (sec->num_fdes * sizeof(struct sframe_fde_v3));
- dbg("SEC: sframe:0x%lx-0x%lx text:0x%lx-0x%lx "
- "fdes:0x%lx-0x%lx fres:0x%lx-0x%lx "
- "ra_off:%d fp_off:%d\n",
- sec->sframe_start, sec->sframe_end, sec->text_start, sec->text_end,
- sec->fdes_start, fdes_end, sec->fres_start, sec->fres_end,
- sec->ra_off, sec->fp_off);
+ dbg_sec("SEC: sframe:0x%lx-0x%lx text:0x%lx-0x%lx "
+ "fdes:0x%lx-0x%lx fres:0x%lx-0x%lx "
+ "ra_off:%d fp_off:%d\n",
+ sec->sframe_start, sec->sframe_end, sec->text_start, sec->text_end,
+ sec->fdes_start, fdes_end, sec->fres_start, sec->fres_end,
+ sec->ra_off, sec->fp_off);
+}
+
+static inline void dbg_init(struct sframe_section *sec)
+{
+ struct mm_struct *mm = current->mm;
+ struct vm_area_struct *vma;
+
+ guard(mmap_read_lock)(mm);
+ vma = vma_lookup(mm, sec->sframe_start);
+ if (!vma)
+ sec->filename = kstrdup("(vma gone???)", GFP_KERNEL_ACCOUNT);
+ else if (vma->vm_file)
+ sec->filename = kstrdup_quotable_file(vma->vm_file, GFP_KERNEL_ACCOUNT);
+ else if (vma->vm_ops && vma->vm_ops->name)
+ sec->filename = kstrdup(vma->vm_ops->name(vma), GFP_KERNEL_ACCOUNT);
+ else if (arch_vma_name(vma))
+ sec->filename = kstrdup(arch_vma_name(vma), GFP_KERNEL_ACCOUNT);
+ else if (!vma->vm_mm)
+ sec->filename = kstrdup("(vdso)", GFP_KERNEL_ACCOUNT);
+ else
+ sec->filename = kstrdup("(anonymous)", GFP_KERNEL_ACCOUNT);
+}
+
+static inline void dbg_free(struct sframe_section *sec)
+{
+ kfree(sec->filename);
}
#else /* !CONFIG_DYNAMIC_DEBUG */
#define dbg(args...) no_printk(args)
+#define dbg_sec(args...) no_printk(args)
static inline void dbg_print_header(struct sframe_section *sec) {}
+static inline void dbg_init(struct sframe_section *sec) {}
+static inline void dbg_free(struct sframe_section *sec) {}
+
#endif /* !CONFIG_DYNAMIC_DEBUG */
#endif /* _SFRAME_DEBUG_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 16/20] unwind_user/sframe: Add support for SFrame V3 flexible FDEs
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
SFrame V3 introduces flexible FDEs in addition to the regular FDEs.
The key difference is that flexible FDEs encode the CFA, RA, and FP
tracking information using two FRE data words, a control word and an
offset, or a single padding data word of zero (e.g. to represent FP
without RA tracking information).
The control word contains the following information:
- reg_p: Whether to use the register contents (reg_p=1) specified
by regnum or the CFA (reg_p=0) as base.
- deref_p: Whether to dereference.
- regnum: A DWARF register number.
The offset is added to the base (i.e. CFA or register contents). Then
the resulting address may optionally be dereferenced.
This enables the following flexible CFA and FP/RA recovery rules:
- CFA = register + offset // reg_p=1, deref_p=0
- CFA = *(register + offset) // reg_p=1, deref_p=1
- FP/RA = *(CFA + offset) // reg_p=0, deref_p=0
- FP/RA = register + offset // reg_p=1, deref_p=0
- FP/RA = *(register + offset) // reg_p=1, deref_p=1
Note that for the CFA a rule with reg_p=0 is invalid, as the value of
the CFA cannot be described using itself as base. For FP/RA a rule with
reg_p=0 and deref_p=0 and regnum=0 is invalid, as it that is equal to
the padding data word of zero.
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v16:
- __find_fre(): Move declaration of ret to function scope to resolve
compile error. (Sashiko AI)
Changes in v15:
- __read_flex_fde_fre_datawords(): Add comment on FRE dataword RA/FP
location info decoding logic. (Sashiko AI)
- Fix outermost frame (FRE without datawords) handling to not cause
sframe_init_cfa_rule_data() and ultimately sframe_find() to fail
with -EINVAL. (Sashiko AI)
- sframe_init_[cfa_]rule_data(): Reject FRE control word with
reserved_p=1. (Sashiko AI)
- __find_fre(): Return RC of sframe_init_[cfa_]rule_data() if bad RC.
- Normalize error code usage (.sframe is removed for all but ENOENT):
ENOENT: No sframe or no FDE for IP found
(FDE found but no FRE is EINVAL)
EFAULT: Bad address
EINVAL: Invalid input or sframe
Changes in v14:
- Rename __read_regular_fre_datawords() to
__read_default_fre_datawords() to align to SFrame V3 specification
(default FRE).
- Rename SFRAME_FDE_TYPE_FLEXIBLE to SFRAME_FDE_TYPE_FLEX to match
SFrame V3 specification and adjust to rename of SFRAME_FDE_TYPE_*.
- Rename SFRAME_V3_FLEX_FDE_CTLWORD_*() to
SFRAME_V3_FLEX_FDE_CTRLWORD_*() to match SFrame V3 reference
implementation.
- Add arch/*/include/asm/unwind_user_sframe.h to MAINTAINERS.
MAINTAINERS | 1 +
kernel/unwind/sframe.c | 287 +++++++++++++++++++++++++++++++++--------
kernel/unwind/sframe.h | 6 +
3 files changed, 238 insertions(+), 56 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index a9b42b67a88d..25f0b933511c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27875,6 +27875,7 @@ M: Josh Poimboeuf <jpoimboe@kernel.org>
M: Steven Rostedt <rostedt@goodmis.org>
S: Maintained
F: arch/*/include/asm/unwind_user.h
+F: arch/*/include/asm/unwind_user_sframe.h
F: include/asm-generic/unwind_user.h
F: include/linux/sframe.h
F: include/linux/unwind*.h
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index daa97d8b0231..b623dca072da 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -12,6 +12,7 @@
#include <linux/mm.h>
#include <linux/string_helpers.h>
#include <linux/sframe.h>
+#include <asm/unwind_user_sframe.h>
#include <linux/unwind_user_types.h>
#include "sframe.h"
@@ -31,8 +32,11 @@ struct sframe_fde_internal {
struct sframe_fre_internal {
unsigned int size;
u32 ip_off;
+ u32 cfa_ctl;
s32 cfa_off;
+ u32 ra_ctl;
s32 ra_off;
+ u32 fp_ctl;
s32 fp_off;
u8 info;
};
@@ -200,19 +204,160 @@ static __always_inline int __find_fde(struct sframe_section *sec,
s32 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
s64 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
+static __always_inline int
+__read_default_fre_datawords(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long cur,
+ unsigned char dataword_count,
+ unsigned char dataword_size,
+ struct sframe_fre_internal *fre)
+{
+ s32 cfa_off, ra_off, fp_off;
+ unsigned int cfa_regnum;
+
+ UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+ dataword_count--;
+
+ ra_off = sec->ra_off;
+ if (!ra_off && dataword_count) {
+ dataword_count--;
+ UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+ }
+
+ fp_off = sec->fp_off;
+ if (!fp_off && dataword_count) {
+ dataword_count--;
+ UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ }
+
+ if (dataword_count)
+ return -EINVAL;
+
+ cfa_regnum =
+ (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre->info) == SFRAME_BASE_REG_FP) ?
+ SFRAME_REG_FP : SFRAME_REG_SP;
+
+ fre->cfa_ctl = (cfa_regnum << 3) | 1; /* regnum, deref_p=0, reg_p=1 */
+ fre->cfa_off = cfa_off;
+ fre->ra_ctl = ra_off ? 2 : 0; /* regnum=0, deref_p=(ra_off != 0), reg_p=0 */
+ fre->ra_off = ra_off;
+ fre->fp_ctl = fp_off ? 2 : 0; /* regnum=0, deref_p=(fp_off != 0), reg_p=0 */
+ fre->fp_off = fp_off;
+
+ return 0;
+
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int
+__read_flex_fde_fre_datawords(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long cur,
+ unsigned char dataword_count,
+ unsigned char dataword_size,
+ struct sframe_fre_internal *fre)
+{
+ u32 cfa_ctl, ra_ctl, fp_ctl;
+ s32 cfa_off, ra_off, fp_off;
+
+ if (dataword_count < 2)
+ return -EINVAL;
+ UNSAFE_GET_USER_INC(cfa_ctl, cur, dataword_size, Efault);
+ UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+ dataword_count -= 2;
+
+ /*
+ * Each RA/FP location info consumes either two datawords
+ * (control word + offset) or one padding word substituting
+ * for that pair. Padding is only valid as substitution if
+ * followed by further non-padding location info. Therefore
+ * decoding only proceeds with at least two datawords. Any
+ * leftover trailing datawords are invalid and rejected by
+ * the final check.
+ */
+
+ ra_off = sec->ra_off;
+ ra_ctl = ra_off ? 2 : 0; /* regnum=0, deref_p=(ra_off != 0), reg_p=0 */
+ if (dataword_count >= 2) {
+ UNSAFE_GET_USER_INC(ra_ctl, cur, dataword_size, Efault);
+ dataword_count--;
+ if (ra_ctl) {
+ UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+ dataword_count--;
+ } else {
+ /* Padding RA location info */
+ ra_ctl = ra_off ? 2 : 0; /* re-deduce (see above) */
+ }
+ }
+
+ fp_off = sec->fp_off;
+ fp_ctl = fp_off ? 2 : 0; /* regnum=0, deref_p=(fp_off != 0), reg_p=0 */
+ if (dataword_count >= 2) {
+ UNSAFE_GET_USER_INC(fp_ctl, cur, dataword_size, Efault);
+ dataword_count--;
+ if (fp_ctl) {
+ UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ dataword_count--;
+ } else {
+ /* Padding FP location info */
+ fp_ctl = fp_off ? 2 : 0; /* re-deduce (see above) */
+ }
+ }
+
+ /* Reject trailing padding or unknown extra datawords */
+ if (dataword_count)
+ return -EINVAL;
+
+ fre->cfa_ctl = cfa_ctl;
+ fre->cfa_off = cfa_off;
+ fre->ra_ctl = ra_ctl;
+ fre->ra_off = ra_off;
+ fre->fp_ctl = fp_ctl;
+ fre->fp_off = fp_off;
+
+ return 0;
+
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int
+__read_fre_datawords(struct sframe_section *sec,
+ struct sframe_fde_internal *fde,
+ unsigned long cur,
+ unsigned char dataword_count,
+ unsigned char dataword_size,
+ struct sframe_fre_internal *fre)
+{
+ unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2);
+
+ switch (fde_type) {
+ case SFRAME_FDE_TYPE_DEFAULT:
+ return __read_default_fre_datawords(sec, fde, cur,
+ dataword_count,
+ dataword_size,
+ fre);
+ case SFRAME_FDE_TYPE_FLEX:
+ return __read_flex_fde_fre_datawords(sec, fde, cur,
+ dataword_count,
+ dataword_size,
+ fre);
+ default:
+ return -EINVAL;
+ }
+}
+
static __always_inline int __read_fre(struct sframe_section *sec,
struct sframe_fde_internal *fde,
unsigned long fre_addr,
struct sframe_fre_internal *fre)
{
- unsigned char fde_type = SFRAME_V3_FDE_TYPE(fde->info2);
unsigned char fde_pctype = SFRAME_V3_FDE_PCTYPE(fde->info);
unsigned char fre_type = SFRAME_V3_FDE_FRE_TYPE(fde->info);
unsigned char dataword_count, dataword_size;
- s32 cfa_off, ra_off, fp_off;
unsigned long cur = fre_addr;
unsigned char addr_size;
- unsigned int fre_size;
u32 ip_off;
u8 info;
@@ -233,80 +378,105 @@ static __always_inline int __read_fre(struct sframe_section *sec,
dataword_size = dataword_size_enum_to_size(SFRAME_V3_FRE_DATAWORD_SIZE(info));
if (!dataword_size)
return -EINVAL;
- fre_size = addr_size + 1 + (dataword_count * dataword_size);
if (cur + (dataword_count * dataword_size) > sec->fres_end)
return -EFAULT;
- /* TODO: Support for flexible FDEs not implemented yet. */
- if (fde_type != SFRAME_FDE_TYPE_DEFAULT)
- return -EINVAL;
+ fre->size = addr_size + 1 + (dataword_count * dataword_size);
+ fre->ip_off = ip_off;
+ fre->info = info;
if (!dataword_count) {
/*
- * A FRE without data words indicates RA undefined /
- * outermost frame.
+ * A FRE without datawords indicates an outermost
+ * frame. Zero-initialize CFA, RA, and FP location
+ * info, except for the CFA control word, so that
+ * neither sframe_init_cfa_rule_data() nor
+ * sframe_init_rule_data() fail.
*/
- cfa_off = 0;
- ra_off = 0;
- fp_off = 0;
- goto done;
- }
-
- UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
- dataword_count--;
-
- ra_off = sec->ra_off;
- if (!ra_off && dataword_count) {
- dataword_count--;
- UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
- }
+ fre->cfa_ctl = (SFRAME_REG_SP << 3) | 1; /* regnum=SP, deref_p=0, reg_p=1 */
+ fre->cfa_off = 0;
+ fre->ra_ctl = 0;
+ fre->ra_off = 0;
+ fre->fp_ctl = 0;
+ fre->fp_off = 0;
- fp_off = sec->fp_off;
- if (!fp_off && dataword_count) {
- dataword_count--;
- UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ return 0;
}
- if (dataword_count)
- return -EINVAL;
-
-done:
- fre->size = fre_size;
- fre->ip_off = ip_off;
- fre->cfa_off = cfa_off;
- fre->ra_off = ra_off;
- fre->fp_off = fp_off;
- fre->info = info;
-
- return 0;
+ return __read_fre_datawords(sec, fde, cur, dataword_count, dataword_size, fre);
Efault:
return -EFAULT;
}
-static __always_inline void
+static __always_inline int
sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
- unsigned char fre_info,
- s32 offset)
+ u32 ctlword, s32 offset)
{
- if (SFRAME_V3_FRE_CFA_BASE_REG_ID(fre_info) == SFRAME_BASE_REG_FP)
- cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
- else
+ bool deref_p = SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(ctlword);
+ bool reg_p = SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(ctlword);
+ bool reserved_p = SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(ctlword);
+ unsigned int regnum = SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(ctlword);
+
+ if (reserved_p)
+ return -EINVAL;
+
+ /* CFA recovery rule must be register-based */
+ if (!reg_p)
+ return -EINVAL;
+
+ switch (regnum) {
+ case SFRAME_REG_SP:
cfa_rule_data->rule = UNWIND_USER_CFA_RULE_SP_OFFSET;
+ break;
+ case SFRAME_REG_FP:
+ cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
+ break;
+ default:
+ cfa_rule_data->rule = UNWIND_USER_CFA_RULE_REG_OFFSET;
+ cfa_rule_data->regnum = regnum;
+ }
+
+ if (deref_p)
+ cfa_rule_data->rule |= UNWIND_USER_RULE_DEREF;
+
cfa_rule_data->offset = offset;
+
+ return 0;
}
-static __always_inline void
+static __always_inline int
sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
- s32 offset)
+ u32 ctlword, s32 offset)
{
- if (offset) {
- rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF;
- rule_data->offset = offset;
- } else {
+ bool deref_p = SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(ctlword);
+ bool reg_p = SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(ctlword);
+ bool reserved_p = SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(ctlword);
+
+ if (!ctlword && !offset) {
rule_data->rule = UNWIND_USER_RULE_RETAIN;
+ return 0;
+ }
+
+ if (reserved_p)
+ return -EINVAL;
+
+ if (reg_p) {
+ unsigned int regnum = SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(ctlword);
+
+ rule_data->rule = UNWIND_USER_RULE_REG_OFFSET;
+ rule_data->regnum = regnum;
+ } else {
+ rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET;
}
+
+ if (deref_p)
+ rule_data->rule |= UNWIND_USER_RULE_DEREF;
+
+ rule_data->offset = offset;
+
+ return 0;
}
static __always_inline int __find_fre(struct sframe_section *sec,
@@ -321,6 +491,7 @@ static __always_inline int __find_fre(struct sframe_section *sec,
bool which = false;
unsigned int i;
u32 ip_off;
+ int ret;
ip_off = ip - fde->func_addr;
@@ -330,8 +501,6 @@ static __always_inline int __find_fre(struct sframe_section *sec,
fre_addr = sec->fres_start + fde->fres_off;
for (i = 0; i < fde->fres_num; i++) {
- int ret;
-
/*
* Alternate between the two fre_addr[] entries for 'fre' and
* 'prev_fre'.
@@ -358,9 +527,15 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return -EINVAL;
fre = prev_fre;
- sframe_init_cfa_rule_data(&frame->cfa, fre->info, fre->cfa_off);
- sframe_init_rule_data(&frame->ra, fre->ra_off);
- sframe_init_rule_data(&frame->fp, fre->fp_off);
+ ret = sframe_init_cfa_rule_data(&frame->cfa, fre->cfa_ctl, fre->cfa_off);
+ if (ret)
+ return ret;
+ ret = sframe_init_rule_data(&frame->ra, fre->ra_ctl, fre->ra_off);
+ if (ret)
+ return ret;
+ ret = sframe_init_rule_data(&frame->fp, fre->fp_ctl, fre->fp_off);
+ if (ret)
+ return ret;
frame->outermost = SFRAME_V3_FRE_RA_UNDEFINED_P(fre->info);
return 0;
diff --git a/kernel/unwind/sframe.h b/kernel/unwind/sframe.h
index ed111fd0d702..1a2528e4b149 100644
--- a/kernel/unwind/sframe.h
+++ b/kernel/unwind/sframe.h
@@ -66,6 +66,7 @@ struct sframe_fda_v3 {
#define SFRAME_V3_AARCH64_FDE_PAUTH_KEY(info) (((info) >> 5) & 0x1)
#define SFRAME_FDE_TYPE_DEFAULT 0
+#define SFRAME_FDE_TYPE_FLEX 1
#define SFRAME_V3_FDE_TYPE_MASK 0x1f
#define SFRAME_V3_FDE_TYPE(info2) ((info2) & SFRAME_V3_FDE_TYPE_MASK)
@@ -79,4 +80,9 @@ struct sframe_fda_v3 {
#define SFRAME_V3_AARCH64_FRE_MANGLED_RA_P(info) (((info) >> 7) & 0x1)
#define SFRAME_V3_FRE_RA_UNDEFINED_P(info) (SFRAME_V3_FRE_DATAWORD_COUNT(info) == 0)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_REGNUM(data) (((data) >> 3) & 0x1f)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_RESERVED_P(data) (((data) >> 2) & 0x1)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_DEREF_P(data) (((data) >> 1) & 0x1)
+#define SFRAME_V3_FLEX_FDE_CTRLWORD_REG_P(data) ((data) & 0x1)
+
#endif /* _SFRAME_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 06/20] unwind_user/sframe: Detect .sframe sections in executables
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
When loading an ELF executable, automatically detect an .sframe section
and associate it with the mm_struct.
[ Jens Remus: Fix checkpatch warning "braces {} are not necessary for
single statement blocks". ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v15:
- Only add sframe for text that is PT_LOAD in addition to PF_X.
(Sashiko AI)
fs/binfmt_elf.c | 48 +++++++++++++++++++++++++++++++++++++---
include/uapi/linux/elf.h | 1 +
2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 16a56b6b3f6c..980a9f229cd1 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -48,6 +48,7 @@
#include <linux/uaccess.h>
#include <uapi/linux/rseq.h>
#include <linux/rseq.h>
+#include <linux/sframe.h>
#include <asm/param.h>
#include <asm/page.h>
@@ -637,6 +638,21 @@ static inline int make_prot(u32 p_flags, struct arch_elf_state *arch_state,
return arch_elf_adjust_prot(prot, arch_state, has_interp, is_interp);
}
+static void elf_add_sframe(struct elf_phdr *text, struct elf_phdr *sframe,
+ unsigned long base_addr)
+{
+ unsigned long sframe_start, sframe_end, text_start, text_end;
+
+ sframe_start = base_addr + sframe->p_vaddr;
+ sframe_end = sframe_start + sframe->p_memsz;
+
+ text_start = base_addr + text->p_vaddr;
+ text_end = text_start + text->p_memsz;
+
+ /* Ignore return value, sframe section isn't critical */
+ sframe_add_section(sframe_start, sframe_end, text_start, text_end);
+}
+
/* This is much more generalized than the library routine read function,
so we keep this separate. Technically the library read function
is only provided so that we can read a.out libraries that have
@@ -647,7 +663,7 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
unsigned long no_base, struct elf_phdr *interp_elf_phdata,
struct arch_elf_state *arch_state)
{
- struct elf_phdr *eppnt;
+ struct elf_phdr *eppnt, *sframe_phdr = NULL;
unsigned long load_addr = 0;
int load_addr_set = 0;
unsigned long error = ~0UL;
@@ -673,7 +689,8 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
eppnt = interp_elf_phdata;
for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
- if (eppnt->p_type == PT_LOAD) {
+ switch (eppnt->p_type) {
+ case PT_LOAD: {
int elf_type = MAP_PRIVATE;
int elf_prot = make_prot(eppnt->p_flags, arch_state,
true, true);
@@ -712,6 +729,19 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
error = -ENOMEM;
goto out;
}
+ break;
+ }
+ case PT_GNU_SFRAME:
+ sframe_phdr = eppnt;
+ break;
+ }
+ }
+
+ if (sframe_phdr) {
+ eppnt = interp_elf_phdata;
+ for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
+ if (eppnt->p_flags & PF_X && eppnt->p_type == PT_LOAD)
+ elf_add_sframe(eppnt, sframe_phdr, load_addr);
}
}
@@ -836,7 +866,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
int first_pt_load = 1;
unsigned long error;
struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
- struct elf_phdr *elf_property_phdata = NULL;
+ struct elf_phdr *elf_property_phdata = NULL, *sframe_phdr = NULL;
unsigned long elf_brk;
bool brk_moved = false;
int retval, i;
@@ -945,6 +975,10 @@ static int load_elf_binary(struct linux_binprm *bprm)
executable_stack = EXSTACK_DISABLE_X;
break;
+ case PT_GNU_SFRAME:
+ sframe_phdr = elf_ppnt;
+ break;
+
case PT_LOPROC ... PT_HIPROC:
retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
bprm->file, false,
@@ -1242,6 +1276,14 @@ static int load_elf_binary(struct linux_binprm *bprm)
elf_brk = k;
}
+ if (sframe_phdr) {
+ for (i = 0, elf_ppnt = elf_phdata;
+ i < elf_ex->e_phnum; i++, elf_ppnt++) {
+ if (elf_ppnt->p_flags & PF_X && elf_ppnt->p_type == PT_LOAD)
+ elf_add_sframe(elf_ppnt, sframe_phdr, load_bias);
+ }
+ }
+
e_entry = elf_ex->e_entry + load_bias;
phdr_addr += load_bias;
elf_brk += load_bias;
diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h
index ee30dcd80901..e2a7dbed2e80 100644
--- a/include/uapi/linux/elf.h
+++ b/include/uapi/linux/elf.h
@@ -41,6 +41,7 @@ typedef __u16 Elf64_Versym;
#define PT_GNU_STACK (PT_LOOS + 0x474e551)
#define PT_GNU_RELRO (PT_LOOS + 0x474e552)
#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553)
+#define PT_GNU_SFRAME (PT_LOOS + 0x474e554)
/* ARM MTE memory tag segment type */
--
2.51.0
^ permalink raw reply related
* [PATCH v16 07/20] unwind_user/sframe: Wire up unwind_user to sframe
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich, Steven Rostedt (Google)
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
From: Josh Poimboeuf <jpoimboe@kernel.org>
Now that the sframe infrastructure is fully in place, make it work by
hooking it up to the unwind_user interface.
[ Jens Remus: Remove unused pt_regs from unwind_user_next_common() and
its callers. Simplify unwind_user_next_sframe(). ]
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Indu Bhagat <ibhagatgnu@gmail.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
arch/Kconfig | 1 +
include/linux/unwind_user_types.h | 4 +++-
kernel/unwind/user.c | 23 +++++++++++++++++++++++
3 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/arch/Kconfig b/arch/Kconfig
index 94b2d5e8e529..37549832bd1f 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -488,6 +488,7 @@ config HAVE_UNWIND_USER_FP
config HAVE_UNWIND_USER_SFRAME
bool
+ select UNWIND_USER
config HAVE_PERF_REGS
bool
diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 412729a269bc..43e4b160883f 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -9,7 +9,8 @@
* available.
*/
enum unwind_user_type_bits {
- UNWIND_USER_TYPE_FP_BIT = 0,
+ UNWIND_USER_TYPE_SFRAME_BIT = 0,
+ UNWIND_USER_TYPE_FP_BIT = 1,
NR_UNWIND_USER_TYPE_BITS,
};
@@ -17,6 +18,7 @@ enum unwind_user_type_bits {
enum unwind_user_type {
/* Type "none" for the start of stack walk iteration. */
UNWIND_USER_TYPE_NONE = 0,
+ UNWIND_USER_TYPE_SFRAME = BIT(UNWIND_USER_TYPE_SFRAME_BIT),
UNWIND_USER_TYPE_FP = BIT(UNWIND_USER_TYPE_FP_BIT),
};
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index 90ab3c1a205e..1fb272419733 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -7,6 +7,7 @@
#include <linux/sched/task_stack.h>
#include <linux/unwind_user.h>
#include <linux/uaccess.h>
+#include <linux/sframe.h>
#define for_each_user_frame(state) \
for (unwind_user_start(state); !(state)->done; unwind_user_next(state))
@@ -82,6 +83,16 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
return unwind_user_next_common(state, &fp_frame);
}
+static int unwind_user_next_sframe(struct unwind_user_state *state)
+{
+ struct unwind_user_frame frame;
+
+ /* sframe expects the frame to be local storage */
+ if (sframe_find(state->ip, &frame))
+ return -ENOENT;
+ return unwind_user_next_common(state, &frame);
+}
+
static int unwind_user_next(struct unwind_user_state *state)
{
unsigned long iter_mask = state->available_types;
@@ -95,6 +106,16 @@ static int unwind_user_next(struct unwind_user_state *state)
state->current_type = type;
switch (type) {
+ case UNWIND_USER_TYPE_SFRAME:
+ switch (unwind_user_next_sframe(state)) {
+ case 0:
+ return 0;
+ case -ENOENT:
+ continue; /* Try next method. */
+ default:
+ state->done = true;
+ }
+ break;
case UNWIND_USER_TYPE_FP:
if (!unwind_user_next_fp(state))
return 0;
@@ -123,6 +144,8 @@ static int unwind_user_start(struct unwind_user_state *state)
return -EINVAL;
}
+ if (current_has_sframe())
+ state->available_types |= UNWIND_USER_TYPE_SFRAME;
if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP))
state->available_types |= UNWIND_USER_TYPE_FP;
--
2.51.0
^ permalink raw reply related
* [PATCH v16 01/20] unwind_user: Add generic and arch-specific headers to MAINTAINERS
From: Jens Remus @ 2026-05-21 14:25 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, x86, Steven Rostedt,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Dylan Hatch,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H. Peter Anvin, Mathieu Desnoyers, Kees Cook, Sam James
Cc: Jens Remus, bpf, linux-mm, Namhyung Kim, Andrii Nakryiko,
Jose E. Marchesi, Beau Belgrave, Florian Weimer,
Carlos O'Donell, Masami Hiramatsu, Jiri Olsa,
Arnaldo Carvalho de Melo, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Heiko Carstens, Vasily Gorbik,
Ilya Leoshkevich
In-Reply-To: <20260521142546.3908498-1-jremus@linux.ibm.com>
Commit 71753c6ed2bf ("unwind_user: Add user space unwinding API with
frame pointer support") introduced include/asm-generic/unwind_user.h
without adding it to MAINTAINERS, as well as any future arch-specific
versions such as the one added by commit 49cf34c0815f
("unwind_user/x86: Enable frame pointer unwinding on x86") which
introduced arch/x86/include/asm/unwind_user.h.
Suggested-by: Dylan Hatch <dylanbhatch@google.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---
Notes (jremus):
Changes in v14:
- New patch.
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d79275c6..7434e9d7b33f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27874,6 +27874,8 @@ USERSPACE STACK UNWINDING
M: Josh Poimboeuf <jpoimboe@kernel.org>
M: Steven Rostedt <rostedt@goodmis.org>
S: Maintained
+F: arch/*/include/asm/unwind_user.h
+F: include/asm-generic/unwind_user.h
F: include/linux/unwind*.h
F: kernel/unwind/
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v6 19/43] KVM: Let userspace disable per-VM mem attributes, enable per-gmem attributes
From: Sean Christopherson @ 2026-05-21 14:21 UTC (permalink / raw)
To: Fuad Tabba
Cc: ackerleytng, aik, andrew.jones, binbin.wu, brauner, chao.p.peng,
david, ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, willy, wyihan, yan.y.zhao, forkloop, pratyush,
suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt,
Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka, kvm,
linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco
In-Reply-To: <CA+EHjTzu=28Sr3=9A9LmJEGz0tBEDbU9taznVV5kdL7s8Nw=Jg@mail.gmail.com>
On Thu, May 21, 2026, Fuad Tabba wrote:
> Hi Ackerley,
>
> On Thu, 7 May 2026 at 21:22, Ackerley Tng via B4 Relay
> <devnull+ackerleytng.google.com@kernel.org> wrote:
> >
> > From: Sean Christopherson <seanjc@google.com>
> >
> > Make vm_memory_attributes a module parameter so that userspace can disable
> > the use of memory attributes on the VM level.
> >
> > To avoid inconsistencies in the way memory attributes are tracked in KVM
> > and guest_memfd, the vm_memory_attributes module_param is made
> > read-only (0444).
> >
> > Make CONFIG_KVM_VM_MEMORY_ATTRIBUTES selectable, only for (CoCo) VM types
> > that might use vm_memory_attributes.
> >
> > Signed-off-by: Sean Christopherson <seanjc@google.com>
> > Signed-off-by: Ackerley Tng <ackerleytng@google.com>
>
> Config files always confuse me, but Sashiko might be onto something:
>
> https://sashiko.dev/#/patchset/20260507-gmem-inplace-conversion-v6-0-91ab5a8b19a4%40google.com?part=19
: Since this prompt does not have a default value, will it default to N
: and silently drop KVM_VM_MEMORY_ATTRIBUTES during configuration updates
: like make olddefconfig?
:
: Existing userspace VMMs that rely on the KVM_SET_MEMORY_ATTRIBUTES ioctl
: for TDX or SEV VMs might fail to boot if the feature is unexpectedly
: compiled out. Could a default y be used to preserve backwards
: compatibility for existing configurations?
> I think this partially goes back to commit 6, the one I flagged
> yesterday. But also adding "default y" to KVM_VM_MEMORY_ATTRIBUTES?
> The default value should at least fix this issue, but I'm not sure if
> it would cause other problems...
Hrm. As much as I want per-gmem attributes to be the default going forward,
silently breaking existing setups isn't great. On the other hand, I'm *very*
skeptical there are any SNP or TDX deployments using a distro kernel, so I'm
still leaning towards forcing the issue and turning per-VM attributes off by
default.
^ permalink raw reply
* [PATCH v2 5/6] rtla/tests: Add unit tests for _parse_args() functions
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
Add a test suite for the _parse_args() function of each tool that checks
the params structures (struct common_params, struct osnoise_params,
struct timerlat_params) returned by them for correctness.
One test case is added per option, as well as a few special cases for
tricky combinations of options. Test cases are ordered the same as the
option arrays and help message to allow easy checking of whether all
options are covered.
This should help clarify what the proper command line behavior of RTLA
is in case there are holes in the documentation and verify that the
intended behavior is implemented correctly.
A few necessary changes to the unit tests were done as part of this
commit:
- Unit tests now also link to libsubcmd and its dependencies.
- A new global variable in_unit_test is added to RTLA's CLI interface,
causing it to skip check for root if running in unit tests. This
allows the CLI unit tests to run as non-root, like existing unit
tests.
There is quite a lot of duplication, some of it is mitigated with macros,
but partially it is intentional so that future changes in behavior are
tracked across tools.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/cli.c | 8 +-
tools/tracing/rtla/src/cli.h | 2 +
tools/tracing/rtla/tests/unit/Build | 4 +
tools/tracing/rtla/tests/unit/Makefile.unit | 2 +-
.../rtla/tests/unit/cli_params_assert.h | 68 ++
.../rtla/tests/unit/osnoise_hist_cli.c | 557 ++++++++++++++
.../tracing/rtla/tests/unit/osnoise_top_cli.c | 503 +++++++++++++
.../rtla/tests/unit/timerlat_hist_cli.c | 702 ++++++++++++++++++
.../rtla/tests/unit/timerlat_top_cli.c | 634 ++++++++++++++++
tools/tracing/rtla/tests/unit/unit_tests.c | 11 +
10 files changed, 2486 insertions(+), 5 deletions(-)
create mode 100644 tools/tracing/rtla/tests/unit/cli_params_assert.h
create mode 100644 tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/osnoise_top_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/timerlat_top_cli.c
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
index 7f531519df44..709219341a56 100644
--- a/tools/tracing/rtla/src/cli.c
+++ b/tools/tracing/rtla/src/cli.c
@@ -124,7 +124,7 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv)
if (cb_data.trace_output)
actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
- if (geteuid())
+ if (geteuid() && !in_unit_test)
fatal("osnoise needs root permission");
return ¶ms->common;
@@ -206,7 +206,7 @@ struct common_params *osnoise_hist_parse_args(int argc, char **argv)
if (cb_data.trace_output)
actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
- if (geteuid())
+ if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
if (params->common.hist.no_index && !params->common.hist.with_zeros)
@@ -301,7 +301,7 @@ struct common_params *timerlat_top_parse_args(int argc, char **argv)
if (cb_data.trace_output)
actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
- if (geteuid())
+ if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
/*
@@ -427,7 +427,7 @@ struct common_params *timerlat_hist_parse_args(int argc, char **argv)
if (cb_data.trace_output)
actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
- if (geteuid())
+ if (geteuid() && !in_unit_test)
fatal("rtla needs root permission");
if (params->common.hist.no_irq && params->common.hist.no_thread)
diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h
index c49ccb3e92f5..633a2322cf89 100644
--- a/tools/tracing/rtla/src/cli.h
+++ b/tools/tracing/rtla/src/cli.h
@@ -5,3 +5,5 @@ struct common_params *osnoise_top_parse_args(int argc, char **argv);
struct common_params *osnoise_hist_parse_args(int argc, char **argv);
struct common_params *timerlat_top_parse_args(int argc, char **argv);
struct common_params *timerlat_hist_parse_args(int argc, char **argv);
+
+extern bool in_unit_test;
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
index 2749f4cf202a..16139f17ea1f 100644
--- a/tools/tracing/rtla/tests/unit/Build
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -1,3 +1,7 @@
unit_tests-y += utils.o
unit_tests-y += actions.o
unit_tests-y += unit_tests.o
+unit_tests-y += osnoise_top_cli.o
+unit_tests-y += osnoise_hist_cli.o
+unit_tests-y += timerlat_top_cli.o
+unit_tests-y += timerlat_hist_cli.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
index bacb00164e46..8c33a9583c30 100644
--- a/tools/tracing/rtla/tests/unit/Makefile.unit
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -3,7 +3,7 @@
UNIT_TESTS := $(OUTPUT)unit_tests
UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
-$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN)
+$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
$(UNIT_TESTS_IN): fixdep
diff --git a/tools/tracing/rtla/tests/unit/cli_params_assert.h b/tools/tracing/rtla/tests/unit/cli_params_assert.h
new file mode 100644
index 000000000000..4bc7d582fcf4
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_params_assert.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "../../src/timerlat.h"
+
+/* Tracing Options */
+
+#define CLI_ASSERT_SINGLE_EVENT(_system, _event) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->system, _system);\
+ ck_assert_str_eq(params->events->event, _event);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_FILTER(_filter) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->filter, _filter);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_TRIGGER(_trigger) do {\
+ ck_assert_ptr_nonnull(params->events);\
+ ck_assert_str_eq(params->events->trigger, _trigger);\
+ ck_assert_ptr_null(params->events->next);\
+} while (0)
+
+/* CPU Configuration */
+
+#define CLI_ASSERT_CPUSET(_field, ...) do {\
+ int n;\
+ int cpus[] = { __VA_ARGS__ };\
+ for (n = 0; n < sizeof(cpus) / sizeof(int); n++)\
+ ck_assert(CPU_ISSET(cpus[n], ¶ms->_field));\
+ ck_assert_int_eq(CPU_COUNT(¶ms->_field), n);\
+} while (0)
+
+/* Auto Analysis and Actions */
+
+#define CLI_OSNOISE_ASSERT_AUTO(_stop) do {\
+ ck_assert_int_eq(params->stop_us, _stop);\
+ ck_assert_int_eq(osn_params->threshold, 1);\
+ ck_assert_int_eq(params->threshold_actions.len, 1);\
+ ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+ ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "osnoise_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AUTO(_threshold) do {\
+ ck_assert_int_eq(params->stop_us, _threshold);\
+ ck_assert_int_eq(params->stop_total_us, _threshold);\
+ ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+ ck_assert_int_eq(params->threshold_actions.len, 1);\
+ ck_assert_int_eq(params->threshold_actions.list[0].type, ACTION_TRACE_OUTPUT);\
+ ck_assert_str_eq(params->threshold_actions.list[0].trace_output, "timerlat_trace.txt");\
+} while (0)
+
+#define CLI_TIMERLAT_ASSERT_AA_ONLY(_threshold) do {\
+ ck_assert_int_eq(params->stop_us, _threshold);\
+ ck_assert_int_eq(params->stop_total_us, _threshold);\
+ ck_assert_int_eq(tlat_params->print_stack, _threshold);\
+ ck_assert_int_eq(params->threshold_actions.len, 0);\
+ ck_assert(params->aa_only);\
+} while (0)
+
+#define CLI_ASSERT_SINGLE_ACTION(_actions, _type, _arg, _valtype, _value) do {\
+ ck_assert_int_eq(params->_actions.len, 1);\
+ ck_assert_int_eq(params->_actions.list[0].type, _type);\
+ ck_assert_##_valtype##_eq(params->_actions.list[0]._arg, _value);\
+} while (0)
diff --git a/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
new file mode 100644
index 000000000000..3661529f93dc
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
@@ -0,0 +1,557 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ osnoise_hist_parse_args(argc, argv);\
+ struct osnoise_params *osn_params __maybe_unused =\
+ to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-p", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--period", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-r", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--runtime", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-s", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--stop", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-S", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--stop-total", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-T", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--threshold", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("osnoise", "hist", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("osnoise", "hist", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "hist", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("osnoise", "hist", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("osnoise", "hist", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-b", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--bucket-size", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-E", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--entries", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+ PARSE_ARGS("osnoise", "hist", "--no-header");
+
+ ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+ PARSE_ARGS("osnoise", "hist", "--with-zeros", "--no-index");
+
+ ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+ PARSE_ARGS("osnoise", "hist", "--no-summary");
+
+ ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+ PARSE_ARGS("osnoise", "hist", "--with-zeros");
+
+ ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("osnoise", "hist", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("osnoise", "hist", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("osnoise", "hist", "-a", "20");
+
+ CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("osnoise", "hist", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("osnoise", "hist", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("osnoise", "hist", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("osnoise", "hist", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_hist_cli_suite(void)
+{
+ Suite *s = suite_create("osnoise_hist_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_runtime_short);
+ tcase_add_test(tc, test_runtime_long);
+ tcase_add_test(tc, test_stop_short);
+ tcase_add_test(tc, test_stop_long);
+ tcase_add_test(tc, test_stop_total_short);
+ tcase_add_test(tc, test_stop_total_long);
+ tcase_add_test(tc, test_threshold_short);
+ tcase_add_test(tc, test_threshold_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("histogram_options");
+ tcase_add_test(tc, test_bucket_size_short);
+ tcase_add_test(tc, test_bucket_size_long);
+ tcase_add_test(tc, test_entries_short);
+ tcase_add_test(tc, test_entries_long);
+ tcase_add_test(tc, test_no_header);
+ tcase_add_test(tc, test_no_index);
+ tcase_add_test(tc, test_no_summary);
+ tcase_add_test(tc, test_with_zeros);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/osnoise_top_cli.c b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
new file mode 100644
index 000000000000..f3a8633cc84e
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/osnoise_top_cli.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ osnoise_top_parse_args(argc, argv);\
+ struct osnoise_params *osn_params __maybe_unused =\
+ to_osnoise_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("osnoise", "top", "-p", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("osnoise", "top", "--period", "100000");
+
+ ck_assert_int_eq(osn_params->period, 100000);
+}
+END_TEST
+
+START_TEST(test_runtime_short)
+{
+ PARSE_ARGS("osnoise", "top", "-r", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_runtime_long)
+{
+ PARSE_ARGS("osnoise", "top", "--runtime", "95000");
+
+ ck_assert_int_eq(osn_params->runtime, 95000);
+}
+END_TEST
+
+START_TEST(test_stop_short)
+{
+ PARSE_ARGS("osnoise", "top", "-s", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_long)
+{
+ PARSE_ARGS("osnoise", "top", "--stop", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_short)
+{
+ PARSE_ARGS("osnoise", "top", "-S", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_stop_total_long)
+{
+ PARSE_ARGS("osnoise", "top", "--stop-total", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_threshold_short)
+{
+ PARSE_ARGS("osnoise", "top", "-T", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_threshold_long)
+{
+ PARSE_ARGS("osnoise", "top", "--threshold", "5");
+
+ ck_assert_int_eq(osn_params->threshold, 5);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("osnoise", "top", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("osnoise", "top", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("osnoise", "top", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("osnoise", "top", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("osnoise", "top", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("osnoise", "top", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("osnoise", "top", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("osnoise", "top", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("osnoise", "top", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("osnoise", "top", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("osnoise", "top", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("osnoise", "top", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("osnoise", "top", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("osnoise", "top", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_quiet_short)
+{
+ PARSE_ARGS("osnoise", "top", "-q");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+ PARSE_ARGS("osnoise", "top", "--quiet");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("osnoise", "top", "-a", "20");
+
+ CLI_OSNOISE_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("osnoise", "top", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("osnoise", "top", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "osnoise_trace.txt");
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("osnoise", "top", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("osnoise", "top", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("osnoise", "top", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("osnoise", "top", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("osnoise", "top", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("osnoise", "top", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *osnoise_top_cli_suite(void)
+{
+ Suite *s = suite_create("osnoise_top_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_runtime_short);
+ tcase_add_test(tc, test_runtime_long);
+ tcase_add_test(tc, test_stop_short);
+ tcase_add_test(tc, test_stop_long);
+ tcase_add_test(tc, test_stop_total_short);
+ tcase_add_test(tc, test_stop_total_long);
+ tcase_add_test(tc, test_threshold_short);
+ tcase_add_test(tc, test_threshold_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_quiet_short);
+ tcase_add_test(tc, test_quiet_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
new file mode 100644
index 000000000000..81dc04596cd1
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ timerlat_hist_parse_args(argc, argv);\
+ struct timerlat_params *tlat_params __maybe_unused =\
+ to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-i", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--irq", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-p", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--period", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-s", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--stack", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-T", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--thread", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("timerlat", "hist", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("timerlat", "hist", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "hist", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("timerlat", "hist", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("timerlat", "hist", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-k");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--kernel-threads");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-U");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--user-load");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-u");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--user-threads");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+/* Histogram Options */
+
+START_TEST(test_bucket_size_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-b", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_bucket_size_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--bucket-size", "2");
+
+ ck_assert_int_eq(params->hist.bucket_size, 2);
+}
+END_TEST
+
+START_TEST(test_entries_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-E", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_entries_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--entries", "512");
+
+ ck_assert_int_eq(params->hist.entries, 512);
+}
+END_TEST
+
+START_TEST(test_no_header)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-header");
+
+ ck_assert(params->hist.no_header);
+}
+END_TEST
+
+START_TEST(test_no_index)
+{
+ PARSE_ARGS("timerlat", "hist", "--with-zeros", "--no-index");
+
+ ck_assert(params->hist.no_index);
+}
+END_TEST
+
+START_TEST(test_no_irq)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-irq");
+
+ ck_assert(params->hist.no_irq);
+}
+END_TEST
+
+START_TEST(test_no_summary)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-summary");
+
+ ck_assert(params->hist.no_summary);
+}
+END_TEST
+
+START_TEST(test_no_thread)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-thread");
+
+ ck_assert(params->hist.no_thread);
+}
+END_TEST
+
+START_TEST(test_with_zeros)
+{
+ PARSE_ARGS("timerlat", "hist", "--with-zeros");
+
+ ck_assert(params->hist.with_zeros);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-n");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--nano");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+ PARSE_ARGS("timerlat", "hist", "--deepest-idle-state", "1");
+
+ ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+ PARSE_ARGS("timerlat", "hist", "--dma-latency", "10");
+
+ ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("timerlat", "hist", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("timerlat", "hist", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("timerlat", "hist", "-a", "20");
+
+ CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+ PARSE_ARGS("timerlat", "hist", "--bpf-action", "program");
+
+ ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+ PARSE_ARGS("timerlat", "hist", "--dump-tasks");
+
+ ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+ PARSE_ARGS("timerlat", "hist", "--no-aa");
+
+ ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("timerlat", "hist", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("timerlat", "hist", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+ PARSE_ARGS("timerlat", "hist", "--stack-format", "truncate");
+
+ ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("timerlat", "hist", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("timerlat", "hist", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_hist_cli_suite(void)
+{
+ Suite *s = suite_create("timerlat_hist_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_irq_short);
+ tcase_add_test(tc, test_irq_long);
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_stack_short);
+ tcase_add_test(tc, test_stack_long);
+ tcase_add_test(tc, test_thread_short);
+ tcase_add_test(tc, test_thread_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_kernel_threads_short);
+ tcase_add_test(tc, test_kernel_threads_long);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ tcase_add_test(tc, test_user_load_short);
+ tcase_add_test(tc, test_user_load_long);
+ tcase_add_test(tc, test_user_threads_short);
+ tcase_add_test(tc, test_user_threads_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("histogram_options");
+ tcase_add_test(tc, test_bucket_size_short);
+ tcase_add_test(tc, test_bucket_size_long);
+ tcase_add_test(tc, test_entries_short);
+ tcase_add_test(tc, test_entries_long);
+ tcase_add_test(tc, test_no_header);
+ tcase_add_test(tc, test_no_index);
+ tcase_add_test(tc, test_no_irq);
+ tcase_add_test(tc, test_no_summary);
+ tcase_add_test(tc, test_no_thread);
+ tcase_add_test(tc, test_with_zeros);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_nano_short);
+ tcase_add_test(tc, test_nano_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_deepest_idle_state);
+ tcase_add_test(tc, test_dma_latency);
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_bpf_action);
+ tcase_add_test(tc, test_dump_tasks);
+ tcase_add_test(tc, test_no_aa);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ tcase_add_test(tc, test_stack_format);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/timerlat_top_cli.c b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
new file mode 100644
index 000000000000..1c39008564c5
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/timerlat_top_cli.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <check.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include <linux/container_of.h>
+
+#include "cli_params_assert.h"
+#include "../../src/cli.h"
+
+#define PARSE_ARGS(...) char *argv[] = { __VA_ARGS__, NULL };\
+ int argc = sizeof(argv) / sizeof(char *) - 1;\
+ struct common_params *params =\
+ timerlat_top_parse_args(argc, argv);\
+ struct timerlat_params *tlat_params __maybe_unused =\
+ to_timerlat_params(params)
+
+/* Tracing Options */
+
+START_TEST(test_irq_short)
+{
+ PARSE_ARGS("timerlat", "top", "-i", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_irq_long)
+{
+ PARSE_ARGS("timerlat", "top", "--irq", "20");
+
+ ck_assert_int_eq(params->stop_us, 20);
+}
+END_TEST
+
+START_TEST(test_period_short)
+{
+ PARSE_ARGS("timerlat", "top", "-p", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_period_long)
+{
+ PARSE_ARGS("timerlat", "top", "--period", "200");
+
+ ck_assert_int_eq(tlat_params->timerlat_period_us, 200);
+}
+END_TEST
+
+START_TEST(test_stack_short)
+{
+ PARSE_ARGS("timerlat", "top", "-s", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_stack_long)
+{
+ PARSE_ARGS("timerlat", "top", "--stack", "20");
+
+ ck_assert_int_eq(tlat_params->print_stack, 20);
+}
+END_TEST
+
+START_TEST(test_thread_short)
+{
+ PARSE_ARGS("timerlat", "top", "-T", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+START_TEST(test_thread_long)
+{
+ PARSE_ARGS("timerlat", "top", "--thread", "20");
+
+ ck_assert_int_eq(params->stop_total_us, 20);
+}
+END_TEST
+
+/* Event Configuration */
+
+START_TEST(test_event_short)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_event_long)
+{
+ PARSE_ARGS("timerlat", "top", "--event", "system:event");
+
+ CLI_ASSERT_SINGLE_EVENT("system", "event");
+}
+END_TEST
+
+START_TEST(test_filter)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event", "--filter", "filter");
+
+ CLI_ASSERT_SINGLE_FILTER("filter");
+}
+END_TEST
+
+START_TEST(test_trigger)
+{
+ PARSE_ARGS("timerlat", "top", "-e", "system:event", "--trigger", "trigger");
+
+ CLI_ASSERT_SINGLE_TRIGGER("trigger");
+}
+END_TEST
+
+START_TEST(test_trace_short_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "-t");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_short_followarg)
+{
+ PARSE_ARGS("timerlat", "top", "-t", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_short_space)
+{
+ PARSE_ARGS("timerlat", "top", "-t", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_short_equals)
+{
+ PARSE_ARGS("timerlat", "top", "-t=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "--trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_trace_long_followarg)
+{
+ PARSE_ARGS("timerlat", "top", "--trace", "-d", "20");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+ ck_assert_int_eq(params->duration, 20); /* check if next argument is read correctly */
+}
+END_TEST
+
+START_TEST(test_trace_long_space)
+{
+ PARSE_ARGS("timerlat", "top", "--trace", "tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+START_TEST(test_trace_long_equals)
+{
+ PARSE_ARGS("timerlat", "top", "--trace=tracefile");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "tracefile");
+}
+END_TEST
+
+/* CPU Configuration */
+
+START_TEST(test_cpus_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "-c", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_cpus_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "--cpus", "0-1,3");
+
+ ck_assert_str_eq(params->cpus, "0-1,3");
+ CLI_ASSERT_CPUSET(monitored_cpus, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_short)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "-H", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+START_TEST(test_housekeeping_long)
+{
+ nr_cpus = 4;
+
+ PARSE_ARGS("timerlat", "top", "--house-keeping", "0-1,3");
+
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 3);
+}
+END_TEST
+
+/* Thread Configuration */
+
+START_TEST(test_cgroup_short_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "-C");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_short_space)
+{
+ PARSE_ARGS("timerlat", "top", "-C", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_short_equals)
+{
+ PARSE_ARGS("timerlat", "top", "-C=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_noarg)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_ptr_null(params->cgroup_name);
+}
+END_TEST
+
+START_TEST(test_cgroup_long_space)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup", "cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_cgroup_long_equals)
+{
+ PARSE_ARGS("timerlat", "top", "--cgroup=cgroup");
+
+ ck_assert(params->cgroup);
+ ck_assert_str_eq(params->cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_kernel_threads_short)
+{
+ PARSE_ARGS("timerlat", "top", "-k");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_kernel_threads_long)
+{
+ PARSE_ARGS("timerlat", "top", "--kernel-threads");
+
+ ck_assert(params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(!params->user_data);
+}
+END_TEST
+
+START_TEST(test_priority_short)
+{
+ PARSE_ARGS("timerlat", "top", "-P", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_priority_long)
+{
+ PARSE_ARGS("timerlat", "top", "--priority", "f:95");
+
+ ck_assert_int_eq(params->sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params->sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_user_load_short)
+{
+ PARSE_ARGS("timerlat", "top", "-U");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_load_long)
+{
+ PARSE_ARGS("timerlat", "top", "--user-load");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(!params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_short)
+{
+ PARSE_ARGS("timerlat", "top", "-u");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+START_TEST(test_user_threads_long)
+{
+ PARSE_ARGS("timerlat", "top", "--user-threads");
+
+ ck_assert(!params->kernel_workload);
+ ck_assert(params->user_workload);
+ ck_assert(params->user_data);
+}
+END_TEST
+
+/* Output */
+
+START_TEST(test_nano_short)
+{
+ PARSE_ARGS("timerlat", "top", "-n");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_nano_long)
+{
+ PARSE_ARGS("timerlat", "top", "--nano");
+
+ ck_assert_int_eq(params->output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_quiet_short)
+{
+ PARSE_ARGS("timerlat", "top", "-q");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+START_TEST(test_quiet_long)
+{
+ PARSE_ARGS("timerlat", "top", "--quiet");
+
+ ck_assert(params->quiet);
+}
+END_TEST
+
+/* System Tuning */
+
+START_TEST(test_deepest_idle_state)
+{
+ PARSE_ARGS("timerlat", "top", "--deepest-idle-state", "1");
+
+ ck_assert_int_eq(tlat_params->deepest_idle_state, 1);
+}
+END_TEST
+
+START_TEST(test_dma_latency)
+{
+ PARSE_ARGS("timerlat", "top", "--dma-latency", "10");
+
+ ck_assert_int_eq(tlat_params->dma_latency, 10);
+}
+END_TEST
+
+START_TEST(test_trace_buffer_size)
+{
+ PARSE_ARGS("timerlat", "top", "--trace-buffer-size", "200");
+
+ ck_assert_int_eq(params->buffer_size, 200);
+}
+END_TEST
+
+START_TEST(test_warm_up)
+{
+ PARSE_ARGS("timerlat", "top", "--warm-up", "5");
+
+ ck_assert_int_eq(params->warmup, 5);
+}
+END_TEST
+
+/* Auto Analysis and Actions */
+
+START_TEST(test_auto)
+{
+ PARSE_ARGS("timerlat", "top", "-a", "20");
+
+ CLI_TIMERLAT_ASSERT_AUTO(20);
+}
+END_TEST
+
+START_TEST(test_aa_only)
+{
+ PARSE_ARGS("timerlat", "top", "--aa-only", "20");
+
+ CLI_TIMERLAT_ASSERT_AA_ONLY(20);
+}
+END_TEST
+
+START_TEST(test_bpf_action)
+{
+ PARSE_ARGS("timerlat", "top", "--bpf-action", "program");
+
+ ck_assert_str_eq(tlat_params->bpf_action_program, "program");
+}
+END_TEST
+
+START_TEST(test_dump_tasks)
+{
+ PARSE_ARGS("timerlat", "top", "--dump-tasks");
+
+ ck_assert(tlat_params->dump_tasks);
+}
+END_TEST
+
+START_TEST(test_no_aa)
+{
+ PARSE_ARGS("timerlat", "top", "--no-aa");
+
+ ck_assert(tlat_params->no_aa);
+}
+END_TEST
+
+START_TEST(test_on_end)
+{
+ PARSE_ARGS("timerlat", "top", "--on-end", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(end_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_on_threshold)
+{
+ PARSE_ARGS("timerlat", "top", "--on-threshold", "trace");
+
+ CLI_ASSERT_SINGLE_ACTION(threshold_actions, ACTION_TRACE_OUTPUT, trace_output, str,
+ "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_stack_format)
+{
+ PARSE_ARGS("timerlat", "top", "--stack-format", "truncate");
+
+ ck_assert_int_eq(tlat_params->stack_format, STACK_FORMAT_TRUNCATE);
+}
+END_TEST
+
+/* General */
+
+START_TEST(test_debug_short)
+{
+ PARSE_ARGS("timerlat", "top", "-D");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_debug_long)
+{
+ PARSE_ARGS("timerlat", "top", "--debug");
+
+ ck_assert(config_debug);
+}
+END_TEST
+
+START_TEST(test_duration_short)
+{
+ PARSE_ARGS("timerlat", "top", "-d", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+START_TEST(test_duration_long)
+{
+ PARSE_ARGS("timerlat", "top", "--duration", "1m");
+
+ ck_assert_int_eq(params->duration, 60);
+}
+END_TEST
+
+Suite *timerlat_top_cli_suite(void)
+{
+ Suite *s = suite_create("timerlat_top_cli");
+ TCase *tc;
+
+ tc = tcase_create("tracing_options");
+ tcase_add_test(tc, test_irq_short);
+ tcase_add_test(tc, test_irq_long);
+ tcase_add_test(tc, test_period_short);
+ tcase_add_test(tc, test_period_long);
+ tcase_add_test(tc, test_stack_short);
+ tcase_add_test(tc, test_stack_long);
+ tcase_add_test(tc, test_thread_short);
+ tcase_add_test(tc, test_thread_long);
+ tcase_add_test(tc, test_trace_short_noarg);
+ tcase_add_test(tc, test_trace_short_followarg);
+ tcase_add_test(tc, test_trace_short_space);
+ tcase_add_test(tc, test_trace_short_equals);
+ tcase_add_test(tc, test_trace_long_noarg);
+ tcase_add_test(tc, test_trace_long_followarg);
+ tcase_add_test(tc, test_trace_long_space);
+ tcase_add_test(tc, test_trace_long_equals);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("event_configuration");
+ tcase_add_test(tc, test_event_short);
+ tcase_add_test(tc, test_event_long);
+ tcase_add_test(tc, test_filter);
+ tcase_add_test(tc, test_trigger);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("cpu_configuration");
+ tcase_add_test(tc, test_cpus_short);
+ tcase_add_test(tc, test_cpus_long);
+ tcase_add_test(tc, test_housekeeping_short);
+ tcase_add_test(tc, test_housekeeping_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("thread_configuration");
+ tcase_add_test(tc, test_cgroup_short_noarg);
+ tcase_add_test(tc, test_cgroup_short_space);
+ tcase_add_test(tc, test_cgroup_short_equals);
+ tcase_add_test(tc, test_cgroup_long_noarg);
+ tcase_add_test(tc, test_cgroup_long_space);
+ tcase_add_test(tc, test_cgroup_long_equals);
+ tcase_add_test(tc, test_kernel_threads_short);
+ tcase_add_test(tc, test_kernel_threads_long);
+ tcase_add_test(tc, test_priority_short);
+ tcase_add_test(tc, test_priority_long);
+ tcase_add_test(tc, test_user_load_short);
+ tcase_add_test(tc, test_user_load_long);
+ tcase_add_test(tc, test_user_threads_short);
+ tcase_add_test(tc, test_user_threads_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("output");
+ tcase_add_test(tc, test_nano_short);
+ tcase_add_test(tc, test_nano_long);
+ tcase_add_test(tc, test_quiet_short);
+ tcase_add_test(tc, test_quiet_long);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("system_tuning");
+ tcase_add_test(tc, test_deepest_idle_state);
+ tcase_add_test(tc, test_dma_latency);
+ tcase_add_test(tc, test_trace_buffer_size);
+ tcase_add_test(tc, test_warm_up);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("aa_actions");
+ tcase_add_test(tc, test_auto);
+ tcase_add_test(tc, test_aa_only);
+ tcase_add_test(tc, test_bpf_action);
+ tcase_add_test(tc, test_dump_tasks);
+ tcase_add_test(tc, test_no_aa);
+ tcase_add_test(tc, test_on_end);
+ tcase_add_test(tc, test_on_threshold);
+ tcase_add_test(tc, test_stack_format);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("general");
+ tcase_add_test(tc, test_debug_short);
+ tcase_add_test(tc, test_debug_long);
+ tcase_add_test(tc, test_duration_short);
+ tcase_add_test(tc, test_duration_long);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
index f87d761f9b12..64884f6cbdeb 100644
--- a/tools/tracing/rtla/tests/unit/unit_tests.c
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -5,17 +5,28 @@
#include <stdbool.h>
#include "../../src/utils.h"
+#include "../../src/cli.h"
Suite *utils_suite(void);
Suite *actions_suite(void);
+Suite *osnoise_top_cli_suite(void);
+Suite *osnoise_hist_cli_suite(void);
+Suite *timerlat_top_cli_suite(void);
+Suite *timerlat_hist_cli_suite(void);
int main(int argc, char *argv[])
{
int num_failed;
SRunner *sr;
+ in_unit_test = true;
+
sr = srunner_create(utils_suite());
srunner_add_suite(sr, actions_suite());
+ srunner_add_suite(sr, osnoise_top_cli_suite());
+ srunner_add_suite(sr, osnoise_hist_cli_suite());
+ srunner_add_suite(sr, timerlat_top_cli_suite());
+ srunner_add_suite(sr, timerlat_hist_cli_suite());
srunner_run_all(sr, CK_VERBOSE);
num_failed = srunner_ntests_failed(sr);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6/6] rtla/tests: Add unit tests for CLI option callbacks
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
In addition to testing all tool_parse_args() functions, test also all
callbacks used for parsing custom option formats.
The callbacks represent a middle layer between the parsing functions
and utility functions dedicated to checking specific argument formats,
for example, scheduling class and duration. Callback tests are run
before parsing functions to make sure any issue in the former is
reported before it is encountered through the latter.
Tests verify both successful parsing and proper rejection of invalid
inputs (via exit tests). To enable testing static callbacks, a pragma
once guard is added to timerlat.h for safe inclusion by cli_p.h.
Add dependency of UNIT_TESTS_IN on LIBSUBCMD_INCLUDES, as the new test
file tests/unit/cli_opt_callback.c includes cli_p.h which includes
subcmd/parse-options.h.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/timerlat.h | 2 +
tools/tracing/rtla/tests/unit/Build | 1 +
tools/tracing/rtla/tests/unit/Makefile.unit | 2 +-
.../rtla/tests/unit/cli_opt_callback.c | 704 ++++++++++++++++++
tools/tracing/rtla/tests/unit/unit_tests.c | 2 +
5 files changed, 710 insertions(+), 1 deletion(-)
create mode 100644 tools/tracing/rtla/tests/unit/cli_opt_callback.c
diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index 37a808f1611e..38ab6b41a15e 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -1,4 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
+#pragma once
+
#include "osnoise.h"
/*
diff --git a/tools/tracing/rtla/tests/unit/Build b/tools/tracing/rtla/tests/unit/Build
index 16139f17ea1f..d5a0f13922be 100644
--- a/tools/tracing/rtla/tests/unit/Build
+++ b/tools/tracing/rtla/tests/unit/Build
@@ -5,3 +5,4 @@ unit_tests-y += osnoise_top_cli.o
unit_tests-y += osnoise_hist_cli.o
unit_tests-y += timerlat_top_cli.o
unit_tests-y += timerlat_hist_cli.o
+unit_tests-y += cli_opt_callback.o
diff --git a/tools/tracing/rtla/tests/unit/Makefile.unit b/tools/tracing/rtla/tests/unit/Makefile.unit
index 8c33a9583c30..839abda64b76 100644
--- a/tools/tracing/rtla/tests/unit/Makefile.unit
+++ b/tools/tracing/rtla/tests/unit/Makefile.unit
@@ -6,7 +6,7 @@ UNIT_TESTS_IN := $(UNIT_TESTS)-in.o
$(UNIT_TESTS): $(UNIT_TESTS_IN) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(EXTLIBS) -lcheck
-$(UNIT_TESTS_IN): fixdep
+$(UNIT_TESTS_IN): fixdep $(LIBSUBCMD_INCLUDES)
make $(build)=unit_tests
unit-tests: FORCE
diff --git a/tools/tracing/rtla/tests/unit/cli_opt_callback.c b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
new file mode 100644
index 000000000000..01647f4227d1
--- /dev/null
+++ b/tools/tracing/rtla/tests/unit/cli_opt_callback.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <check.h>
+
+#define RTLA_ALLOW_CLI_P_H
+#include "../../src/cli_p.h"
+#include "cli_params_assert.h"
+
+#define TEST_CALLBACK(value, cb) OPT_CALLBACK('t', "test", value, "test value", "test help", cb)
+
+START_TEST(test_opt_llong_callback_simple)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_max)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "9223372036854775807", 0), 0);
+ ck_assert_int_eq(test_value, 9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_llong_callback_min)
+{
+ long long test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_llong_callback);
+
+ ck_assert_int_eq(opt_llong_callback(&opt, "-9223372036854775808", 0), 0);
+ ck_assert_int_eq(test_value, ~9223372036854775807LL);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_simple)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890", 0), 0);
+ ck_assert_int_eq(test_value, 1234567890);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_max)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "2147483647", 0), 0);
+ ck_assert_int_eq(test_value, 2147483647);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_min)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "-2147483648", 0), 0);
+ ck_assert_int_eq(test_value, -2147483648);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_int_callback_non_numeric_suffix)
+{
+ int test_value = 0;
+ const struct option opt = TEST_CALLBACK(&test_value, opt_int_callback);
+
+ ck_assert_int_eq(opt_int_callback(&opt, "1234567890abc", 0), -1);
+ ck_assert_int_eq(test_value, 0);
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_cpus_cb);
+
+ nr_cpus = 4;
+ ck_assert_int_eq(opt_cpus_cb(&opt, "0-3", 0), 0);
+ ck_assert_str_eq(params.cpus, "0-3");
+}
+END_TEST
+
+START_TEST(test_opt_cpus_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_cpus_cb);
+
+ nr_cpus = 4;
+ assert(freopen("/dev/null", "w", stderr));
+ opt_cpus_cb(&opt, "0-3,5", 0);
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_cgroup_cb);
+
+ ck_assert_int_eq(opt_cgroup_cb(&opt, "cgroup", 0), 0);
+ ck_assert_int_eq(params.cgroup, 1);
+ ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_cgroup_cb_equals)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_cgroup_cb);
+
+ ck_assert_int_eq(opt_cgroup_cb(&opt, "=cgroup", 0), 0);
+ ck_assert_int_eq(params.cgroup, 1);
+ ck_assert_str_eq(params.cgroup_name, "cgroup");
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_duration_cb);
+
+ ck_assert_int_eq(opt_duration_cb(&opt, "1m", 0), 0);
+ ck_assert_int_eq(params.duration, 60);
+}
+END_TEST
+
+START_TEST(test_opt_duration_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_duration_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_duration_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+ ck_assert_str_eq(events->system, "sched");
+ ck_assert_str_eq(events->event, "sched_switch");
+ ck_assert_ptr_eq(events->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_event_cb_multiple)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_event_cb);
+
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_switch", 0), 0);
+ ck_assert_int_eq(opt_event_cb(&opt, "sched:sched_wakeup", 0), 0);
+ ck_assert_str_eq(events->system, "sched");
+ ck_assert_str_eq(events->event, "sched_wakeup");
+ ck_assert_str_eq(events->next->system, "sched");
+ ck_assert_str_eq(events->next->event, "sched_switch");
+ ck_assert_ptr_eq(events->next->next, NULL);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb)
+{
+ struct common_params __params = {0};
+ struct common_params *params = &__params;
+ const struct option opt = TEST_CALLBACK(params, opt_housekeeping_cb);
+
+ nr_cpus = 4;
+ ck_assert_int_eq(opt_housekeeping_cb(&opt, "0-3", 0), 0);
+ ck_assert_int_eq(params->hk_cpus, 1);
+ CLI_ASSERT_CPUSET(hk_cpu_set, 0, 1, 2, 3);
+}
+END_TEST
+
+START_TEST(test_opt_housekeeping_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_housekeeping_cb);
+
+ nr_cpus = 4;
+ assert(freopen("/dev/null", "w", stderr));
+ opt_housekeeping_cb(&opt, "0-3,5", 0);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_priority_cb);
+
+ ck_assert_int_eq(opt_priority_cb(&opt, "f:95", 0), 0);
+ ck_assert_int_eq(params.sched_param.sched_policy, SCHED_FIFO);
+ ck_assert_int_eq(params.sched_param.sched_priority, 95);
+}
+END_TEST
+
+START_TEST(test_opt_priority_cb_invalid)
+{
+ struct common_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_priority_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_priority_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb)
+{
+ struct trace_events *events = trace_event_alloc("sched:sched_switch");
+ const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+ ck_assert_int_eq(opt_trigger_cb(&opt, "stacktrace", 0), 0);
+ ck_assert_str_eq(events->trigger, "stacktrace");
+}
+END_TEST
+
+START_TEST(test_opt_trigger_cb_no_event)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_trigger_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_trigger_cb(&opt, "stacktrace", 0);
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb)
+{
+ struct trace_events *events = trace_event_alloc("sched:sched_switch");
+ const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+ ck_assert_int_eq(opt_filter_cb(&opt, "comm ~ \"rtla\"", 0), 0);
+ ck_assert_str_eq(events->filter, "comm ~ \"rtla\"");
+}
+END_TEST
+
+START_TEST(test_opt_filter_cb_no_event)
+{
+ struct trace_events *events = NULL;
+ const struct option opt = TEST_CALLBACK(&events, opt_filter_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_filter_cb(&opt, "comm ~ \"rtla\"", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_auto_cb)
+{
+ struct osnoise_params params = {0};
+ struct osnoise_cb_data cb_data = {¶ms};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_osnoise_auto_cb);
+
+ ck_assert_int_eq(opt_osnoise_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.threshold, 1);
+ ck_assert_str_eq(cb_data.trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_period_cb)
+{
+ unsigned long long period = 0;
+ const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+ ck_assert_int_eq(opt_osnoise_period_cb(&opt, "1000000", 0), 0);
+ ck_assert_int_eq(period, 1000000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_period_cb_invalid)
+{
+ unsigned long long period = 0;
+ const struct option opt = TEST_CALLBACK(&period, opt_osnoise_period_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_osnoise_period_cb(&opt, "10000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb)
+{
+ unsigned long long runtime = 0;
+ const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+ ck_assert_int_eq(opt_osnoise_runtime_cb(&opt, "900000", 0), 0);
+ ck_assert_int_eq(runtime, 900000);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_runtime_cb_invalid)
+{
+ unsigned long long runtime = 0;
+ const struct option opt = TEST_CALLBACK(&runtime, opt_osnoise_runtime_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_osnoise_runtime_cb(&opt, "99", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_trace_output_cb_noarg)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_osnoise_trace_output_cb);
+
+ ck_assert_int_eq(opt_osnoise_trace_output_cb(&opt, NULL, 0), 0);
+ ck_assert_str_eq(trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+ ck_assert_int_eq(opt_osnoise_on_threshold_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_threshold_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_threshold_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_osnoise_on_threshold_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+ ck_assert_int_eq(opt_osnoise_on_end_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "osnoise_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_osnoise_on_end_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_osnoise_on_end_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_osnoise_on_end_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb)
+{
+ long long period = 0;
+ const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+ ck_assert_int_eq(opt_timerlat_period_cb(&opt, "1000", 0), 0);
+ ck_assert_int_eq(period, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_period_cb_invalid)
+{
+ long long period = 0;
+ const struct option opt = TEST_CALLBACK(&period, opt_timerlat_period_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_timerlat_period_cb(&opt, "1000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_auto_cb)
+{
+ struct timerlat_params params = {0};
+ struct timerlat_cb_data cb_data = {¶ms};
+ const struct option opt = TEST_CALLBACK(&cb_data, opt_timerlat_auto_cb);
+
+ ck_assert_int_eq(opt_timerlat_auto_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.common.stop_total_us, 10);
+ ck_assert_int_eq(params.print_stack, 10);
+ ck_assert_str_eq(cb_data.trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb)
+{
+ int dma_latency = 0;
+ const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+ ck_assert_int_eq(opt_dma_latency_cb(&opt, "1000", 0), 0);
+ ck_assert_int_eq(dma_latency, 1000);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_min)
+{
+ int dma_latency = 0;
+ const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_dma_latency_cb(&opt, "-1", 0);
+}
+END_TEST
+
+START_TEST(test_opt_dma_latency_cb_max)
+{
+ int dma_latency = 0;
+ const struct option opt = TEST_CALLBACK(&dma_latency, opt_dma_latency_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_dma_latency_cb(&opt, "10001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_aa_only_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_aa_only_cb);
+
+ ck_assert_int_eq(opt_aa_only_cb(&opt, "10", 0), 0);
+ ck_assert_int_eq(params.common.stop_us, 10);
+ ck_assert_int_eq(params.common.stop_total_us, 10);
+ ck_assert_int_eq(params.print_stack, 10);
+ ck_assert_int_eq(params.common.aa_only, 1);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, "trace.txt", 0), 0);
+ ck_assert_str_eq(trace_output, "trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_trace_output_cb_noarg)
+{
+ const char *trace_output = NULL;
+ const struct option opt = TEST_CALLBACK(&trace_output, opt_timerlat_trace_output_cb);
+
+ ck_assert_int_eq(opt_timerlat_trace_output_cb(&opt, NULL, 0), 0);
+ ck_assert_str_eq(trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+ ck_assert_int_eq(opt_timerlat_on_threshold_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_threshold_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_threshold_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_timerlat_on_threshold_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+ ck_assert_int_eq(opt_timerlat_on_end_cb(&opt, "trace", 0), 0);
+ ck_assert_int_eq(actions.len, 1);
+ ck_assert_int_eq(actions.list[0].type, ACTION_TRACE_OUTPUT);
+ ck_assert_str_eq(actions.list[0].trace_output, "timerlat_trace.txt");
+}
+END_TEST
+
+START_TEST(test_opt_timerlat_on_end_cb_invalid)
+{
+ struct actions actions = {0};
+ const struct option opt = TEST_CALLBACK(&actions, opt_timerlat_on_end_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_timerlat_on_end_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_user_threads_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_user_threads_cb);
+
+ ck_assert_int_eq(opt_user_threads_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(params.common.user_workload, 1);
+ ck_assert_int_eq(params.common.user_data, 1);
+}
+END_TEST
+
+START_TEST(test_opt_nano_cb)
+{
+ struct timerlat_params params = {0};
+ const struct option opt = TEST_CALLBACK(¶ms, opt_nano_cb);
+
+ ck_assert_int_eq(opt_nano_cb(&opt, NULL, 0), 0);
+ ck_assert_int_eq(params.common.output_divisor, 1);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb)
+{
+ int stack_format = 0;
+ const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+ ck_assert_int_eq(opt_stack_format_cb(&opt, "full", 0), 0);
+ ck_assert_int_eq(stack_format, STACK_FORMAT_FULL);
+}
+END_TEST
+
+START_TEST(test_opt_stack_format_cb_invalid)
+{
+ int stack_format = 0;
+ const struct option opt = TEST_CALLBACK(&stack_format, opt_stack_format_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_stack_format_cb(&opt, "abc", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_cb)
+{
+ int bucket_size = 0;
+ const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+ ck_assert_int_eq(opt_bucket_size_cb(&opt, "100", 0), 0);
+ ck_assert_int_eq(bucket_size, 100);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_min)
+{
+ int bucket_size = 0;
+ const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_bucket_size_cb(&opt, "0", 0);
+}
+END_TEST
+
+START_TEST(test_opt_bucket_size_max)
+{
+ int bucket_size = 0;
+ const struct option opt = TEST_CALLBACK(&bucket_size, opt_bucket_size_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_bucket_size_cb(&opt, "1000001", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_cb)
+{
+ int entries = 0;
+ const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+ ck_assert_int_eq(opt_entries_cb(&opt, "100", 0), 0);
+ ck_assert_int_eq(entries, 100);
+}
+END_TEST
+
+START_TEST(test_opt_entries_min)
+{
+ int entries = 0;
+ const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_entries_cb(&opt, "9", 0);
+}
+END_TEST
+
+START_TEST(test_opt_entries_max)
+{
+ int entries = 0;
+ const struct option opt = TEST_CALLBACK(&entries, opt_entries_cb);
+
+ assert(freopen("/dev/null", "w", stderr));
+ opt_entries_cb(&opt, "10000000", 0);
+}
+END_TEST
+
+Suite *cli_opt_callback_suite(void)
+{
+ Suite *s = suite_create("cli_opt_callback");
+ TCase *tc;
+
+ tc = tcase_create("common");
+ tcase_add_test(tc, test_opt_llong_callback_simple);
+ tcase_add_test(tc, test_opt_llong_callback_max);
+ tcase_add_test(tc, test_opt_llong_callback_min);
+ tcase_add_test(tc, test_opt_int_callback_simple);
+ tcase_add_test(tc, test_opt_int_callback_max);
+ tcase_add_test(tc, test_opt_int_callback_min);
+ tcase_add_test(tc, test_opt_int_callback_non_numeric);
+ tcase_add_test(tc, test_opt_int_callback_non_numeric_suffix);
+ tcase_add_test(tc, test_opt_cpus_cb);
+ tcase_add_exit_test(tc, test_opt_cpus_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_cgroup_cb);
+ tcase_add_test(tc, test_opt_cgroup_cb_equals);
+ tcase_add_test(tc, test_opt_duration_cb);
+ tcase_add_exit_test(tc, test_opt_duration_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_event_cb);
+ tcase_add_test(tc, test_opt_event_cb_multiple);
+ tcase_add_test(tc, test_opt_housekeeping_cb);
+ tcase_add_exit_test(tc, test_opt_housekeeping_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_priority_cb);
+ tcase_add_exit_test(tc, test_opt_priority_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_trigger_cb);
+ tcase_add_exit_test(tc, test_opt_trigger_cb_no_event, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_filter_cb);
+ tcase_add_exit_test(tc, test_opt_filter_cb_no_event, EXIT_FAILURE);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("osnoise");
+ tcase_add_test(tc, test_opt_osnoise_auto_cb);
+ tcase_add_test(tc, test_opt_osnoise_period_cb);
+ tcase_add_exit_test(tc, test_opt_osnoise_period_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_osnoise_runtime_cb);
+ tcase_add_exit_test(tc, test_opt_osnoise_runtime_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_osnoise_trace_output_cb);
+ tcase_add_test(tc, test_opt_osnoise_trace_output_cb_noarg);
+ tcase_add_test(tc, test_opt_osnoise_on_threshold_cb);
+ tcase_add_exit_test(tc, test_opt_osnoise_on_threshold_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_osnoise_on_end_cb);
+ tcase_add_exit_test(tc, test_opt_osnoise_on_end_cb_invalid, EXIT_FAILURE);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("timerlat");
+ tcase_add_test(tc, test_opt_timerlat_period_cb);
+ tcase_add_exit_test(tc, test_opt_timerlat_period_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_timerlat_auto_cb);
+ tcase_add_test(tc, test_opt_dma_latency_cb);
+ tcase_add_exit_test(tc, test_opt_dma_latency_cb_min, EXIT_FAILURE);
+ tcase_add_exit_test(tc, test_opt_dma_latency_cb_max, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_aa_only_cb);
+ tcase_add_test(tc, test_opt_timerlat_trace_output_cb);
+ tcase_add_test(tc, test_opt_timerlat_trace_output_cb_noarg);
+ tcase_add_test(tc, test_opt_timerlat_on_threshold_cb);
+ tcase_add_exit_test(tc, test_opt_timerlat_on_threshold_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_timerlat_on_end_cb);
+ tcase_add_exit_test(tc, test_opt_timerlat_on_end_cb_invalid, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_user_threads_cb);
+ tcase_add_test(tc, test_opt_nano_cb);
+ tcase_add_test(tc, test_opt_stack_format_cb);
+ tcase_add_exit_test(tc, test_opt_stack_format_cb_invalid, EXIT_FAILURE);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("histogram");
+ tcase_add_test(tc, test_opt_bucket_size_cb);
+ tcase_add_exit_test(tc, test_opt_bucket_size_min, EXIT_FAILURE);
+ tcase_add_exit_test(tc, test_opt_bucket_size_max, EXIT_FAILURE);
+ tcase_add_test(tc, test_opt_entries_cb);
+ tcase_add_exit_test(tc, test_opt_entries_min, EXIT_FAILURE);
+ tcase_add_exit_test(tc, test_opt_entries_max, EXIT_FAILURE);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/tools/tracing/rtla/tests/unit/unit_tests.c b/tools/tracing/rtla/tests/unit/unit_tests.c
index 64884f6cbdeb..75ca813f81ca 100644
--- a/tools/tracing/rtla/tests/unit/unit_tests.c
+++ b/tools/tracing/rtla/tests/unit/unit_tests.c
@@ -13,6 +13,7 @@ Suite *osnoise_top_cli_suite(void);
Suite *osnoise_hist_cli_suite(void);
Suite *timerlat_top_cli_suite(void);
Suite *timerlat_hist_cli_suite(void);
+Suite *cli_opt_callback_suite(void);
int main(int argc, char *argv[])
{
@@ -22,6 +23,7 @@ int main(int argc, char *argv[])
in_unit_test = true;
sr = srunner_create(utils_suite());
+ srunner_add_suite(sr, cli_opt_callback_suite());
srunner_add_suite(sr, actions_suite());
srunner_add_suite(sr, osnoise_top_cli_suite());
srunner_add_suite(sr, osnoise_hist_cli_suite());
--
2.54.0
^ permalink raw reply related
* [PATCH v2 4/6] rtla: Parse cmdline using libsubcmd
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
Instead of using getopt_long() directly to parse the command line
arguments given to an RTLA tool, use libsubcmd's parse_options().
Utilizing libsubcmd for parsing command line arguments has several
benefits:
- A help message is automatically generated by libsubcmd from the
specification, removing the need of writing it by hand.
- Options are sorted into groups based on which part of tracing (CPU,
thread, auto-analysis, tuning, histogram) they relate to.
- Common parsing patterns for numerical and boolean values now share
code, with the target variable being stored in the option array.
To avoid duplication of the option parsing logic, RTLA-specific
macros defining struct option values are created:
- RTLA_OPT_* for options common to all tools
- OSNOISE_OPT_* and TIMERLAT_OPT_* for options specific to
osnoise/timerlat tools
- HIST_OPT_* macros for options specific to histogram-based tools.
Individual *_parse_args() functions then construct an array out of
these macros that is then passed to libsubcmd's parse_options().
All code specific to command line options parsing is moved out of the
individual tool files into a new file, cli.c, which also contains the
contents of the rtla.c file. A private header, cli_p.h, is added
alongside the public header cli.h, so that unit tests are able to test
statically declared option callbacks.
Minor changes:
- The return value of tool-level help option changes to 129, as this is
the value set by libsubcmd; this is reflected in affected test cases.
The implementation of help for command-level and tracer-level help
is set to 129 as well for consistency, and the change is reflected in
exit value documentation.
- Related to the above, {rtla,osnoise,timerlat}_usage() are marked
__noreturn and exit() is removed from after they are called for
cleaner code.
- The error messages for invalid argument for options --dma-latency and
-E/--entries were corrected, fixing off-by-one in the limits.
Note that unsetting options (using --no-<opt> syntax) is currently not
implemented for options that use custom callbacks. For --irq and
--thread, it will never be implemented, as they conflict with already
existing --no-irq and --no-thread with a different meaning.
Assisted-by: Composer:composer-1.5
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
Documentation/tools/rtla/common_appendix.txt | 7 +-
tools/tracing/rtla/src/Build | 2 +-
tools/tracing/rtla/src/cli.c | 537 +++++++++++++++
tools/tracing/rtla/src/cli.h | 7 +
tools/tracing/rtla/src/cli_p.h | 670 +++++++++++++++++++
tools/tracing/rtla/src/common.c | 109 ---
tools/tracing/rtla/src/common.h | 27 +-
tools/tracing/rtla/src/osnoise.c | 9 +-
tools/tracing/rtla/src/osnoise_hist.c | 221 +-----
tools/tracing/rtla/src/osnoise_top.c | 200 +-----
tools/tracing/rtla/src/rtla.c | 92 ---
tools/tracing/rtla/src/timerlat.c | 9 +-
tools/tracing/rtla/src/timerlat.h | 4 +-
tools/tracing/rtla/src/timerlat_hist.c | 317 +--------
tools/tracing/rtla/src/timerlat_top.c | 286 +-------
tools/tracing/rtla/src/utils.c | 28 +-
tools/tracing/rtla/src/utils.h | 3 +-
tools/tracing/rtla/tests/hwnoise.t | 2 +-
tools/tracing/rtla/tests/osnoise.t | 6 +-
tools/tracing/rtla/tests/timerlat.t | 6 +-
20 files changed, 1256 insertions(+), 1286 deletions(-)
create mode 100644 tools/tracing/rtla/src/cli.c
create mode 100644 tools/tracing/rtla/src/cli.h
create mode 100644 tools/tracing/rtla/src/cli_p.h
delete mode 100644 tools/tracing/rtla/src/rtla.c
diff --git a/Documentation/tools/rtla/common_appendix.txt b/Documentation/tools/rtla/common_appendix.txt
index 8c90a02588e7..68cb15840d3a 100644
--- a/Documentation/tools/rtla/common_appendix.txt
+++ b/Documentation/tools/rtla/common_appendix.txt
@@ -26,9 +26,10 @@ EXIT STATUS
::
- 0 Passed: the test did not hit the stop tracing condition
- 1 Error: invalid argument
- 2 Failed: the test hit the stop tracing condition
+ 0 Passed: the test did not hit the stop tracing condition
+ 1 Error: invalid argument
+ 2 Failed: the test hit the stop tracing condition
+ 129 Help: either user requested help or incorrect option was specified
REPORTING BUGS
==============
diff --git a/tools/tracing/rtla/src/Build b/tools/tracing/rtla/src/Build
index 329e24a40cf7..a1f3ab927207 100644
--- a/tools/tracing/rtla/src/Build
+++ b/tools/tracing/rtla/src/Build
@@ -11,4 +11,4 @@ rtla-y += timerlat_hist.o
rtla-y += timerlat_u.o
rtla-y += timerlat_aa.o
rtla-y += timerlat_bpf.o
-rtla-y += rtla.o
+rtla-y += cli.o
diff --git a/tools/tracing/rtla/src/cli.c b/tools/tracing/rtla/src/cli.c
new file mode 100644
index 000000000000..7f531519df44
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.c
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <linux/compiler.h>
+
+#define RTLA_ALLOW_CLI_P_H
+#include "cli_p.h"
+
+static const char * const osnoise_top_usage[] = {
+ "rtla osnoise [top] [<options>] [-h|--help]",
+ NULL,
+};
+
+static const char * const osnoise_hist_usage[] = {
+ "rtla osnoise hist [<options>] [-h|--help]",
+ NULL,
+};
+
+static const char * const timerlat_top_usage[] = {
+ "rtla timerlat [top] [<options>] [-h|--help]",
+ NULL,
+};
+
+static const char * const timerlat_hist_usage[] = {
+ "rtla timerlat hist [<options>] [-h|--help]",
+ NULL,
+};
+
+static const char * const hwnoise_usage[] = {
+ "rtla hwnoise [<options>] [-h|--help]",
+ NULL,
+};
+
+static const int common_parse_options_flags = PARSE_OPT_OPTARG_ALLOW_NEXT;
+
+bool in_unit_test;
+
+/*
+ * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
+ */
+struct common_params *osnoise_top_parse_args(int argc, char **argv)
+{
+ struct osnoise_params *params;
+ struct osnoise_cb_data cb_data;
+ const char * const *usage;
+
+ params = calloc_fatal(1, sizeof(*params));
+
+ cb_data.params = params;
+ cb_data.trace_output = NULL;
+
+ if (strcmp(argv[0], "hwnoise") == 0) {
+ params->mode = MODE_HWNOISE;
+ /*
+ * Reduce CPU usage for 75% to avoid killing the system.
+ */
+ params->runtime = 750000;
+ params->period = 1000000;
+ usage = hwnoise_usage;
+ } else {
+ usage = osnoise_top_usage;
+ }
+
+ const struct option osnoise_top_options[] = {
+ OPT_GROUP("Tracing Options:"),
+ OSNOISE_OPT_PERIOD,
+ OSNOISE_OPT_RUNTIME,
+ RTLA_OPT_STOP('s', "stop", "single sample"),
+ RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
+ OSNOISE_OPT_THRESHOLD,
+ RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
+
+ OPT_GROUP("Event Configuration:"),
+ RTLA_OPT_EVENT,
+ RTLA_OPT_FILTER,
+ RTLA_OPT_TRIGGER,
+
+ OPT_GROUP("CPU Configuration:"),
+ RTLA_OPT_CPUS,
+ RTLA_OPT_HOUSEKEEPING,
+
+ OPT_GROUP("Thread Configuration:"),
+ RTLA_OPT_PRIORITY,
+ RTLA_OPT_CGROUP,
+
+ OPT_GROUP("Output:"),
+ RTLA_OPT_QUIET,
+
+ OPT_GROUP("System Tuning:"),
+ RTLA_OPT_TRACE_BUFFER_SIZE,
+ RTLA_OPT_WARM_UP,
+
+ OPT_GROUP("Auto Analysis and Actions:"),
+ RTLA_OPT_AUTO(opt_osnoise_auto_cb),
+ RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
+ RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
+
+ OPT_GROUP("General:"),
+ RTLA_OPT_DURATION,
+ RTLA_OPT_DEBUG,
+
+ OPT_END(),
+ };
+
+ actions_init(¶ms->common.threshold_actions);
+ actions_init(¶ms->common.end_actions);
+
+ argc = parse_options(argc, (const char **)argv,
+ osnoise_top_options,
+ usage,
+ common_parse_options_flags);
+ if (argc < 0)
+ return NULL;
+
+ if (cb_data.trace_output)
+ actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid())
+ fatal("osnoise needs root permission");
+
+ return ¶ms->common;
+}
+
+/*
+ * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
+ */
+struct common_params *osnoise_hist_parse_args(int argc, char **argv)
+{
+ struct osnoise_params *params;
+ struct osnoise_cb_data cb_data;
+
+ params = calloc_fatal(1, sizeof(*params));
+
+ cb_data.params = params;
+ cb_data.trace_output = NULL;
+
+ const struct option osnoise_hist_options[] = {
+ OPT_GROUP("Tracing Options:"),
+ OSNOISE_OPT_PERIOD,
+ OSNOISE_OPT_RUNTIME,
+ RTLA_OPT_STOP('s', "stop", "single sample"),
+ RTLA_OPT_STOP_TOTAL('S', "stop-total", "total sample"),
+ OSNOISE_OPT_THRESHOLD,
+ RTLA_OPT_TRACE_OUTPUT("osnoise", opt_osnoise_trace_output_cb),
+
+ OPT_GROUP("Event Configuration:"),
+ RTLA_OPT_EVENT,
+ RTLA_OPT_FILTER,
+ RTLA_OPT_TRIGGER,
+
+ OPT_GROUP("CPU Configuration:"),
+ RTLA_OPT_CPUS,
+ RTLA_OPT_HOUSEKEEPING,
+
+ OPT_GROUP("Thread Configuration:"),
+ RTLA_OPT_PRIORITY,
+ RTLA_OPT_CGROUP,
+
+ OPT_GROUP("Histogram Options:"),
+ HIST_OPT_BUCKET_SIZE,
+ HIST_OPT_ENTRIES,
+ HIST_OPT_NO_HEADER,
+ HIST_OPT_NO_SUMMARY,
+ HIST_OPT_NO_INDEX,
+ HIST_OPT_WITH_ZEROS,
+
+ OPT_GROUP("System Tuning:"),
+ RTLA_OPT_TRACE_BUFFER_SIZE,
+ RTLA_OPT_WARM_UP,
+
+ OPT_GROUP("Auto Analysis and Actions:"),
+ RTLA_OPT_AUTO(opt_osnoise_auto_cb),
+ RTLA_OPT_ON_THRESHOLD("stop-total", opt_osnoise_on_threshold_cb),
+ RTLA_OPT_ON_END(opt_osnoise_on_end_cb),
+
+ OPT_GROUP("General:"),
+ RTLA_OPT_DURATION,
+ RTLA_OPT_DEBUG,
+
+ OPT_END(),
+ };
+
+ actions_init(¶ms->common.threshold_actions);
+ actions_init(¶ms->common.end_actions);
+
+ /* display data in microseconds */
+ params->common.output_divisor = 1000;
+ params->common.hist.bucket_size = 1;
+ params->common.hist.entries = 256;
+
+ argc = parse_options(argc, (const char **)argv,
+ osnoise_hist_options, osnoise_hist_usage,
+ common_parse_options_flags);
+ if (argc < 0)
+ return NULL;
+
+ if (cb_data.trace_output)
+ actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
+
+ if (params->common.hist.no_index && !params->common.hist.with_zeros)
+ fatal("no-index set and with-zeros not set - it does not make sense");
+
+ return ¶ms->common;
+}
+
+struct common_params *timerlat_top_parse_args(int argc, char **argv)
+{
+ struct timerlat_params *params;
+ struct timerlat_cb_data cb_data;
+
+ params = calloc_fatal(1, sizeof(*params));
+
+ cb_data.params = params;
+ cb_data.trace_output = NULL;
+
+ const struct option timerlat_top_options[] = {
+ OPT_GROUP("Tracing Options:"),
+ TIMERLAT_OPT_PERIOD,
+ RTLA_OPT_STOP('i', "irq", "irq latency"),
+ RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
+ TIMERLAT_OPT_STACK,
+ RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
+
+ OPT_GROUP("Event Configuration:"),
+ RTLA_OPT_EVENT,
+ RTLA_OPT_FILTER,
+ RTLA_OPT_TRIGGER,
+
+ OPT_GROUP("CPU Configuration:"),
+ RTLA_OPT_CPUS,
+ RTLA_OPT_HOUSEKEEPING,
+
+ OPT_GROUP("Thread Configuration:"),
+ RTLA_OPT_PRIORITY,
+ RTLA_OPT_CGROUP,
+ RTLA_OPT_USER_THREADS,
+ RTLA_OPT_KERNEL_THREADS,
+ RTLA_OPT_USER_LOAD,
+
+ OPT_GROUP("Output:"),
+ TIMERLAT_OPT_NANO,
+ RTLA_OPT_QUIET,
+
+ OPT_GROUP("System Tuning:"),
+ TIMERLAT_OPT_DMA_LATENCY,
+ TIMERLAT_OPT_DEEPEST_IDLE_STATE,
+ RTLA_OPT_TRACE_BUFFER_SIZE,
+ RTLA_OPT_WARM_UP,
+
+ OPT_GROUP("Auto Analysis and Actions:"),
+ RTLA_OPT_AUTO(opt_timerlat_auto_cb),
+ TIMERLAT_OPT_AA_ONLY,
+ TIMERLAT_OPT_NO_AA,
+ TIMERLAT_OPT_DUMPS_TASKS,
+ RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
+ RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
+ TIMERLAT_OPT_BPF_ACTION,
+ TIMERLAT_OPT_STACK_FORMAT,
+
+ OPT_GROUP("General:"),
+ RTLA_OPT_DURATION,
+ RTLA_OPT_DEBUG,
+
+ OPT_END(),
+ };
+
+ actions_init(¶ms->common.threshold_actions);
+ actions_init(¶ms->common.end_actions);
+
+ /* disabled by default */
+ params->dma_latency = -1;
+ params->deepest_idle_state = -2;
+
+ /* display data in microseconds */
+ params->common.output_divisor = 1000;
+
+ /* default to BPF mode */
+ params->mode = TRACING_MODE_BPF;
+
+ /* default to truncate stack format */
+ params->stack_format = STACK_FORMAT_TRUNCATE;
+
+ argc = parse_options(argc, (const char **)argv,
+ timerlat_top_options, timerlat_top_usage,
+ common_parse_options_flags);
+ if (argc < 0)
+ return NULL;
+
+ if (cb_data.trace_output)
+ actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
+
+ /*
+ * Auto analysis only happens if stop tracing, thus:
+ */
+ if (!params->common.stop_us && !params->common.stop_total_us)
+ params->no_aa = 1;
+
+ if (params->no_aa && params->common.aa_only)
+ fatal("--no-aa and --aa-only are mutually exclusive!");
+
+ if (params->common.kernel_workload && params->common.user_workload)
+ fatal("--kernel-threads and --user-threads are mutually exclusive!");
+
+ /*
+ * If auto-analysis or trace output is enabled, switch from BPF mode to
+ * mixed mode
+ */
+ if (params->mode == TRACING_MODE_BPF &&
+ (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+ params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+ !params->no_aa))
+ params->mode = TRACING_MODE_MIXED;
+
+ return ¶ms->common;
+}
+
+struct common_params *timerlat_hist_parse_args(int argc, char **argv)
+{
+ struct timerlat_params *params;
+ struct timerlat_cb_data cb_data;
+
+ params = calloc_fatal(1, sizeof(*params));
+
+ cb_data.params = params;
+ cb_data.trace_output = NULL;
+
+ const struct option timerlat_hist_options[] = {
+ OPT_GROUP("Tracing Options:"),
+ TIMERLAT_OPT_PERIOD,
+ RTLA_OPT_STOP('i', "irq", "irq latency"),
+ RTLA_OPT_STOP_TOTAL('T', "thread", "thread latency"),
+ TIMERLAT_OPT_STACK,
+ RTLA_OPT_TRACE_OUTPUT("timerlat", opt_timerlat_trace_output_cb),
+
+ OPT_GROUP("Event Configuration:"),
+ RTLA_OPT_EVENT,
+ RTLA_OPT_FILTER,
+ RTLA_OPT_TRIGGER,
+
+ OPT_GROUP("CPU Configuration:"),
+ RTLA_OPT_CPUS,
+ RTLA_OPT_HOUSEKEEPING,
+
+ OPT_GROUP("Thread Configuration:"),
+ RTLA_OPT_PRIORITY,
+ RTLA_OPT_CGROUP,
+ RTLA_OPT_USER_THREADS,
+ RTLA_OPT_KERNEL_THREADS,
+ RTLA_OPT_USER_LOAD,
+
+ OPT_GROUP("Histogram Options:"),
+ HIST_OPT_BUCKET_SIZE,
+ HIST_OPT_ENTRIES,
+ HIST_OPT_NO_IRQ,
+ HIST_OPT_NO_THREAD,
+ HIST_OPT_NO_HEADER,
+ HIST_OPT_NO_SUMMARY,
+ HIST_OPT_NO_INDEX,
+ HIST_OPT_WITH_ZEROS,
+
+ OPT_GROUP("Output:"),
+ TIMERLAT_OPT_NANO,
+
+ OPT_GROUP("System Tuning:"),
+ TIMERLAT_OPT_DMA_LATENCY,
+ TIMERLAT_OPT_DEEPEST_IDLE_STATE,
+ RTLA_OPT_TRACE_BUFFER_SIZE,
+ RTLA_OPT_WARM_UP,
+
+ OPT_GROUP("Auto Analysis and Actions:"),
+ RTLA_OPT_AUTO(opt_timerlat_auto_cb),
+ TIMERLAT_OPT_NO_AA,
+ TIMERLAT_OPT_DUMPS_TASKS,
+ RTLA_OPT_ON_THRESHOLD("latency", opt_timerlat_on_threshold_cb),
+ RTLA_OPT_ON_END(opt_timerlat_on_end_cb),
+ TIMERLAT_OPT_BPF_ACTION,
+ TIMERLAT_OPT_STACK_FORMAT,
+
+ OPT_GROUP("General:"),
+ RTLA_OPT_DURATION,
+ RTLA_OPT_DEBUG,
+
+ OPT_END(),
+ };
+
+ actions_init(¶ms->common.threshold_actions);
+ actions_init(¶ms->common.end_actions);
+
+ /* disabled by default */
+ params->dma_latency = -1;
+
+ /* disabled by default */
+ params->deepest_idle_state = -2;
+
+ /* display data in microseconds */
+ params->common.output_divisor = 1000;
+ params->common.hist.bucket_size = 1;
+ params->common.hist.entries = 256;
+
+ /* default to BPF mode */
+ params->mode = TRACING_MODE_BPF;
+
+ /* default to truncate stack format */
+ params->stack_format = STACK_FORMAT_TRUNCATE;
+
+ argc = parse_options(argc, (const char **)argv,
+ timerlat_hist_options, timerlat_hist_usage,
+ common_parse_options_flags);
+ if (argc < 0)
+ return NULL;
+
+ if (cb_data.trace_output)
+ actions_add_trace_output(¶ms->common.threshold_actions, cb_data.trace_output);
+
+ if (geteuid())
+ fatal("rtla needs root permission");
+
+ if (params->common.hist.no_irq && params->common.hist.no_thread)
+ fatal("no-irq and no-thread set, there is nothing to do here");
+
+ if (params->common.hist.no_index && !params->common.hist.with_zeros)
+ fatal("no-index set with with-zeros is not set - it does not make sense");
+
+ /*
+ * Auto analysis only happens if stop tracing, thus:
+ */
+ if (!params->common.stop_us && !params->common.stop_total_us)
+ params->no_aa = 1;
+
+ if (params->common.kernel_workload && params->common.user_workload)
+ fatal("--kernel-threads and --user-threads are mutually exclusive!");
+
+ /*
+ * If auto-analysis or trace output is enabled, switch from BPF mode to
+ * mixed mode
+ */
+ if (params->mode == TRACING_MODE_BPF &&
+ (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
+ params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
+ !params->no_aa))
+ params->mode = TRACING_MODE_MIXED;
+
+ return ¶ms->common;
+}
+
+/*
+ * rtla_usage - print rtla usage
+ */
+__noreturn static void rtla_usage(int err)
+{
+ int i;
+
+ static const char * const msg[] = {
+ "",
+ "rtla version " VERSION,
+ "",
+ " usage: rtla COMMAND ...",
+ "",
+ " commands:",
+ " osnoise - gives information about the operating system noise (osnoise)",
+ " hwnoise - gives information about hardware-related noise",
+ " timerlat - measures the timer irq and thread latency",
+ "",
+ NULL,
+ };
+
+ for (i = 0; msg[i]; i++)
+ fprintf(stderr, "%s\n", msg[i]);
+ exit(err);
+}
+
+/*
+ * run_tool_command - try to run a rtla tool command
+ *
+ * It returns 0 if it fails. The tool's main will generally not
+ * return as they should call exit().
+ */
+int run_tool_command(int argc, char **argv, int start_position)
+{
+ if (strcmp(argv[start_position], "osnoise") == 0) {
+ osnoise_main(argc-start_position, &argv[start_position]);
+ goto ran;
+ } else if (strcmp(argv[start_position], "hwnoise") == 0) {
+ hwnoise_main(argc-start_position, &argv[start_position]);
+ goto ran;
+ } else if (strcmp(argv[start_position], "timerlat") == 0) {
+ timerlat_main(argc-start_position, &argv[start_position]);
+ goto ran;
+ }
+
+ return 0;
+ran:
+ return 1;
+}
+
+/* Set main as weak to allow overriding it for building unit test binary */
+#pragma weak main
+
+int main(int argc, char *argv[])
+{
+ int retval;
+
+ /* is it an alias? */
+ retval = run_tool_command(argc, argv, 0);
+ if (retval)
+ exit(0);
+
+ if (argc < 2)
+ goto usage;
+
+ if (strcmp(argv[1], "-h") == 0)
+ rtla_usage(129);
+ else if (strcmp(argv[1], "--help") == 0)
+ rtla_usage(129);
+
+ retval = run_tool_command(argc, argv, 1);
+ if (retval)
+ exit(0);
+
+usage:
+ rtla_usage(129);
+}
diff --git a/tools/tracing/rtla/src/cli.h b/tools/tracing/rtla/src/cli.h
new file mode 100644
index 000000000000..c49ccb3e92f5
--- /dev/null
+++ b/tools/tracing/rtla/src/cli.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+struct common_params *osnoise_top_parse_args(int argc, char **argv);
+struct common_params *osnoise_hist_parse_args(int argc, char **argv);
+struct common_params *timerlat_top_parse_args(int argc, char **argv);
+struct common_params *timerlat_hist_parse_args(int argc, char **argv);
diff --git a/tools/tracing/rtla/src/cli_p.h b/tools/tracing/rtla/src/cli_p.h
new file mode 100644
index 000000000000..3cea4f6e976e
--- /dev/null
+++ b/tools/tracing/rtla/src/cli_p.h
@@ -0,0 +1,670 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#ifndef RTLA_ALLOW_CLI_P_H
+#error "Private header file included outside of cli.c module"
+#endif
+
+#include <linux/kernel.h>
+#include <subcmd/parse-options.h>
+
+#include "cli.h"
+#include "osnoise.h"
+#include "timerlat.h"
+
+struct osnoise_cb_data {
+ struct osnoise_params *params;
+ char *trace_output;
+};
+
+struct timerlat_cb_data {
+ struct timerlat_params *params;
+ char *trace_output;
+};
+
+/*
+ * Macros for command line options common to all tools
+ *
+ * Note: Some of the options are common to both timerlat and osnoise, but
+ * have a slightly different meaning. Such options take additional arguments
+ * that have to be provided by the *_parse_args() function of the corresponding
+ * tool.
+ *
+ * All macros defined here assume the presence of a params variable of
+ * the corresponding tool type (i.e struct timerlat_params or struct osnoise_params)
+ * and a cb_data variable of the matching type.
+ */
+
+ #define RTLA_OPT_STOP(short, long, name) OPT_CALLBACK_FLAG(short, long, \
+ ¶ms->common.stop_us, \
+ "us", \
+ "stop trace if " name " is higher than the argument in us", \
+ opt_llong_callback, PARSE_OPT_NOAUTONEG)
+
+#define RTLA_OPT_STOP_TOTAL(short, long, name) OPT_CALLBACK_FLAG(short, long, \
+ ¶ms->common.stop_total_us, \
+ "us", \
+ "stop trace if " name " is higher than the argument in us", \
+ opt_llong_callback, PARSE_OPT_NOAUTONEG)
+
+#define RTLA_OPT_TRACE_OUTPUT(tracer, cb) OPT_CALLBACK_OPTARG('t', "trace", \
+ (const char **)&cb_data.trace_output, \
+ tracer "_trace.txt", \
+ "[file]", \
+ "save the stopped trace to [file|" tracer "_trace.txt]", \
+ cb)
+
+#define RTLA_OPT_CPUS OPT_CALLBACK('c', "cpus", ¶ms->common, \
+ "cpu-list", \
+ "run the tracer only on the given cpus", \
+ opt_cpus_cb)
+
+#define RTLA_OPT_CGROUP OPT_CALLBACK_OPTARG('C', "cgroup", ¶ms->common, \
+ "[cgroup_name]", NULL, \
+ "set cgroup, no argument means rtla's cgroup will be inherited", \
+ opt_cgroup_cb)
+
+#define RTLA_OPT_USER_THREADS OPT_CALLBACK_NOOPT('u', "user-threads", params, NULL, \
+ "use rtla user-space threads instead of kernel-space timerlat threads", \
+ opt_user_threads_cb)
+
+#define RTLA_OPT_KERNEL_THREADS OPT_BOOLEAN('k', "kernel-threads", \
+ ¶ms->common.kernel_workload, \
+ "use timerlat kernel-space threads instead of rtla user-space threads")
+
+#define RTLA_OPT_USER_LOAD OPT_BOOLEAN('U', "user-load", ¶ms->common.user_data, \
+ "enable timerlat for user-defined user-space workload")
+
+#define RTLA_OPT_DURATION OPT_CALLBACK('d', "duration", ¶ms->common, \
+ "time[s|m|h|d]", \
+ "set the duration of the session", \
+ opt_duration_cb)
+
+#define RTLA_OPT_EVENT OPT_CALLBACK('e', "event", ¶ms->common.events, \
+ "sys:event", \
+ "enable the <sys:event> in the trace instance, multiple -e are allowed", \
+ opt_event_cb)
+
+#define RTLA_OPT_HOUSEKEEPING OPT_CALLBACK('H', "house-keeping", ¶ms->common, \
+ "cpu-list", \
+ "run rtla control threads only on the given cpus", \
+ opt_housekeeping_cb)
+
+#define RTLA_OPT_PRIORITY OPT_CALLBACK('P', "priority", ¶ms->common, \
+ "o:prio|r:prio|f:prio|d:runtime:period", \
+ "set scheduling parameters", \
+ opt_priority_cb)
+
+#define RTLA_OPT_TRIGGER OPT_CALLBACK(0, "trigger", ¶ms->common.events, \
+ "trigger", \
+ "enable a trace event trigger to the previous -e event", \
+ opt_trigger_cb)
+
+#define RTLA_OPT_FILTER OPT_CALLBACK(0, "filter", ¶ms->common.events, \
+ "filter", \
+ "enable a trace event filter to the previous -e event", \
+ opt_filter_cb)
+
+#define RTLA_OPT_QUIET OPT_BOOLEAN('q', "quiet", ¶ms->common.quiet, \
+ "print only a summary at the end")
+
+#define RTLA_OPT_TRACE_BUFFER_SIZE OPT_CALLBACK(0, "trace-buffer-size", \
+ ¶ms->common.buffer_size, "kB", \
+ "set the per-cpu trace buffer size in kB", \
+ opt_int_callback)
+
+#define RTLA_OPT_WARM_UP OPT_CALLBACK(0, "warm-up", ¶ms->common.warmup, "s", \
+ "let the workload run for s seconds before collecting data", \
+ opt_int_callback)
+
+#define RTLA_OPT_AUTO(cb) OPT_CALLBACK('a', "auto", &cb_data, "us", \
+ "set automatic trace mode, stopping the session if argument in us sample is hit", \
+ cb)
+
+#define RTLA_OPT_ON_THRESHOLD(threshold, cb) OPT_CALLBACK(0, "on-threshold", \
+ ¶ms->common.threshold_actions, \
+ "action", \
+ "define action to be executed at " threshold " threshold, multiple are allowed", \
+ cb)
+
+#define RTLA_OPT_ON_END(cb) OPT_CALLBACK(0, "on-end", ¶ms->common.end_actions, \
+ "action", \
+ "define action to be executed at measurement end, multiple are allowed", \
+ cb)
+
+#define RTLA_OPT_DEBUG OPT_BOOLEAN('D', "debug", &config_debug, \
+ "print debug info")
+
+/*
+ * Common callback functions for command line options
+ */
+
+static int opt_llong_callback(const struct option *opt, const char *arg, int unset)
+{
+ long long *value = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *value = get_llong_from_str((char *)arg);
+ return 0;
+}
+
+static int opt_int_callback(const struct option *opt, const char *arg, int unset)
+{
+ int *value = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ if (strtoi(arg, value))
+ return -1;
+
+ return 0;
+}
+
+static int opt_cpus_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct common_params *params = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = parse_cpu_set((char *)arg, ¶ms->monitored_cpus);
+ if (retval)
+ fatal("Invalid -c cpu list");
+ params->cpus = (char *)arg;
+
+ return 0;
+}
+
+static int opt_cgroup_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct common_params *params = opt->value;
+
+ if (unset)
+ return -1;
+
+ params->cgroup = 1;
+ params->cgroup_name = (char *)arg;
+ if (params->cgroup_name && params->cgroup_name[0] == '=')
+ /* Allow -C=<cgroup_name> next to -C[ ]<cgroup_name> */
+ ++params->cgroup_name;
+
+ return 0;
+}
+
+static int opt_duration_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct common_params *params = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ params->duration = parse_seconds_duration((char *)arg);
+ if (!params->duration)
+ fatal("Invalid -d duration");
+
+ return 0;
+}
+
+static int opt_event_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct trace_events **events = opt->value;
+ struct trace_events *tevent;
+
+ if (unset || !arg)
+ return -1;
+
+ tevent = trace_event_alloc((char *)arg);
+ if (!tevent)
+ fatal("Error alloc trace event");
+
+ if (*events)
+ tevent->next = *events;
+ *events = tevent;
+
+ return 0;
+}
+
+static int opt_housekeeping_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct common_params *params = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ params->hk_cpus = 1;
+ retval = parse_cpu_set((char *)arg, ¶ms->hk_cpu_set);
+ if (retval)
+ fatal("Error parsing house keeping CPUs");
+
+ return 0;
+}
+
+static int opt_priority_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct common_params *params = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = parse_prio((char *)arg, ¶ms->sched_param);
+ if (retval == -1)
+ fatal("Invalid -P priority");
+ params->set_sched = 1;
+
+ return 0;
+}
+
+static int opt_trigger_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct trace_events **events = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ if (!*events)
+ fatal("--trigger requires a previous -e");
+
+ trace_event_add_trigger(*events, (char *)arg);
+
+ return 0;
+}
+
+static int opt_filter_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct trace_events **events = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ if (!*events)
+ fatal("--filter requires a previous -e");
+
+ trace_event_add_filter(*events, (char *)arg);
+
+ return 0;
+}
+
+/*
+ * Macros for command line options specific to osnoise
+ */
+#define OSNOISE_OPT_PERIOD OPT_CALLBACK('p', "period", ¶ms->period, "us", \
+ "osnoise period in us", \
+ opt_osnoise_period_cb)
+
+#define OSNOISE_OPT_RUNTIME OPT_CALLBACK('r', "runtime", ¶ms->runtime, "us", \
+ "osnoise runtime in us", \
+ opt_osnoise_runtime_cb)
+
+#define OSNOISE_OPT_THRESHOLD OPT_CALLBACK('T', "threshold", ¶ms->threshold, "us", \
+ "the minimum delta to be considered a noise", \
+ opt_llong_callback)
+
+/*
+ * Callback functions for command line options for osnoise tools
+ */
+
+static int opt_osnoise_auto_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct osnoise_cb_data *cb_data = opt->value;
+ struct osnoise_params *params = cb_data->params;
+ long long auto_thresh;
+
+ if (unset || !arg)
+ return -1;
+
+ auto_thresh = get_llong_from_str((char *)arg);
+ params->common.stop_us = auto_thresh;
+ params->threshold = 1;
+
+ if (!cb_data->trace_output)
+ cb_data->trace_output = "osnoise_trace.txt";
+
+ return 0;
+}
+
+static int opt_osnoise_period_cb(const struct option *opt, const char *arg, int unset)
+{
+ unsigned long long *period = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *period = get_llong_from_str((char *)arg);
+ if (*period > 10000000)
+ fatal("Period longer than 10 s");
+
+ return 0;
+}
+
+static int opt_osnoise_runtime_cb(const struct option *opt, const char *arg, int unset)
+{
+ unsigned long long *runtime = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *runtime = get_llong_from_str((char *)arg);
+ if (*runtime < 100)
+ fatal("Runtime shorter than 100 us");
+
+ return 0;
+}
+
+static int opt_osnoise_trace_output_cb(const struct option *opt, const char *arg, int unset)
+{
+ const char **trace_output = opt->value;
+
+ if (unset)
+ return -1;
+
+ if (!arg) {
+ *trace_output = "osnoise_trace.txt";
+ } else {
+ *trace_output = (char *)arg;
+ if (*trace_output && (*trace_output)[0] == '=')
+ /* Allow -t=<trace_output> next to -t[ ]<trace_output> */
+ ++*trace_output;
+ }
+
+ return 0;
+}
+
+static int opt_osnoise_on_threshold_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct actions *actions = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", arg);
+
+ return 0;
+}
+
+static int opt_osnoise_on_end_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct actions *actions = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = actions_parse(actions, (char *)arg, "osnoise_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", arg);
+
+ return 0;
+}
+
+/*
+ * Macros for command line options specific to timerlat
+ */
+#define TIMERLAT_OPT_PERIOD OPT_CALLBACK('p', "period", ¶ms->timerlat_period_us, "us", \
+ "timerlat period in us", \
+ opt_timerlat_period_cb)
+
+#define TIMERLAT_OPT_STACK OPT_CALLBACK('s', "stack", ¶ms->print_stack, "us", \
+ "save the stack trace at the IRQ if a thread latency is higher than the argument in us", \
+ opt_llong_callback)
+
+#define TIMERLAT_OPT_NANO OPT_CALLBACK_NOOPT('n', "nano", params, NULL, \
+ "display data in nanoseconds", \
+ opt_nano_cb)
+
+#define TIMERLAT_OPT_DMA_LATENCY OPT_CALLBACK(0, "dma-latency", ¶ms->dma_latency, "us", \
+ "set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency", \
+ opt_dma_latency_cb)
+
+#define TIMERLAT_OPT_DEEPEST_IDLE_STATE OPT_CALLBACK(0, "deepest-idle-state", \
+ ¶ms->deepest_idle_state, "n", \
+ "only go down to idle state n on cpus used by timerlat to reduce exit from idle latency", \
+ opt_int_callback)
+
+#define TIMERLAT_OPT_AA_ONLY OPT_CALLBACK(0, "aa-only", params, "us", \
+ "stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)", \
+ opt_aa_only_cb)
+
+#define TIMERLAT_OPT_NO_AA OPT_BOOLEAN(0, "no-aa", ¶ms->no_aa, \
+ "disable auto-analysis, reducing rtla timerlat cpu usage")
+
+#define TIMERLAT_OPT_DUMPS_TASKS OPT_BOOLEAN(0, "dump-tasks", ¶ms->dump_tasks, \
+ "prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)")
+
+#define TIMERLAT_OPT_BPF_ACTION OPT_STRING(0, "bpf-action", ¶ms->bpf_action_program, \
+ "program", \
+ "load and execute BPF program when latency threshold is exceeded")
+
+#define TIMERLAT_OPT_STACK_FORMAT OPT_CALLBACK(0, "stack-format", ¶ms->stack_format, "format", \
+ "set the stack format (truncate, skip, full)", \
+ opt_stack_format_cb)
+
+/*
+ * Callback functions for command line options for timerlat tools
+ */
+
+static int opt_timerlat_period_cb(const struct option *opt, const char *arg, int unset)
+{
+ long long *period = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *period = get_llong_from_str((char *)arg);
+ if (*period > 1000000)
+ fatal("Period longer than 1 s");
+
+ return 0;
+}
+
+static int opt_timerlat_auto_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct timerlat_cb_data *cb_data = opt->value;
+ struct timerlat_params *params = cb_data->params;
+ long long auto_thresh;
+
+ if (unset || !arg)
+ return -1;
+
+ auto_thresh = get_llong_from_str((char *)arg);
+ params->common.stop_total_us = auto_thresh;
+ params->common.stop_us = auto_thresh;
+ params->print_stack = auto_thresh;
+
+ if (!cb_data->trace_output)
+ cb_data->trace_output = "timerlat_trace.txt";
+
+ return 0;
+}
+
+static int opt_dma_latency_cb(const struct option *opt, const char *arg, int unset)
+{
+ int *dma_latency = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = strtoi((char *)arg, dma_latency);
+ if (retval)
+ fatal("Invalid -dma-latency %s", arg);
+ if (*dma_latency < 0 || *dma_latency > 10000)
+ fatal("--dma-latency needs to be >= 0 and <= 10000");
+
+ return 0;
+}
+
+static int opt_aa_only_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct timerlat_params *params = opt->value;
+ long long auto_thresh;
+
+ if (unset || !arg)
+ return -1;
+
+ auto_thresh = get_llong_from_str((char *)arg);
+ params->common.stop_total_us = auto_thresh;
+ params->common.stop_us = auto_thresh;
+ params->print_stack = auto_thresh;
+ params->common.aa_only = 1;
+
+ return 0;
+}
+
+static int opt_timerlat_trace_output_cb(const struct option *opt, const char *arg, int unset)
+{
+ const char **trace_output = opt->value;
+
+ if (unset)
+ return -1;
+
+ if (!arg) {
+ *trace_output = "timerlat_trace.txt";
+ } else {
+ *trace_output = (char *)arg;
+ if (*trace_output && (*trace_output)[0] == '=')
+ /* Allow -t=<trace_output> next to -t[ ]<trace_output> */
+ ++*trace_output;
+ }
+
+ return 0;
+}
+
+static int opt_timerlat_on_threshold_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct actions *actions = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", arg);
+
+ return 0;
+}
+
+static int opt_timerlat_on_end_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct actions *actions = opt->value;
+ int retval;
+
+ if (unset || !arg)
+ return -1;
+
+ retval = actions_parse(actions, (char *)arg, "timerlat_trace.txt");
+ if (retval)
+ fatal("Invalid action %s", arg);
+
+ return 0;
+}
+
+static int opt_user_threads_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct timerlat_params *params = opt->value;
+
+ if (unset)
+ return -1;
+
+ params->common.user_workload = true;
+ params->common.user_data = true;
+
+ return 0;
+}
+
+static int opt_nano_cb(const struct option *opt, const char *arg, int unset)
+{
+ struct timerlat_params *params = opt->value;
+
+ if (unset)
+ return -1;
+
+ params->common.output_divisor = 1;
+
+ return 0;
+}
+
+static int opt_stack_format_cb(const struct option *opt, const char *arg, int unset)
+{
+ int *format = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *format = parse_stack_format((char *)arg);
+
+ if (*format == -1)
+ fatal("Invalid --stack-format option");
+
+ return 0;
+}
+
+/*
+ * Macros for command line options specific to histogram-based tools
+ */
+
+#define HIST_OPT_BUCKET_SIZE OPT_CALLBACK('b', "bucket-size", \
+ ¶ms->common.hist.bucket_size, "N", \
+ "set the histogram bucket size (default 1)", \
+ opt_bucket_size_cb)
+
+#define HIST_OPT_ENTRIES OPT_CALLBACK('E', "entries", ¶ms->common.hist.entries, "N", \
+ "set the number of entries of the histogram (default 256)", \
+ opt_entries_cb)
+
+#define HIST_OPT_NO_IRQ OPT_BOOLEAN_FLAG(0, "no-irq", ¶ms->common.hist.no_irq, \
+ "ignore IRQ latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_THREAD OPT_BOOLEAN_FLAG(0, "no-thread", ¶ms->common.hist.no_thread, \
+ "ignore thread latencies", PARSE_OPT_NOAUTONEG)
+
+#define HIST_OPT_NO_HEADER OPT_BOOLEAN(0, "no-header", ¶ms->common.hist.no_header, \
+ "do not print header")
+
+#define HIST_OPT_NO_SUMMARY OPT_BOOLEAN(0, "no-summary", ¶ms->common.hist.no_summary, \
+ "do not print summary")
+
+#define HIST_OPT_NO_INDEX OPT_BOOLEAN(0, "no-index", ¶ms->common.hist.no_index, \
+ "do not print index")
+
+#define HIST_OPT_WITH_ZEROS OPT_BOOLEAN(0, "with-zeros", ¶ms->common.hist.with_zeros, \
+ "print zero only entries")
+
+/* Histogram-specific callbacks */
+
+static int opt_bucket_size_cb(const struct option *opt, const char *arg, int unset)
+{
+ int *bucket_size = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *bucket_size = get_llong_from_str((char *)arg);
+ if (*bucket_size == 0 || *bucket_size >= 1000000)
+ fatal("Bucket size needs to be > 0 and <= 1000000");
+
+ return 0;
+}
+
+static int opt_entries_cb(const struct option *opt, const char *arg, int unset)
+{
+ int *entries = opt->value;
+
+ if (unset || !arg)
+ return -1;
+
+ *entries = get_llong_from_str((char *)arg);
+ if (*entries < 10 || *entries > 9999999)
+ fatal("Entries must be > 10 and < 10000000");
+
+ return 0;
+}
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index effad523e8cf..d0a8a6edbf0c 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -5,7 +5,6 @@
#include <signal.h>
#include <stdlib.h>
#include <string.h>
-#include <getopt.h>
#include <sys/sysinfo.h>
#include "common.h"
@@ -57,114 +56,6 @@ static void unset_signals(struct common_params *params)
}
}
-/*
- * getopt_auto - auto-generates optstring from long_options
- */
-int getopt_auto(int argc, char **argv, const struct option *long_opts)
-{
- char opts[256];
- int n = 0;
-
- for (int i = 0; long_opts[i].name; i++) {
- if (long_opts[i].val < 32 || long_opts[i].val > 127)
- continue;
-
- if (n + 4 >= sizeof(opts))
- fatal("optstring buffer overflow");
-
- opts[n++] = long_opts[i].val;
-
- if (long_opts[i].has_arg == required_argument)
- opts[n++] = ':';
- else if (long_opts[i].has_arg == optional_argument) {
- opts[n++] = ':';
- opts[n++] = ':';
- }
- }
-
- opts[n] = '\0';
-
- return getopt_long(argc, argv, opts, long_opts, NULL);
-}
-
-/*
- * common_parse_options - parse common command line options
- *
- * @argc: argument count
- * @argv: argument vector
- * @common: common parameters structure
- *
- * Parse command line options that are common to all rtla tools.
- *
- * Returns: non zero if a common option was parsed, or 0
- * if the option should be handled by tool-specific parsing.
- */
-int common_parse_options(int argc, char **argv, struct common_params *common)
-{
- struct trace_events *tevent;
- int saved_state = optind;
- int c;
-
- static struct option long_options[] = {
- {"cpus", required_argument, 0, 'c'},
- {"cgroup", optional_argument, 0, 'C'},
- {"debug", no_argument, 0, 'D'},
- {"duration", required_argument, 0, 'd'},
- {"event", required_argument, 0, 'e'},
- {"house-keeping", required_argument, 0, 'H'},
- {"priority", required_argument, 0, 'P'},
- {0, 0, 0, 0}
- };
-
- opterr = 0;
- c = getopt_auto(argc, argv, long_options);
- opterr = 1;
-
- switch (c) {
- case 'c':
- if (parse_cpu_set(optarg, &common->monitored_cpus))
- fatal("Invalid -c cpu list");
- common->cpus = optarg;
- break;
- case 'C':
- common->cgroup = 1;
- common->cgroup_name = parse_optional_arg(argc, argv);
- break;
- case 'D':
- config_debug = 1;
- break;
- case 'd':
- common->duration = parse_seconds_duration(optarg);
- if (!common->duration)
- fatal("Invalid -d duration");
- break;
- case 'e':
- tevent = trace_event_alloc(optarg);
- if (!tevent)
- fatal("Error alloc trace event");
-
- if (common->events)
- tevent->next = common->events;
- common->events = tevent;
- break;
- case 'H':
- common->hk_cpus = 1;
- if (parse_cpu_set(optarg, &common->hk_cpu_set))
- fatal("Error parsing house keeping CPUs");
- break;
- case 'P':
- if (parse_prio(optarg, &common->sched_param) == -1)
- fatal("Invalid -P priority");
- common->set_sched = 1;
- break;
- default:
- optind = saved_state;
- return 0;
- }
-
- return c;
-}
-
/*
* common_apply_config - apply common configs to the initialized tool
*/
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index eba40b6d9504..0dfca83bd726 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -1,7 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 */
#pragma once
-#include <getopt.h>
#include "actions.h"
#include "timerlat_u.h"
#include "trace.h"
@@ -57,12 +56,12 @@ struct osnoise_context {
extern volatile int stop_tracing;
struct hist_params {
- char no_irq;
- char no_thread;
- char no_header;
- char no_summary;
- char no_index;
- char with_zeros;
+ bool no_irq;
+ bool no_thread;
+ bool no_header;
+ bool no_summary;
+ bool no_index;
+ bool with_zeros;
int bucket_size;
int entries;
};
@@ -95,12 +94,12 @@ struct common_params {
/* Other parameters */
struct hist_params hist;
int output_divisor;
- int pretty_output;
- int quiet;
- int user_workload;
- int kernel_workload;
- int user_data;
- int aa_only;
+ bool pretty_output;
+ bool quiet;
+ bool user_workload;
+ bool kernel_workload;
+ bool user_data;
+ bool aa_only;
struct actions threshold_actions;
struct actions end_actions;
@@ -176,8 +175,6 @@ int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
int osnoise_set_stop_total_us(struct osnoise_context *context,
long long stop_total_us);
-int getopt_auto(int argc, char **argv, const struct option *long_opts);
-int common_parse_options(int argc, char **argv, struct common_params *common);
int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
int top_main_loop(struct osnoise_tool *tool);
int hist_main_loop(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 2db3db155c44..e1e32898af2d 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -15,6 +15,8 @@
#include <stdio.h>
#include <sched.h>
+#include <linux/compiler.h>
+
#include "osnoise.h"
#define DEFAULT_SAMPLE_PERIOD 1000000 /* 1s */
@@ -1171,7 +1173,7 @@ int osnoise_enable(struct osnoise_tool *tool)
return 0;
}
-static void osnoise_usage(int err)
+__noreturn static void osnoise_usage(int err)
{
int i;
@@ -1209,7 +1211,7 @@ int osnoise_main(int argc, char *argv[])
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
- osnoise_usage(0);
+ osnoise_usage(129);
} else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
run_tool(&osnoise_top_ops, argc, argv);
@@ -1223,8 +1225,7 @@ int osnoise_main(int argc, char *argv[])
}
usage:
- osnoise_usage(1);
- exit(1);
+ osnoise_usage(129);
}
int hwnoise_main(int argc, char *argv[])
diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c
index 8ad816b80265..dfa91d0681f8 100644
--- a/tools/tracing/rtla/src/osnoise_hist.c
+++ b/tools/tracing/rtla/src/osnoise_hist.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -13,6 +12,7 @@
#include <time.h>
#include "osnoise.h"
+#include "cli.h"
struct osnoise_hist_cpu {
int *samples;
@@ -400,225 +400,6 @@ osnoise_print_stats(struct osnoise_tool *tool)
osnoise_report_missed_events(tool);
}
-/*
- * osnoise_hist_usage - prints osnoise hist usage message
- */
-static void osnoise_hist_usage(void)
-{
- static const char * const msg_start[] = {
- "[-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
- " [-c cpu-list] [-H cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [-C [cgroup_name]] [--warm-up]",
- NULL,
- };
-
- static const char * const msg_opts[] = {
- " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
- " -p/--period us: osnoise period in us",
- " -r/--runtime us: osnoise runtime in us",
- " -s/--stop us: stop trace if a single sample is higher than the argument in us",
- " -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
- " -T/--threshold us: the minimum delta to be considered a noise",
- " -c/--cpus cpu-list: list of cpus to run osnoise threads",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -b/--bucket-size N: set the histogram bucket size (default 1)",
- " -E/--entries N: set the number of entries of the histogram (default 256)",
- " --no-header: do not print header",
- " --no-summary: do not print summary",
- " --no-index: do not print index",
- " --with-zeros: print zero only entries",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period: set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " --warm-up: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
- " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- common_usage("osnoise", "hist", "a per-cpu histogram of the OS noise",
- msg_start, msg_opts);
-}
-
-/*
- * osnoise_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*osnoise_hist_parse_args(int argc, char *argv[])
-{
- struct osnoise_params *params;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc_fatal(1, sizeof(*params));
-
- actions_init(¶ms->common.threshold_actions);
- actions_init(¶ms->common.end_actions);
-
- /* display data in microseconds */
- params->common.output_divisor = 1000;
- params->common.hist.bucket_size = 1;
- params->common.hist.entries = 256;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"bucket-size", required_argument, 0, 'b'},
- {"entries", required_argument, 0, 'E'},
- {"help", no_argument, 0, 'h'},
- {"period", required_argument, 0, 'p'},
- {"runtime", required_argument, 0, 'r'},
- {"stop", required_argument, 0, 's'},
- {"stop-total", required_argument, 0, 'S'},
- {"trace", optional_argument, 0, 't'},
- {"threshold", required_argument, 0, 'T'},
- {"no-header", no_argument, 0, '0'},
- {"no-summary", no_argument, 0, '1'},
- {"no-index", no_argument, 0, '2'},
- {"with-zeros", no_argument, 0, '3'},
- {"trigger", required_argument, 0, '4'},
- {"filter", required_argument, 0, '5'},
- {"warm-up", required_argument, 0, '6'},
- {"trace-buffer-size", required_argument, 0, '7'},
- {"on-threshold", required_argument, 0, '8'},
- {"on-end", required_argument, 0, '9'},
- {0, 0, 0, 0}
- };
-
- if (common_parse_options(argc, argv, ¶ms->common))
- continue;
-
- c = getopt_auto(argc, argv, long_options);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- /* set sample stop to auto_thresh */
- params->common.stop_us = get_llong_from_str(optarg);
-
- /* set sample threshold to 1 */
- params->threshold = 1;
-
- /* set trace */
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
-
- break;
- case 'b':
- params->common.hist.bucket_size = get_llong_from_str(optarg);
- if (params->common.hist.bucket_size == 0 ||
- params->common.hist.bucket_size >= 1000000)
- fatal("Bucket size needs to be > 0 and <= 1000000");
- break;
- case 'E':
- params->common.hist.entries = get_llong_from_str(optarg);
- if (params->common.hist.entries < 10 ||
- params->common.hist.entries > 9999999)
- fatal("Entries must be > 10 and < 9999999");
- break;
- case 'h':
- case '?':
- osnoise_hist_usage();
- break;
- case 'p':
- params->period = get_llong_from_str(optarg);
- if (params->period > 10000000)
- fatal("Period longer than 10 s");
- break;
- case 'r':
- params->runtime = get_llong_from_str(optarg);
- if (params->runtime < 100)
- fatal("Runtime shorter than 100 us");
- break;
- case 's':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'S':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 'T':
- params->threshold = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
- break;
- case '0': /* no header */
- params->common.hist.no_header = 1;
- break;
- case '1': /* no summary */
- params->common.hist.no_summary = 1;
- break;
- case '2': /* no index */
- params->common.hist.no_index = 1;
- break;
- case '3': /* with zeros */
- params->common.hist.with_zeros = 1;
- break;
- case '4': /* trigger */
- if (params->common.events)
- trace_event_add_trigger(params->common.events, optarg);
- else
- fatal("--trigger requires a previous -e");
- break;
- case '5': /* filter */
- if (params->common.events)
- trace_event_add_filter(params->common.events, optarg);
- else
- fatal("--filter requires a previous -e");
- break;
- case '6':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '7':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '8':
- retval = actions_parse(¶ms->common.threshold_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '9':
- retval = actions_parse(¶ms->common.end_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(¶ms->common.threshold_actions, trace_output);
-
- if (geteuid())
- fatal("rtla needs root permission");
-
- if (params->common.hist.no_index && !params->common.hist.with_zeros)
- fatal("no-index set and with-zeros not set - it does not make sense");
-
- return ¶ms->common;
-}
-
/*
* osnoise_hist_apply_config - apply the hist configs to the initialized tool
*/
diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c
index 244bdce022ad..512a6299cb01 100644
--- a/tools/tracing/rtla/src/osnoise_top.c
+++ b/tools/tracing/rtla/src/osnoise_top.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -13,6 +12,7 @@
#include <time.h>
#include "osnoise.h"
+#include "cli.h"
struct osnoise_top_cpu {
unsigned long long sum_runtime;
@@ -245,204 +245,6 @@ osnoise_print_stats(struct osnoise_tool *top)
osnoise_report_missed_events(top);
}
-/*
- * osnoise_top_usage - prints osnoise top usage message
- */
-static void osnoise_top_usage(struct osnoise_params *params)
-{
- const char *tool, *mode, *desc;
-
- static const char * const msg_start[] = {
- "[-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\",
- " [-T us] [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] \\",
- " [-c cpu-list] [-H cpu-list] [-P priority] [-C [cgroup_name]] [--warm-up s]",
- NULL,
- };
-
- static const char * const msg_opts[] = {
- " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit",
- " -p/--period us: osnoise period in us",
- " -r/--runtime us: osnoise runtime in us",
- " -s/--stop us: stop trace if a single sample is higher than the argument in us",
- " -S/--stop-total us: stop trace if the total sample is higher than the argument in us",
- " -T/--threshold us: the minimum delta to be considered a noise",
- " -c/--cpus cpu-list: list of cpus to run osnoise threads",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|osnoise_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -q/--quiet print only a summary at the end",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --on-threshold <action>: define action to be executed at stop-total threshold, multiple are allowed",
- " --on-end: define action to be executed at measurement end, multiple are allowed",
- NULL,
- };
-
- if (params->mode == MODE_OSNOISE) {
- tool = "osnoise";
- mode = "top";
- desc = "a per-cpu summary of the OS noise";
- } else {
- tool = "hwnoise";
- mode = "";
- desc = "a summary of hardware-related noise";
- }
-
- common_usage(tool, mode, desc, msg_start, msg_opts);
-}
-
-/*
- * osnoise_top_parse_args - allocs, parse and fill the cmd line parameters
- */
-struct common_params *osnoise_top_parse_args(int argc, char **argv)
-{
- struct osnoise_params *params;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc_fatal(1, sizeof(*params));
-
- actions_init(¶ms->common.threshold_actions);
- actions_init(¶ms->common.end_actions);
-
- if (strcmp(argv[0], "hwnoise") == 0) {
- params->mode = MODE_HWNOISE;
- /*
- * Reduce CPU usage for 75% to avoid killing the system.
- */
- params->runtime = 750000;
- params->period = 1000000;
- }
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"help", no_argument, 0, 'h'},
- {"period", required_argument, 0, 'p'},
- {"quiet", no_argument, 0, 'q'},
- {"runtime", required_argument, 0, 'r'},
- {"stop", required_argument, 0, 's'},
- {"stop-total", required_argument, 0, 'S'},
- {"threshold", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"trigger", required_argument, 0, '0'},
- {"filter", required_argument, 0, '1'},
- {"warm-up", required_argument, 0, '2'},
- {"trace-buffer-size", required_argument, 0, '3'},
- {"on-threshold", required_argument, 0, '4'},
- {"on-end", required_argument, 0, '5'},
- {0, 0, 0, 0}
- };
-
- if (common_parse_options(argc, argv, ¶ms->common))
- continue;
-
- c = getopt_auto(argc, argv, long_options);
-
- /* Detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- /* set sample stop to auto_thresh */
- params->common.stop_us = get_llong_from_str(optarg);
-
- /* set sample threshold to 1 */
- params->threshold = 1;
-
- /* set trace */
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
-
- break;
- case 'h':
- case '?':
- osnoise_top_usage(params);
- break;
- case 'p':
- params->period = get_llong_from_str(optarg);
- if (params->period > 10000000)
- fatal("Period longer than 10 s");
- break;
- case 'q':
- params->common.quiet = 1;
- break;
- case 'r':
- params->runtime = get_llong_from_str(optarg);
- if (params->runtime < 100)
- fatal("Runtime shorter than 100 us");
- break;
- case 's':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'S':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "osnoise_trace.txt";
- break;
- case 'T':
- params->threshold = get_llong_from_str(optarg);
- break;
- case '0': /* trigger */
- if (params->common.events)
- trace_event_add_trigger(params->common.events, optarg);
- else
- fatal("--trigger requires a previous -e");
- break;
- case '1': /* filter */
- if (params->common.events)
- trace_event_add_filter(params->common.events, optarg);
- else
- fatal("--filter requires a previous -e");
- break;
- case '2':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '3':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '4':
- retval = actions_parse(¶ms->common.threshold_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '5':
- retval = actions_parse(¶ms->common.end_actions, optarg,
- "osnoise_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(¶ms->common.threshold_actions, trace_output);
-
- if (geteuid())
- fatal("osnoise needs root permission");
-
- return ¶ms->common;
-}
-
/*
* osnoise_top_apply_config - apply the top configs to the initialized tool
*/
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
deleted file mode 100644
index f4342ca684c3..000000000000
--- a/tools/tracing/rtla/src/rtla.c
+++ /dev/null
@@ -1,92 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
- */
-
-#include <getopt.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "osnoise.h"
-#include "timerlat.h"
-
-/*
- * rtla_usage - print rtla usage
- */
-static void rtla_usage(int err)
-{
- int i;
-
- static const char *msg[] = {
- "",
- "rtla version " VERSION,
- "",
- " usage: rtla COMMAND ...",
- "",
- " commands:",
- " osnoise - gives information about the operating system noise (osnoise)",
- " hwnoise - gives information about hardware-related noise",
- " timerlat - measures the timer irq and thread latency",
- "",
- NULL,
- };
-
- for (i = 0; msg[i]; i++)
- fprintf(stderr, "%s\n", msg[i]);
- exit(err);
-}
-
-/*
- * run_tool_command - try to run a rtla tool command
- *
- * It returns 0 if it fails. The tool's main will generally not
- * return as they should call exit().
- */
-int run_tool_command(int argc, char **argv, int start_position)
-{
- if (strcmp(argv[start_position], "osnoise") == 0) {
- osnoise_main(argc-start_position, &argv[start_position]);
- goto ran;
- } else if (strcmp(argv[start_position], "hwnoise") == 0) {
- hwnoise_main(argc-start_position, &argv[start_position]);
- goto ran;
- } else if (strcmp(argv[start_position], "timerlat") == 0) {
- timerlat_main(argc-start_position, &argv[start_position]);
- goto ran;
- }
-
- return 0;
-ran:
- return 1;
-}
-
-/* Set main as weak to allow overriding it for building unit test binary */
-#pragma weak main
-
-int main(int argc, char *argv[])
-{
- int retval;
-
- /* is it an alias? */
- retval = run_tool_command(argc, argv, 0);
- if (retval)
- exit(0);
-
- if (argc < 2)
- goto usage;
-
- if (strcmp(argv[1], "-h") == 0) {
- rtla_usage(0);
- } else if (strcmp(argv[1], "--help") == 0) {
- rtla_usage(0);
- }
-
- retval = run_tool_command(argc, argv, 1);
- if (retval)
- exit(0);
-
-usage:
- rtla_usage(1);
- exit(1);
-}
diff --git a/tools/tracing/rtla/src/timerlat.c b/tools/tracing/rtla/src/timerlat.c
index 637f68d684f5..f990c8365776 100644
--- a/tools/tracing/rtla/src/timerlat.c
+++ b/tools/tracing/rtla/src/timerlat.c
@@ -13,6 +13,8 @@
#include <stdio.h>
#include <sched.h>
+#include <linux/compiler.h>
+
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
@@ -231,7 +233,7 @@ void timerlat_free(struct osnoise_tool *tool)
free_cpu_idle_disable_states();
}
-static void timerlat_usage(int err)
+__noreturn static void timerlat_usage(int err)
{
int i;
@@ -269,7 +271,7 @@ int timerlat_main(int argc, char *argv[])
}
if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
- timerlat_usage(0);
+ timerlat_usage(129);
} else if (str_has_prefix(argv[1], "-")) {
/* the user skipped the tool, call the default one */
run_tool(&timerlat_top_ops, argc, argv);
@@ -283,6 +285,5 @@ int timerlat_main(int argc, char *argv[])
}
usage:
- timerlat_usage(1);
- exit(1);
+ timerlat_usage(129);
}
diff --git a/tools/tracing/rtla/src/timerlat.h b/tools/tracing/rtla/src/timerlat.h
index 364203a29abd..37a808f1611e 100644
--- a/tools/tracing/rtla/src/timerlat.h
+++ b/tools/tracing/rtla/src/timerlat.h
@@ -23,8 +23,8 @@ struct timerlat_params {
long long timerlat_period_us;
long long print_stack;
int dma_latency;
- int no_aa;
- int dump_tasks;
+ bool no_aa;
+ bool dump_tasks;
int deepest_idle_state;
enum timerlat_tracing_mode mode;
const char *bpf_action_program;
diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c
index 3a15a85b7d72..df7b1398a966 100644
--- a/tools/tracing/rtla/src/timerlat_hist.c
+++ b/tools/tracing/rtla/src/timerlat_hist.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -17,6 +16,7 @@
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
+#include "cli.h"
#include "common.h"
struct timerlat_hist_cpu {
@@ -685,321 +685,6 @@ timerlat_print_stats(struct osnoise_tool *tool)
osnoise_report_missed_events(tool);
}
-/*
- * timerlat_hist_usage - prints timerlat top usage message
- */
-static void timerlat_hist_usage(void)
-{
- static const char * const msg_start[] = {
- "[-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\",
- " [-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
- " [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\",
- " [--no-index] [--with-zeros] [--dma-latency us] [-C [cgroup_name]] [--no-aa] [--dump-tasks] [-u|-k]",
- " [--warm-up s] [--deepest-idle-state n]",
- NULL,
- };
-
- static const char * const msg_opts[] = {
- " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
- " -p/--period us: timerlat period in us",
- " -i/--irq us: stop trace if the irq latency is higher than the argument in us",
- " -T/--thread us: stop trace if the thread latency is higher than the argument in us",
- " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
- " -c/--cpus cpus: run the tracer only on the given cpus",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[m|h|d]: duration of the session in seconds",
- " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
- " -D/--debug: print debug info",
- " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <filter>: enable a trace event filter to the previous -e event",
- " --trigger <trigger>: enable a trace event trigger to the previous -e event",
- " -n/--nano: display data in nanoseconds",
- " --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
- " -b/--bucket-size N: set the histogram bucket size (default 1)",
- " -E/--entries N: set the number of entries of the histogram (default 256)",
- " --no-irq: ignore IRQ latencies",
- " --no-thread: ignore thread latencies",
- " --no-header: do not print header",
- " --no-summary: do not print summary",
- " --no-index: do not print index",
- " --with-zeros: print zero only entries",
- " --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
- " -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
- " -U/--user-load: enable timerlat for user-defined user-space workload",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
- " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
- " --on-end <action>: define action to be executed at measurement end, multiple are allowed",
- " --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
- " --stack-format <format>: set the stack format (truncate, skip, full)",
- NULL,
- };
-
- common_usage("timerlat", "hist", "a per-cpu histogram of the timer latency",
- msg_start, msg_opts);
-}
-
-/*
- * timerlat_hist_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_hist_parse_args(int argc, char *argv[])
-{
- struct timerlat_params *params;
- int auto_thresh;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc_fatal(1, sizeof(*params));
-
- actions_init(¶ms->common.threshold_actions);
- actions_init(¶ms->common.end_actions);
-
- /* disabled by default */
- params->dma_latency = -1;
-
- /* disabled by default */
- params->deepest_idle_state = -2;
-
- /* display data in microseconds */
- params->common.output_divisor = 1000;
- params->common.hist.bucket_size = 1;
- params->common.hist.entries = 256;
-
- /* default to BPF mode */
- params->mode = TRACING_MODE_BPF;
-
- /* default to truncate stack format */
- params->stack_format = STACK_FORMAT_TRUNCATE;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"bucket-size", required_argument, 0, 'b'},
- {"entries", required_argument, 0, 'E'},
- {"help", no_argument, 0, 'h'},
- {"irq", required_argument, 0, 'i'},
- {"nano", no_argument, 0, 'n'},
- {"period", required_argument, 0, 'p'},
- {"stack", required_argument, 0, 's'},
- {"thread", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"user-threads", no_argument, 0, 'u'},
- {"kernel-threads", no_argument, 0, 'k'},
- {"user-load", no_argument, 0, 'U'},
- {"no-irq", no_argument, 0, '0'},
- {"no-thread", no_argument, 0, '1'},
- {"no-header", no_argument, 0, '2'},
- {"no-summary", no_argument, 0, '3'},
- {"no-index", no_argument, 0, '4'},
- {"with-zeros", no_argument, 0, '5'},
- {"trigger", required_argument, 0, '6'},
- {"filter", required_argument, 0, '7'},
- {"dma-latency", required_argument, 0, '8'},
- {"no-aa", no_argument, 0, '9'},
- {"dump-tasks", no_argument, 0, '\1'},
- {"warm-up", required_argument, 0, '\2'},
- {"trace-buffer-size", required_argument, 0, '\3'},
- {"deepest-idle-state", required_argument, 0, '\4'},
- {"on-threshold", required_argument, 0, '\5'},
- {"on-end", required_argument, 0, '\6'},
- {"bpf-action", required_argument, 0, '\7'},
- {"stack-format", required_argument, 0, '\10'},
- {0, 0, 0, 0}
- };
-
- if (common_parse_options(argc, argv, ¶ms->common))
- continue;
-
- c = getopt_auto(argc, argv, long_options);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set trace */
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
-
- break;
- case 'b':
- params->common.hist.bucket_size = get_llong_from_str(optarg);
- if (params->common.hist.bucket_size == 0 ||
- params->common.hist.bucket_size >= 1000000)
- fatal("Bucket size needs to be > 0 and <= 1000000");
- break;
- case 'E':
- params->common.hist.entries = get_llong_from_str(optarg);
- if (params->common.hist.entries < 10 ||
- params->common.hist.entries > 9999999)
- fatal("Entries must be > 10 and < 9999999");
- break;
- case 'h':
- case '?':
- timerlat_hist_usage();
- break;
- case 'i':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'k':
- params->common.kernel_workload = 1;
- break;
- case 'n':
- params->common.output_divisor = 1;
- break;
- case 'p':
- params->timerlat_period_us = get_llong_from_str(optarg);
- if (params->timerlat_period_us > 1000000)
- fatal("Period longer than 1 s");
- break;
- case 's':
- params->print_stack = get_llong_from_str(optarg);
- break;
- case 'T':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
- break;
- case 'u':
- params->common.user_workload = 1;
- /* fallback: -u implies in -U */
- case 'U':
- params->common.user_data = 1;
- break;
- case '0': /* no irq */
- params->common.hist.no_irq = 1;
- break;
- case '1': /* no thread */
- params->common.hist.no_thread = 1;
- break;
- case '2': /* no header */
- params->common.hist.no_header = 1;
- break;
- case '3': /* no summary */
- params->common.hist.no_summary = 1;
- break;
- case '4': /* no index */
- params->common.hist.no_index = 1;
- break;
- case '5': /* with zeros */
- params->common.hist.with_zeros = 1;
- break;
- case '6': /* trigger */
- if (params->common.events)
- trace_event_add_trigger(params->common.events, optarg);
- else
- fatal("--trigger requires a previous -e");
- break;
- case '7': /* filter */
- if (params->common.events)
- trace_event_add_filter(params->common.events, optarg);
- else
- fatal("--filter requires a previous -e");
- break;
- case '8':
- params->dma_latency = get_llong_from_str(optarg);
- if (params->dma_latency < 0 || params->dma_latency > 10000)
- fatal("--dma-latency needs to be >= 0 and < 10000");
- break;
- case '9':
- params->no_aa = 1;
- break;
- case '\1':
- params->dump_tasks = 1;
- break;
- case '\2':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '\3':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '\4':
- params->deepest_idle_state = get_llong_from_str(optarg);
- break;
- case '\5':
- retval = actions_parse(¶ms->common.threshold_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\6':
- retval = actions_parse(¶ms->common.end_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\7':
- params->bpf_action_program = optarg;
- break;
- case '\10':
- params->stack_format = parse_stack_format(optarg);
- if (params->stack_format == -1)
- fatal("Invalid --stack-format option");
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(¶ms->common.threshold_actions, trace_output);
-
- if (geteuid())
- fatal("rtla needs root permission");
-
- if (params->common.hist.no_irq && params->common.hist.no_thread)
- fatal("no-irq and no-thread set, there is nothing to do here");
-
- if (params->common.hist.no_index && !params->common.hist.with_zeros)
- fatal("no-index set with with-zeros is not set - it does not make sense");
-
- /*
- * Auto analysis only happens if stop tracing, thus:
- */
- if (!params->common.stop_us && !params->common.stop_total_us)
- params->no_aa = 1;
-
- if (params->common.kernel_workload && params->common.user_workload)
- fatal("--kernel-threads and --user-threads are mutually exclusive!");
-
- /*
- * If auto-analysis or trace output is enabled, switch from BPF mode to
- * mixed mode
- */
- if (params->mode == TRACING_MODE_BPF &&
- (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
- params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
- !params->no_aa))
- params->mode = TRACING_MODE_MIXED;
-
- return ¶ms->common;
-}
-
/*
* timerlat_hist_apply_config - apply the hist configs to the initialized tool
*/
diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c
index e3a374005690..18e1071a2e24 100644
--- a/tools/tracing/rtla/src/timerlat_top.c
+++ b/tools/tracing/rtla/src/timerlat_top.c
@@ -4,7 +4,6 @@
*/
#define _GNU_SOURCE
-#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -17,6 +16,7 @@
#include "timerlat.h"
#include "timerlat_aa.h"
#include "timerlat_bpf.h"
+#include "cli.h"
#include "common.h"
struct timerlat_top_cpu {
@@ -459,290 +459,6 @@ timerlat_print_stats(struct osnoise_tool *top)
osnoise_report_missed_events(top);
}
-/*
- * timerlat_top_usage - prints timerlat top usage message
- */
-static void timerlat_top_usage(void)
-{
- static const char *const msg_start[] = {
- "[-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\",
- " [[-t [file]] [-e sys[:event]] [--filter <filter>] [--trigger <trigger>] [-c cpu-list] [-H cpu-list]\\",
- " [-P priority] [--dma-latency us] [--aa-only us] [-C [cgroup_name]] [--dump-tasks] [-u|-k] [--warm-up s]\\",
- " [--deepest-idle-state n]",
- NULL,
- };
-
- static const char *const msg_opts[] = {
- " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit",
- " --aa-only us: stop if <us> latency is hit, only printing the auto analysis (reduces CPU usage)",
- " -p/--period us: timerlat period in us",
- " -i/--irq us: stop trace if the irq latency is higher than the argument in us",
- " -T/--thread us: stop trace if the thread latency is higher than the argument in us",
- " -s/--stack us: save the stack trace at the IRQ if a thread latency is higher than the argument in us",
- " -c/--cpus cpus: run the tracer only on the given cpus",
- " -H/--house-keeping cpus: run rtla control threads only on the given cpus",
- " -C/--cgroup [cgroup_name]: set cgroup, if no cgroup_name is passed, the rtla's cgroup will be inherited",
- " -d/--duration time[s|m|h|d]: duration of the session",
- " -D/--debug: print debug info",
- " --dump-tasks: prints the task running on all CPUs if stop conditions are met (depends on !--no-aa)",
- " -t/--trace [file]: save the stopped trace to [file|timerlat_trace.txt]",
- " -e/--event <sys:event>: enable the <sys:event> in the trace instance, multiple -e are allowed",
- " --filter <command>: enable a trace event filter to the previous -e event",
- " --trigger <command>: enable a trace event trigger to the previous -e event",
- " -n/--nano: display data in nanoseconds",
- " --no-aa: disable auto-analysis, reducing rtla timerlat cpu usage",
- " -q/--quiet print only a summary at the end",
- " --dma-latency us: set /dev/cpu_dma_latency latency <us> to reduce exit from idle latency",
- " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters",
- " o:prio - use SCHED_OTHER with prio",
- " r:prio - use SCHED_RR with prio",
- " f:prio - use SCHED_FIFO with prio",
- " d:runtime[us|ms|s]:period[us|ms|s] - use SCHED_DEADLINE with runtime and period",
- " in nanoseconds",
- " -u/--user-threads: use rtla user-space threads instead of kernel-space timerlat threads",
- " -k/--kernel-threads: use timerlat kernel-space threads instead of rtla user-space threads",
- " -U/--user-load: enable timerlat for user-defined user-space workload",
- " --warm-up s: let the workload run for s seconds before collecting data",
- " --trace-buffer-size kB: set the per-cpu trace buffer size in kB",
- " --deepest-idle-state n: only go down to idle state n on cpus used by timerlat to reduce exit from idle latency",
- " --on-threshold <action>: define action to be executed at latency threshold, multiple are allowed",
- " --on-end: define action to be executed at measurement end, multiple are allowed",
- " --bpf-action <program>: load and execute BPF program when latency threshold is exceeded",
- " --stack-format <format>: set the stack format (truncate, skip, full)",
- NULL,
- };
-
- common_usage("timerlat", "top", "a per-cpu summary of the timer latency",
- msg_start, msg_opts);
-}
-
-/*
- * timerlat_top_parse_args - allocs, parse and fill the cmd line parameters
- */
-static struct common_params
-*timerlat_top_parse_args(int argc, char **argv)
-{
- struct timerlat_params *params;
- long long auto_thresh;
- int retval;
- int c;
- char *trace_output = NULL;
-
- params = calloc_fatal(1, sizeof(*params));
-
- actions_init(¶ms->common.threshold_actions);
- actions_init(¶ms->common.end_actions);
-
- /* disabled by default */
- params->dma_latency = -1;
-
- /* disabled by default */
- params->deepest_idle_state = -2;
-
- /* display data in microseconds */
- params->common.output_divisor = 1000;
-
- /* default to BPF mode */
- params->mode = TRACING_MODE_BPF;
-
- /* default to truncate stack format */
- params->stack_format = STACK_FORMAT_TRUNCATE;
-
- while (1) {
- static struct option long_options[] = {
- {"auto", required_argument, 0, 'a'},
- {"help", no_argument, 0, 'h'},
- {"irq", required_argument, 0, 'i'},
- {"nano", no_argument, 0, 'n'},
- {"period", required_argument, 0, 'p'},
- {"quiet", no_argument, 0, 'q'},
- {"stack", required_argument, 0, 's'},
- {"thread", required_argument, 0, 'T'},
- {"trace", optional_argument, 0, 't'},
- {"user-threads", no_argument, 0, 'u'},
- {"kernel-threads", no_argument, 0, 'k'},
- {"user-load", no_argument, 0, 'U'},
- {"trigger", required_argument, 0, '0'},
- {"filter", required_argument, 0, '1'},
- {"dma-latency", required_argument, 0, '2'},
- {"no-aa", no_argument, 0, '3'},
- {"dump-tasks", no_argument, 0, '4'},
- {"aa-only", required_argument, 0, '5'},
- {"warm-up", required_argument, 0, '6'},
- {"trace-buffer-size", required_argument, 0, '7'},
- {"deepest-idle-state", required_argument, 0, '8'},
- {"on-threshold", required_argument, 0, '9'},
- {"on-end", required_argument, 0, '\1'},
- {"bpf-action", required_argument, 0, '\2'},
- {"stack-format", required_argument, 0, '\3'},
- {0, 0, 0, 0}
- };
-
- if (common_parse_options(argc, argv, ¶ms->common))
- continue;
-
- c = getopt_auto(argc, argv, long_options);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'a':
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set trace */
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
-
- break;
- case '5':
- /* it is here because it is similar to -a */
- auto_thresh = get_llong_from_str(optarg);
-
- /* set thread stop to auto_thresh */
- params->common.stop_total_us = auto_thresh;
- params->common.stop_us = auto_thresh;
-
- /* get stack trace */
- params->print_stack = auto_thresh;
-
- /* set aa_only to avoid parsing the trace */
- params->common.aa_only = 1;
- break;
- case 'h':
- case '?':
- timerlat_top_usage();
- break;
- case 'i':
- params->common.stop_us = get_llong_from_str(optarg);
- break;
- case 'k':
- params->common.kernel_workload = true;
- break;
- case 'n':
- params->common.output_divisor = 1;
- break;
- case 'p':
- params->timerlat_period_us = get_llong_from_str(optarg);
- if (params->timerlat_period_us > 1000000)
- fatal("Period longer than 1 s");
- break;
- case 'q':
- params->common.quiet = 1;
- break;
- case 's':
- params->print_stack = get_llong_from_str(optarg);
- break;
- case 'T':
- params->common.stop_total_us = get_llong_from_str(optarg);
- break;
- case 't':
- trace_output = parse_optional_arg(argc, argv);
- if (!trace_output)
- trace_output = "timerlat_trace.txt";
- break;
- case 'u':
- params->common.user_workload = true;
- /* fallback: -u implies -U */
- case 'U':
- params->common.user_data = true;
- break;
- case '0': /* trigger */
- if (params->common.events)
- trace_event_add_trigger(params->common.events, optarg);
- else
- fatal("--trigger requires a previous -e");
- break;
- case '1': /* filter */
- if (params->common.events)
- trace_event_add_filter(params->common.events, optarg);
- else
- fatal("--filter requires a previous -e");
- break;
- case '2': /* dma-latency */
- params->dma_latency = get_llong_from_str(optarg);
- if (params->dma_latency < 0 || params->dma_latency > 10000)
- fatal("--dma-latency needs to be >= 0 and < 10000");
- break;
- case '3': /* no-aa */
- params->no_aa = 1;
- break;
- case '4':
- params->dump_tasks = 1;
- break;
- case '6':
- params->common.warmup = get_llong_from_str(optarg);
- break;
- case '7':
- params->common.buffer_size = get_llong_from_str(optarg);
- break;
- case '8':
- params->deepest_idle_state = get_llong_from_str(optarg);
- break;
- case '9':
- retval = actions_parse(¶ms->common.threshold_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\1':
- retval = actions_parse(¶ms->common.end_actions, optarg,
- "timerlat_trace.txt");
- if (retval)
- fatal("Invalid action %s", optarg);
- break;
- case '\2':
- params->bpf_action_program = optarg;
- break;
- case '\3':
- params->stack_format = parse_stack_format(optarg);
- if (params->stack_format == -1)
- fatal("Invalid --stack-format option");
- break;
- default:
- fatal("Invalid option");
- }
- }
-
- if (trace_output)
- actions_add_trace_output(¶ms->common.threshold_actions, trace_output);
-
- if (geteuid())
- fatal("rtla needs root permission");
-
- /*
- * Auto analysis only happens if stop tracing, thus:
- */
- if (!params->common.stop_us && !params->common.stop_total_us)
- params->no_aa = 1;
-
- if (params->no_aa && params->common.aa_only)
- fatal("--no-aa and --aa-only are mutually exclusive!");
-
- if (params->common.kernel_workload && params->common.user_workload)
- fatal("--kernel-threads and --user-threads are mutually exclusive!");
-
- /*
- * If auto-analysis or trace output is enabled, switch from BPF mode to
- * mixed mode
- */
- if (params->mode == TRACING_MODE_BPF &&
- (params->common.threshold_actions.present[ACTION_TRACE_OUTPUT] ||
- params->common.end_actions.present[ACTION_TRACE_OUTPUT] ||
- !params->no_aa))
- params->mode = TRACING_MODE_MIXED;
-
- return ¶ms->common;
-}
-
/*
* timerlat_top_apply_config - apply the top configs to the initialized tool
*/
diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c
index 9cec5b3e02c8..cb187e7d48d1 100644
--- a/tools/tracing/rtla/src/utils.c
+++ b/tools/tracing/rtla/src/utils.c
@@ -22,7 +22,7 @@
#include "common.h"
#define MAX_MSG_LENGTH 1024
-int config_debug;
+bool config_debug;
/*
* err_msg - print an error message to the stderr
@@ -1011,32 +1011,6 @@ int auto_house_keeping(cpu_set_t *monitored_cpus)
return 1;
}
-/**
- * parse_optional_arg - Parse optional argument value
- *
- * Parse optional argument value, which can be in the form of:
- * -sarg, -s/--long=arg, -s/--long arg
- *
- * Returns arg value if found, NULL otherwise.
- */
-char *parse_optional_arg(int argc, char **argv)
-{
- if (optarg) {
- if (optarg[0] == '=') {
- /* skip the = */
- return &optarg[1];
- } else {
- return optarg;
- }
- /* parse argument of form -s [arg] and --long [arg]*/
- } else if (optind < argc && argv[optind][0] != '-') {
- /* consume optind */
- return argv[optind++];
- } else {
- return NULL;
- }
-}
-
/*
* strtoi - convert string to integer with error checking
*
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index 96fd72042717..2ba3333669bb 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -39,7 +39,7 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
return strncmp(str, prefix, strlen(prefix)) == 0;
}
-extern int config_debug;
+extern bool config_debug;
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
void fatal(const char *fmt, ...);
@@ -47,7 +47,6 @@ void fatal(const char *fmt, ...);
long parse_seconds_duration(char *val);
void get_duration(time_t start_time, char *output, int output_size);
-char *parse_optional_arg(int argc, char **argv);
long long get_llong_from_str(char *start);
static inline void
diff --git a/tools/tracing/rtla/tests/hwnoise.t b/tools/tracing/rtla/tests/hwnoise.t
index 23ce250a6852..cfe687ff5ee1 100644
--- a/tools/tracing/rtla/tests/hwnoise.t
+++ b/tools/tracing/rtla/tests/hwnoise.t
@@ -6,7 +6,7 @@ test_begin
set_timeout 2m
check "verify help page" \
- "hwnoise --help" 0 "summary of hardware-related noise"
+ "hwnoise --help" 129 "Usage: rtla hwnoise"
check "detect noise higher than one microsecond" \
"hwnoise -c 0 -T 1 -d 5s -q" 0
check "set the automatic trace mode" \
diff --git a/tools/tracing/rtla/tests/osnoise.t b/tools/tracing/rtla/tests/osnoise.t
index 06787471d0e8..c3dce1c9d197 100644
--- a/tools/tracing/rtla/tests/osnoise.t
+++ b/tools/tracing/rtla/tests/osnoise.t
@@ -6,9 +6,9 @@ test_begin
set_timeout 2m
check "verify help page" \
- "osnoise --help" 0 "osnoise version"
+ "osnoise --help" 129 "osnoise version"
check_top_hist "verify help page" \
- "osnoise TOOL --help" 0 "rtla osnoise"
+ "osnoise TOOL --help" 129 "rtla osnoise"
check_top_q_hist "verify the --stop/-s param" \
"osnoise TOOL -s 30 -T 1" 2 "osnoise hit stop tracing"
check_top_q_hist "verify the --trace param" \
@@ -32,7 +32,7 @@ check "hist with -b/--bucket-size" \
check "hist with -E/--entries" \
"osnoise hist -E 10 -d 1s"
check "hist with -E/--entries out of range" \
- "osnoise hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 9999999$"
+ "osnoise hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
check "hist with --no-header" \
"osnoise hist --no-header -d 1s" 0 "" "RTLA osnoise histogram"
check "hist with --with-zeros" \
diff --git a/tools/tracing/rtla/tests/timerlat.t b/tools/tracing/rtla/tests/timerlat.t
index 3ebfe316b39e..27fd3143b379 100644
--- a/tools/tracing/rtla/tests/timerlat.t
+++ b/tools/tracing/rtla/tests/timerlat.t
@@ -21,9 +21,9 @@ export RTLA_NO_BPF=$option
# Basic tests
check "verify help page" \
- "timerlat --help" 0 "timerlat version"
+ "timerlat --help" 129 "timerlat version"
check_top_hist "verify help page" \
- "timerlat TOOL --help" 0 "rtla timerlat"
+ "timerlat TOOL --help" 129 "rtla timerlat"
check_top_hist "verify -s/--stack" \
"timerlat TOOL -s 3 -T 10 -t" 2 "Blocking thread stack trace"
check_top_hist "test in nanoseconds" \
@@ -61,7 +61,7 @@ check "hist with -b/--bucket-size" \
check "hist with -E/--entries" \
"timerlat hist -E 10 -d 1s"
check "hist with -E/--entries out of range" \
- "timerlat hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 9999999$"
+ "timerlat hist -E 1 -d 1s" 1 "^Entries must be > 10 and < 10000000$"
check "hist with --no-header" \
"timerlat hist --no-header -d 1s" 0 "" "RTLA timerlat histogram"
check "hist with --with-zeros" \
--
2.54.0
^ permalink raw reply related
* [PATCH v2 3/6] tools subcmd: allow parsing distinct --opt and --no-opt
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
libsubcmd automatically generates for every option --opt an equivalent
negated option, --no-opt, to unset the option. Vice versa, for every
option declared as --no-opt, a shorthand --opt is declared for
convenience.
Add a flag, PARSE_OPT_NOAUTONEG, to disable this behavior. This new flag
behaves similarly to the already existing PARSE_OPT_NONEG, only it does
not reject the --no-opt variant, but leaves it undefined. That is useful
when there is a conflicting distinct --no-opt option in the syntax of
the tool.
PARSE_OPT_NOAUTONEG is enabled per-option, allowing to unset other
options that do not have this conflict.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/lib/subcmd/parse-options.c | 10 ++++++----
tools/lib/subcmd/parse-options.h | 3 +++
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 664b2053bb77..e83200e9f56a 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -427,7 +427,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
return 0;
}
if (!rest) {
- if (strstarts(options->long_name, "no-")) {
+ if (strstarts(options->long_name, "no-") &&
+ !(options->flags & PARSE_OPT_NOAUTONEG)) {
/*
* The long name itself starts with "no-", so
* accept the option without "no-" so that users
@@ -465,12 +466,12 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
continue;
}
/* negated and abbreviated very much? */
- if (strstarts("no-", arg)) {
+ if (strstarts("no-", arg) && !(options->flags & PARSE_OPT_NOAUTONEG)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
- if (strncmp(arg, "no-", 3))
+ if (strncmp(arg, "no-", 3) || (options->flags & PARSE_OPT_NOAUTONEG))
continue;
flags |= OPT_UNSET;
rest = skip_prefix(arg + 3, options->long_name);
@@ -1019,7 +1020,8 @@ int parse_options_usage(const char * const *usagestr,
if (strstarts(opts->long_name, optstr))
print_option_help(opts, 0);
if (strstarts("no-", optstr) &&
- strstarts(opts->long_name, optstr + 3))
+ strstarts(opts->long_name, optstr + 3) &&
+ !(opts->flags & PARSE_OPT_NOAUTONEG))
print_option_help(opts, 0);
}
diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h
index c573a0ca5ca6..38df5fd21963 100644
--- a/tools/lib/subcmd/parse-options.h
+++ b/tools/lib/subcmd/parse-options.h
@@ -47,6 +47,7 @@ enum parse_opt_option_flags {
PARSE_OPT_NOEMPTY = 128,
PARSE_OPT_NOBUILD = 256,
PARSE_OPT_CANSKIP = 512,
+ PARSE_OPT_NOAUTONEG = 1024,
};
struct option;
@@ -149,6 +150,8 @@ struct option {
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f) }
+#define OPT_CALLBACK_FLAG(s, l, v, a, h, f, fl) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .flags = (fl) }
#define OPT_CALLBACK_SET(s, l, v, os, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .set = check_vtype(os, bool *)}
#define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \
--
2.54.0
^ permalink raw reply related
* [PATCH v2 2/6] tools subcmd: support optarg as separate argument
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
In addition to "-ovalue" and "--opt=value" syntax, allow also "-o value"
and "--opt value" for options with optional argument when the newly
added PARSE_OPT_OPTARG_ALLOW_NEXT flag is set.
This behavior is turned off by default since it does not make sense for
tools using non-option command line arguments. Consider the ambiguity
of "cmd -d x", where "-d x" can mean either "-d with argument of x" or
"-d without argument, followed by non-option argument x". This is not an
issue in the case that the tool takes no non-option arguments.
To implement this, a new local variable, force_defval, is created in
get_value(), along with a comment explaining the logic.
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/lib/subcmd/parse-options.c | 53 +++++++++++++++++++++++++++-----
tools/lib/subcmd/parse-options.h | 1 +
2 files changed, 46 insertions(+), 8 deletions(-)
diff --git a/tools/lib/subcmd/parse-options.c b/tools/lib/subcmd/parse-options.c
index 555d617c1f50..664b2053bb77 100644
--- a/tools/lib/subcmd/parse-options.c
+++ b/tools/lib/subcmd/parse-options.c
@@ -72,6 +72,7 @@ static int get_value(struct parse_opt_ctx_t *p,
const char *s, *arg = NULL;
const int unset = flags & OPT_UNSET;
int err;
+ bool force_defval = false;
if (unset && p->opt)
return opterror(opt, "takes no value", flags);
@@ -123,6 +124,42 @@ static int get_value(struct parse_opt_ctx_t *p,
}
}
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (!(p->flags & PARSE_OPT_OPTARG_ALLOW_NEXT)) {
+ /*
+ * If the option has an optional argument, and the argument is not
+ * provided in the option itself, do not attempt to get it from
+ * the next argument, unless PARSE_OPT_OPTARG_ALLOW_NEXT is set.
+ *
+ * This prevents a non-option argument from being interpreted as an
+ * optional argument of a preceding option, for example:
+ *
+ * $ cmd --opt val
+ * -> is "val" argument of "--opt" or a separate non-option
+ * argument?
+ *
+ * With PARSE_OPT_OPTARG_ALLOW_NEXT, "val" is interpreted as
+ * the argument of "--opt", i.e. the same as "--opt=val".
+ * Without PARSE_OPT_OPTARG_ALLOW_NEXT, --opt is interpreted
+ * as having the default value, and "val" as a separate non-option
+ * argument.
+ *
+ * PARSE_OPT_OPTARG_ALLOW_NEXT is useful for commands that take no
+ * non-option arguments and want to allow more flexibility in
+ * optional argument passing.
+ */
+ force_defval = true;
+ }
+
+ if (p->argc <= 1 || p->argv[1][0] == '-') {
+ /*
+ * If next argument is an option or does not exist,
+ * use the default value.
+ */
+ force_defval = true;
+ }
+ }
+
if (opt->flags & PARSE_OPT_NOBUILD) {
char reason[128];
bool noarg = false;
@@ -148,7 +185,7 @@ static int get_value(struct parse_opt_ctx_t *p,
noarg = true;
if (opt->flags & PARSE_OPT_NOARG)
noarg = true;
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ if (force_defval)
noarg = true;
switch (opt->type) {
@@ -212,7 +249,7 @@ static int get_value(struct parse_opt_ctx_t *p,
err = 0;
if (unset)
*(const char **)opt->value = NULL;
- else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ else if (force_defval)
*(const char **)opt->value = (const char *)opt->defval;
else
err = get_arg(p, opt, flags, (const char **)opt->value);
@@ -244,7 +281,7 @@ static int get_value(struct parse_opt_ctx_t *p,
return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
if (opt->flags & PARSE_OPT_NOARG)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ if (force_defval)
return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
if (get_arg(p, opt, flags, &arg))
return -1;
@@ -255,7 +292,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(int *)opt->value = 0;
return 0;
}
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (force_defval) {
*(int *)opt->value = opt->defval;
return 0;
}
@@ -271,7 +308,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(unsigned int *)opt->value = 0;
return 0;
}
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (force_defval) {
*(unsigned int *)opt->value = opt->defval;
return 0;
}
@@ -289,7 +326,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(long *)opt->value = 0;
return 0;
}
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (force_defval) {
*(long *)opt->value = opt->defval;
return 0;
}
@@ -305,7 +342,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(unsigned long *)opt->value = 0;
return 0;
}
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (force_defval) {
*(unsigned long *)opt->value = opt->defval;
return 0;
}
@@ -321,7 +358,7 @@ static int get_value(struct parse_opt_ctx_t *p,
*(u64 *)opt->value = 0;
return 0;
}
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ if (force_defval) {
*(u64 *)opt->value = opt->defval;
return 0;
}
diff --git a/tools/lib/subcmd/parse-options.h b/tools/lib/subcmd/parse-options.h
index 8e9147358a28..c573a0ca5ca6 100644
--- a/tools/lib/subcmd/parse-options.h
+++ b/tools/lib/subcmd/parse-options.h
@@ -33,6 +33,7 @@ enum parse_opt_flags {
PARSE_OPT_KEEP_ARGV0 = 4,
PARSE_OPT_KEEP_UNKNOWN = 8,
PARSE_OPT_NO_INTERNAL_HELP = 16,
+ PARSE_OPT_OPTARG_ALLOW_NEXT = 32,
};
enum parse_opt_option_flags {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 1/6] rtla: Add libsubcmd dependency
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
In-Reply-To: <20260521141833.2353025-1-tglozar@redhat.com>
In preparation for migrating RTLA to libsubcmd, build libsubcmd from the
appropriate directory next to the RTLA build proper, and link the
resulting object to RTLA.
libsubcmd uses str_error_r() and strlcpy() at several places. To support
these, also link the respective libraries from tools/lib.
For completeness, also add tools/include to include path. This will
allow other userspace functions and macros shipped with the kernel to be
used in RTLA; perf and bpftool, two other users of libsubcmd, already do
that.
To prevent a name conflict, rename RTLA's run_command() function to
run_tool_command(), and replace RTLA's own container_of implementation
with the one in tools/include/linux/container_of.h.
Assisted-by: Composer:composer-1
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/.gitignore | 2 ++
tools/tracing/rtla/Makefile | 66 ++++++++++++++++++++++++++++++----
tools/tracing/rtla/src/rtla.c | 8 ++---
tools/tracing/rtla/src/utils.h | 6 ++--
4 files changed, 67 insertions(+), 15 deletions(-)
diff --git a/tools/tracing/rtla/.gitignore b/tools/tracing/rtla/.gitignore
index 231fb8d67f97..c7b4bf1c8ba9 100644
--- a/tools/tracing/rtla/.gitignore
+++ b/tools/tracing/rtla/.gitignore
@@ -10,3 +10,5 @@ custom_filename.txt
osnoise_irq_noise_hist.txt
osnoise_trace.txt
timerlat_trace.txt
+libsubcmd/
+lib/
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 45690ee14544..19eb5581fa23 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -27,6 +27,30 @@ endif
RTLA := $(OUTPUT)rtla
RTLA_IN := $(RTLA)-in.o
+LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
+ifneq ($(OUTPUT),)
+ LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
+else
+ LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd
+endif
+LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a
+LIBSUBCMD_INCLUDES = $(LIBSUBCMD_OUTPUT)/include
+LIBSUBCMD_MAKEFLAGS = O=$(LIBSUBCMD_OUTPUT) DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir=
+
+TOOLS_INCLUDES = -I$(srctree)/tools/include
+
+ifneq ($(OUTPUT),)
+ LIB_OUTPUT = $(abspath $(OUTPUT))/lib
+else
+ LIB_OUTPUT = $(CURDIR)/lib
+endif
+
+LIB_STRING = $(LIB_OUTPUT)/string.o
+LIB_STRING_SRC = $(srctree)/tools/lib/string.c
+
+LIB_STR_ERROR_R = $(LIB_OUTPUT)/str_error_r.o
+LIB_STR_ERROR_R_SRC = $(srctree)/tools/lib/str_error_r.c
+
VERSION := $(shell sh -c "make -sC ../../.. kernelversion | grep -v make")
DOCSRC := ../../../Documentation/tools/rtla/
@@ -66,7 +90,7 @@ ifeq ($(config),1)
include Makefile.config
endif
-CFLAGS += $(INCLUDES) $(LIB_INCLUDES)
+CFLAGS += $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_INCLUDES) -I$(LIBSUBCMD_INCLUDES)
export CFLAGS OUTPUT srctree
@@ -93,20 +117,48 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
$(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
endif
-$(RTLA): $(RTLA_IN)
- $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(EXTLIBS)
+$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
-static: $(RTLA_IN)
+static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(eval LDFLAGS += -static)
- $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(EXTLIBS)
+ $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
rtla.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
-$(RTLA_IN): fixdep FORCE src/timerlat.skel.h
+$(RTLA_IN): fixdep FORCE src/timerlat.skel.h $(LIBSUBCMD_INCLUDES)
make $(build)=rtla
-clean: doc_clean fixdep-clean
+$(LIBSUBCMD_OUTPUT):
+ $(Q)$(MKDIR) -p $@
+
+$(LIBSUBCMD_INCLUDES): $(LIBSUBCMD_OUTPUT)
+ $(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+ install_headers
+
+$(LIBSUBCMD): fixdep $(LIBSUBCMD_OUTPUT)
+ $(Q)$(MAKE) -C $(LIBSUBCMD_DIR) $(LIBSUBCMD_MAKEFLAGS) \
+ $@
+
+$(LIB_OUTPUT):
+ $(Q)$(MKDIR) -p $@
+
+$(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+$(LIB_STRING): $(LIB_STRING_SRC) $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
+libsubcmd-clean:
+ $(call QUIET_CLEAN, libsubcmd)
+ $(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
+
+lib-clean:
+ $(call QUIET_CLEAN, lib)
+ $(Q)$(RM) -r -- $(LIB_OUTPUT)
+
+clean: doc_clean fixdep-clean libsubcmd-clean lib-clean
$(call QUIET_CLEAN, rtla)
$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)rm -f rtla rtla-static fixdep FEATURE-DUMP rtla-*
diff --git a/tools/tracing/rtla/src/rtla.c b/tools/tracing/rtla/src/rtla.c
index 3398250076ea..f4342ca684c3 100644
--- a/tools/tracing/rtla/src/rtla.c
+++ b/tools/tracing/rtla/src/rtla.c
@@ -38,12 +38,12 @@ static void rtla_usage(int err)
}
/*
- * run_command - try to run a rtla tool command
+ * run_tool_command - try to run a rtla tool command
*
* It returns 0 if it fails. The tool's main will generally not
* return as they should call exit().
*/
-int run_command(int argc, char **argv, int start_position)
+int run_tool_command(int argc, char **argv, int start_position)
{
if (strcmp(argv[start_position], "osnoise") == 0) {
osnoise_main(argc-start_position, &argv[start_position]);
@@ -69,7 +69,7 @@ int main(int argc, char *argv[])
int retval;
/* is it an alias? */
- retval = run_command(argc, argv, 0);
+ retval = run_tool_command(argc, argv, 0);
if (retval)
exit(0);
@@ -82,7 +82,7 @@ int main(int argc, char *argv[])
rtla_usage(0);
}
- retval = run_command(argc, argv, 1);
+ retval = run_tool_command(argc, argv, 1);
if (retval)
exit(0);
diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h
index e794ede64b2c..96fd72042717 100644
--- a/tools/tracing/rtla/src/utils.h
+++ b/tools/tracing/rtla/src/utils.h
@@ -7,6 +7,8 @@
#include <stdbool.h>
#include <stdlib.h>
+#include <linux/container_of.h>
+
/*
* '18446744073709551615\0'
*/
@@ -37,10 +39,6 @@ static inline bool str_has_prefix(const char *str, const char *prefix)
return strncmp(str, prefix, strlen(prefix)) == 0;
}
-#define container_of(ptr, type, member)({ \
- const typeof(((type *)0)->member) *__mptr = (ptr); \
- (type *)((char *)__mptr - offsetof(type, member)) ; })
-
extern int config_debug;
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/6] rtla: Migrate to libsubcmd for command line option parsing
From: Tomas Glozar @ 2026-05-21 14:18 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, Ivan Pravdin, Namhyung Kim, Ian Rogers,
Arnaldo Carvalho de Melo, LKML, linux-trace-kernel,
linux-perf-users
[ CC to linux-perf-users for the libsubcmd code changes ]
rtla currently uses its own implementation that uses getopt_long() to
parse command-line arguments.
Migrate rtla to use libsubcmd for command line argument parsing,
similarly to what is already done by other tools like perf, bpftool,
and objtool. Among other benefits, this allows help messages to be
generated automatically rather than having to be typed out manually
for each tool.
libsubcmd is extended with a flag to parse optarg from separate
argument if a new flag is turned on. Without the flag, the old behavior
is preserved. That keeps the parsing working for tools that use
positional arguments, and allows RTLA to keep its flexible syntax for -C
and -t options and their long variants, --cgroup and --trace-output.
Another flag is added to disable automatic definition of --no-xy for
every option --xy and vice versa, which overlaps for RTLA's --irq and
--thread options.
The new implementation is moved into a separate file, cli.c, together
with a tiny header counterpart, cli.h. This helps separate the parsing
logic, which has little in common with the rest of RTLA, in a separate
module. Another new file, cli_p.h, is used as a private header to contain
macros and static function declarations that are also used by unit tests
next to cli.c, but should not be imported from elsewhere.
Macros to generate struct option array fields for libsubcmd's
parse_args() are used to preserve the consolidation of argument parsing
code across different RTLA tools. Kernel and user threads are, as
an exception, treated as common, although they are currently implemented
for timerlat only, in line with earlier consolidation changes.
The test suite is expanded to include two levels of unit tests, one testing
the already existing tool_parse_args() functions, one tests option callbacks,
which are a new level of the CLI parser added in this patchset. This helps
to verify that no regressions are caused by this refactoring.
I expect more improvements to the code being possible in the future,
like creating macros for option groups to further deduplicate the code,
reducing the amount of extra code in the _parse_args() functions, or
implementing support for unsetting options (which is currently only
supported for those that do not use a custom callback).
v2 changes:
- Two unit test suites are added to cover regressions, after several
options were broken by the v1. The test suites cover all parsing
issues reported in the v1 as well as those found during additional
testing.
- Return value of all paths that print help, including those that
are handled in RTLA, are set to libsubcmd's help exit code of 129.
Previously, only the tool help returned 129. While some other tools
(e.g. bpftool) do that, RTLA unifies those for consistency. The return
value is also added to the corresponding section in documentation.
- Incorrect parsing of --no-irq and --no-thread is fixed using a newly
added libsubcmd option flag.
- Incorrect parsing of -n and -u timerlat options (which erroneously
required an argument in v1) is fixed.
- A now stale declaration of removed function common_parse_options()
is removed from common.h.
- Segmentation fault on abbreviated --help (e.g. --he) is fixed.
- Incorrect formatting of OPT_END macro (spurious tab) is fixed.
- All opt_* callbacks now reject unimplemented unset (--no-) correctly.
- opt_trigger_cb() and opt_filter_cb() now take only the required events
field, not the whole params structure.
- Spurious opt_osnoise_threshold_cb() which is actually just a wrapper for
opt_llong_callback() is removed.
- Old off-by-one typo is fixed in --dma-latency and -E/--entries error
messages, to make it consistent with the newly added unit tests.
- Fix a bug in Makefile that defined LIBSUBCMD_INCLUDES as -I... and then
used it as a target name: define it as the path only, and then add -I$(...)
to CFLAGS, as this is the only include path that is generated during the build
itself.
v1: https://lore.kernel.org/linux-trace-kernel/20260320150651.51057-1-tglozar@redhat.com/T
Tomas Glozar (6):
rtla: Add libsubcmd dependency
tools subcmd: support optarg as separate argument
tools subcmd: allow parsing distinct --opt and --no-opt
rtla: Parse cmdline using libsubcmd
rtla/tests: Add unit tests for _parse_args() functions
rtla/tests: Add unit tests for CLI option callbacks
Documentation/tools/rtla/common_appendix.txt | 7 +-
tools/lib/subcmd/parse-options.c | 63 +-
tools/lib/subcmd/parse-options.h | 4 +
tools/tracing/rtla/.gitignore | 2 +
tools/tracing/rtla/Makefile | 66 +-
tools/tracing/rtla/src/Build | 2 +-
tools/tracing/rtla/src/cli.c | 537 +++++++++++++
tools/tracing/rtla/src/cli.h | 9 +
tools/tracing/rtla/src/cli_p.h | 670 +++++++++++++++++
tools/tracing/rtla/src/common.c | 109 ---
tools/tracing/rtla/src/common.h | 27 +-
tools/tracing/rtla/src/osnoise.c | 9 +-
tools/tracing/rtla/src/osnoise_hist.c | 221 +-----
tools/tracing/rtla/src/osnoise_top.c | 200 +----
tools/tracing/rtla/src/rtla.c | 92 ---
tools/tracing/rtla/src/timerlat.c | 9 +-
tools/tracing/rtla/src/timerlat.h | 6 +-
tools/tracing/rtla/src/timerlat_hist.c | 317 +-------
tools/tracing/rtla/src/timerlat_top.c | 286 +------
tools/tracing/rtla/src/utils.c | 28 +-
tools/tracing/rtla/src/utils.h | 9 +-
tools/tracing/rtla/tests/hwnoise.t | 2 +-
tools/tracing/rtla/tests/osnoise.t | 6 +-
tools/tracing/rtla/tests/timerlat.t | 6 +-
tools/tracing/rtla/tests/unit/Build | 5 +
tools/tracing/rtla/tests/unit/Makefile.unit | 4 +-
.../rtla/tests/unit/cli_opt_callback.c | 704 ++++++++++++++++++
.../rtla/tests/unit/cli_params_assert.h | 68 ++
.../rtla/tests/unit/osnoise_hist_cli.c | 557 ++++++++++++++
.../tracing/rtla/tests/unit/osnoise_top_cli.c | 503 +++++++++++++
.../rtla/tests/unit/timerlat_hist_cli.c | 702 +++++++++++++++++
.../rtla/tests/unit/timerlat_top_cli.c | 634 ++++++++++++++++
tools/tracing/rtla/tests/unit/unit_tests.c | 13 +
33 files changed, 4566 insertions(+), 1311 deletions(-)
create mode 100644 tools/tracing/rtla/src/cli.c
create mode 100644 tools/tracing/rtla/src/cli.h
create mode 100644 tools/tracing/rtla/src/cli_p.h
delete mode 100644 tools/tracing/rtla/src/rtla.c
create mode 100644 tools/tracing/rtla/tests/unit/cli_opt_callback.c
create mode 100644 tools/tracing/rtla/tests/unit/cli_params_assert.h
create mode 100644 tools/tracing/rtla/tests/unit/osnoise_hist_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/osnoise_top_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/timerlat_hist_cli.c
create mode 100644 tools/tracing/rtla/tests/unit/timerlat_top_cli.c
--
2.54.0
^ 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