Skip to content

Commit c217157

Browse files
puranjaymohanpalmer-dabbelt
authored andcommitted
riscv: Implement HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS
This patch enables support for DYNAMIC_FTRACE_WITH_CALL_OPS on RISC-V. This allows each ftrace callsite to provide an ftrace_ops to the common ftrace trampoline, allowing each callsite to invoke distinct tracer functions without the need to fall back to list processing or to allocate custom trampolines for each callsite. This significantly speeds up cases where multiple distinct trace functions are used and callsites are mostly traced by a single tracer. The idea and most of the implementation is taken from the ARM64's implementation of the same feature. The idea is to place a pointer to the ftrace_ops as a literal at a fixed offset from the function entry point, which can be recovered by the common ftrace trampoline. We use -fpatchable-function-entry to reserve 8 bytes above the function entry by emitting 2 4 byte or 4 2 byte nops depending on the presence of CONFIG_RISCV_ISA_C. These 8 bytes are patched at runtime with a pointer to the associated ftrace_ops for that callsite. Functions are aligned to 8 bytes to make sure that the accesses to this literal are atomic. This approach allows for directly invoking ftrace_ops::func even for ftrace_ops which are dynamically-allocated (or part of a module), without going via ftrace_ops_list_func. We've benchamrked this with the ftrace_ops sample module on Spacemit K1 Jupiter: Without this patch: baseline (Linux rivos 6.14.0-09584-g7d06015d936c #3 SMP Sat Mar 29 +-----------------------+-----------------+----------------------------+ | Number of tracers | Total time (ns) | Per-call average time | |-----------------------+-----------------+----------------------------| | Relevant | Irrelevant | 100000 calls | Total (ns) | Overhead (ns) | |----------+------------+-----------------+------------+---------------| | 0 | 0 | 1357958 | 13 | - | | 0 | 1 | 1302375 | 13 | - | | 0 | 2 | 1302375 | 13 | - | | 0 | 10 | 1379084 | 13 | - | | 0 | 100 | 1302458 | 13 | - | | 0 | 200 | 1302333 | 13 | - | |----------+------------+-----------------+------------+---------------| | 1 | 0 | 13677833 | 136 | 123 | | 1 | 1 | 18500916 | 185 | 172 | | 1 | 2 | 2285645 | 228 | 215 | | 1 | 10 | 58824709 | 588 | 575 | | 1 | 100 | 505141584 | 5051 | 5038 | | 1 | 200 | 1580473126 | 15804 | 15791 | |----------+------------+-----------------+------------+---------------| | 1 | 0 | 13561000 | 135 | 122 | | 2 | 0 | 19707292 | 197 | 184 | | 10 | 0 | 67774750 | 677 | 664 | | 100 | 0 | 714123125 | 7141 | 7128 | | 200 | 0 | 1918065668 | 19180 | 19167 | +----------+------------+-----------------+------------+---------------+ Note: per-call overhead is estimated relative to the baseline case with 0 relevant tracers and 0 irrelevant tracers. With this patch: v4-rc4 (Linux rivos 6.14.0-09598-gd75747611c93 #4 SMP Sat Mar 29 +-----------------------+-----------------+----------------------------+ | Number of tracers | Total time (ns) | Per-call average time | |-----------------------+-----------------+----------------------------| | Relevant | Irrelevant | 100000 calls | Total (ns) | Overhead (ns) | |----------+------------+-----------------+------------+---------------| | 0 | 0 | 1459917 | 14 | - | | 0 | 1 | 1408000 | 14 | - | | 0 | 2 | 1383792 | 13 | - | | 0 | 10 | 1430709 | 14 | - | | 0 | 100 | 1383791 | 13 | - | | 0 | 200 | 1383750 | 13 | - | |----------+------------+-----------------+------------+---------------| | 1 | 0 | 5238041 | 52 | 38 | | 1 | 1 | 5228542 | 52 | 38 | | 1 | 2 | 5325917 | 53 | 40 | | 1 | 10 | 5299667 | 52 | 38 | | 1 | 100 | 5245250 | 52 | 39 | | 1 | 200 | 5238459 | 52 | 39 | |----------+------------+-----------------+------------+---------------| | 1 | 0 | 5239083 | 52 | 38 | | 2 | 0 | 19449417 | 194 | 181 | | 10 | 0 | 67718584 | 677 | 663 | | 100 | 0 | 709840708 | 7098 | 7085 | | 200 | 0 | 2203580626 | 22035 | 22022 | +----------+------------+-----------------+------------+---------------+ Note: per-call overhead is estimated relative to the baseline case with 0 relevant tracers and 0 irrelevant tracers. As can be seen from the above: a) Whenever there is a single relevant tracer function associated with a tracee, the overhead of invoking the tracer is constant, and does not scale with the number of tracers which are *not* associated with that tracee. b) The overhead for a single relevant tracer has dropped to ~1/3 of the overhead prior to this series (from 122ns to 38ns). This is largely due to permitting calls to dynamically-allocated ftrace_ops without going through ftrace_ops_list_func. Signed-off-by: Puranjay Mohan <puranjay12@gmail.com> [update kconfig, asm, refactor] Signed-off-by: Andy Chiu <andybnac@gmail.com> Tested-by: Björn Töpel <bjorn@rivosinc.com> Link: https://lore.kernel.org/r/20250407180838.42877-10-andybnac@gmail.com Signed-off-by: Alexandre Ghiti <alexghiti@rivosinc.com> Signed-off-by: Palmer Dabbelt <palmer@dabbelt.com>
1 parent d0262e9 commit c217157

File tree

5 files changed

+105
-6
lines changed

5 files changed

+105
-6
lines changed

arch/riscv/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ config RISCV
9999
select EDAC_SUPPORT
100100
select FRAME_POINTER if PERF_EVENTS || (FUNCTION_TRACER && !DYNAMIC_FTRACE)
101101
select FTRACE_MCOUNT_USE_PATCHABLE_FUNCTION_ENTRY if DYNAMIC_FTRACE
102+
select FUNCTION_ALIGNMENT_8B if DYNAMIC_FTRACE_WITH_CALL_OPS
102103
select GENERIC_ARCH_TOPOLOGY
103104
select GENERIC_ATOMIC64 if !64BIT
104105
select GENERIC_CLOCKEVENTS_BROADCAST if SMP
@@ -152,6 +153,7 @@ config RISCV
152153
select HAVE_DYNAMIC_FTRACE if !XIP_KERNEL && MMU && (CLANG_SUPPORTS_DYNAMIC_FTRACE || GCC_SUPPORTS_DYNAMIC_FTRACE)
153154
select FUNCTION_ALIGNMENT_4B if HAVE_DYNAMIC_FTRACE && RISCV_ISA_C
154155
select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
156+
select HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS if (DYNAMIC_FTRACE_WITH_ARGS && !CFI_CLANG)
155157
select HAVE_DYNAMIC_FTRACE_WITH_ARGS if HAVE_DYNAMIC_FTRACE
156158
select HAVE_FTRACE_GRAPH_FUNC
157159
select HAVE_FTRACE_MCOUNT_RECORD if !XIP_KERNEL

arch/riscv/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
1515
LDFLAGS_vmlinux += --no-relax
1616
KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
1717
ifeq ($(CONFIG_RISCV_ISA_C),y)
18-
CC_FLAGS_FTRACE := -fpatchable-function-entry=4
18+
CC_FLAGS_FTRACE := -fpatchable-function-entry=8,4
1919
else
20-
CC_FLAGS_FTRACE := -fpatchable-function-entry=2
20+
CC_FLAGS_FTRACE := -fpatchable-function-entry=4,2
2121
endif
2222
endif
2323

arch/riscv/kernel/asm-offsets.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ void asm_offsets(void)
493493
DEFINE(STACKFRAME_SIZE_ON_STACK, ALIGN(sizeof(struct stackframe), STACK_ALIGN));
494494
OFFSET(STACKFRAME_FP, stackframe, fp);
495495
OFFSET(STACKFRAME_RA, stackframe, ra);
496+
#ifdef CONFIG_FUNCTION_TRACER
497+
DEFINE(FTRACE_OPS_FUNC, offsetof(struct ftrace_ops, func));
498+
#endif
496499

497500
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
498501
DEFINE(FREGS_SIZE_ON_STACK, ALIGN(sizeof(struct __arch_ftrace_regs), STACK_ALIGN));

arch/riscv/kernel/ftrace.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#ifdef CONFIG_DYNAMIC_FTRACE
1717
unsigned long ftrace_call_adjust(unsigned long addr)
1818
{
19+
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS))
20+
return addr + 8;
21+
1922
return addr + MCOUNT_AUIPC_SIZE;
2023
}
2124

@@ -64,9 +67,52 @@ static int __ftrace_modify_call(unsigned long source, unsigned long target, bool
6467
return 0;
6568
}
6669

70+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
71+
static const struct ftrace_ops *riscv64_rec_get_ops(struct dyn_ftrace *rec)
72+
{
73+
const struct ftrace_ops *ops = NULL;
74+
75+
if (rec->flags & FTRACE_FL_CALL_OPS_EN) {
76+
ops = ftrace_find_unique_ops(rec);
77+
WARN_ON_ONCE(!ops);
78+
}
79+
80+
if (!ops)
81+
ops = &ftrace_list_ops;
82+
83+
return ops;
84+
}
85+
86+
static int ftrace_rec_set_ops(const struct dyn_ftrace *rec,
87+
const struct ftrace_ops *ops)
88+
{
89+
unsigned long literal = rec->ip - 8;
90+
91+
return patch_text_nosync((void *)literal, &ops, sizeof(ops));
92+
}
93+
94+
static int ftrace_rec_set_nop_ops(struct dyn_ftrace *rec)
95+
{
96+
return ftrace_rec_set_ops(rec, &ftrace_nop_ops);
97+
}
98+
99+
static int ftrace_rec_update_ops(struct dyn_ftrace *rec)
100+
{
101+
return ftrace_rec_set_ops(rec, riscv64_rec_get_ops(rec));
102+
}
103+
#else
104+
static int ftrace_rec_set_nop_ops(struct dyn_ftrace *rec) { return 0; }
105+
static int ftrace_rec_update_ops(struct dyn_ftrace *rec) { return 0; }
106+
#endif
107+
67108
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
68109
{
69110
unsigned long distance, orig_addr, pc = rec->ip - MCOUNT_AUIPC_SIZE;
111+
int ret;
112+
113+
ret = ftrace_rec_update_ops(rec);
114+
if (ret)
115+
return ret;
70116

71117
orig_addr = (unsigned long)&ftrace_caller;
72118
distance = addr > orig_addr ? addr - orig_addr : orig_addr - addr;
@@ -79,6 +125,11 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
79125
int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr)
80126
{
81127
u32 nop4 = RISCV_INSN_NOP4;
128+
int ret;
129+
130+
ret = ftrace_rec_set_nop_ops(rec);
131+
if (ret)
132+
return ret;
82133

83134
if (patch_insn_write((void *)rec->ip, &nop4, MCOUNT_NOP4_SIZE))
84135
return -EPERM;
@@ -99,6 +150,10 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
99150
unsigned int nops[2], offset;
100151
int ret;
101152

153+
ret = ftrace_rec_set_nop_ops(rec);
154+
if (ret)
155+
return ret;
156+
102157
offset = (unsigned long) &ftrace_caller - pc;
103158
nops[0] = to_auipc_t0(offset);
104159
nops[1] = RISCV_INSN_NOP4;
@@ -113,6 +168,13 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
113168
ftrace_func_t ftrace_call_dest = ftrace_stub;
114169
int ftrace_update_ftrace_func(ftrace_func_t func)
115170
{
171+
/*
172+
* When using CALL_OPS, the function to call is associated with the
173+
* call site, and we don't have a global function pointer to update.
174+
*/
175+
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS))
176+
return 0;
177+
116178
WRITE_ONCE(ftrace_call_dest, func);
117179
/*
118180
* The data fence ensure that the update to ftrace_call_dest happens
@@ -143,8 +205,13 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
143205
{
144206
unsigned long caller = rec->ip - MCOUNT_AUIPC_SIZE;
145207
unsigned int call[2];
208+
int ret;
146209

147210
make_call_t0(caller, old_addr, call);
211+
ret = ftrace_rec_update_ops(rec);
212+
if (ret)
213+
return ret;
214+
148215
return __ftrace_modify_call(caller, addr, true);
149216
}
150217
#endif

arch/riscv/kernel/mcount-dyn.S

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,21 +139,48 @@
139139

140140
.macro PREPARE_ARGS
141141
addi a0, t0, -MCOUNT_JALR_SIZE // ip (callsite's jalr insn)
142+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
143+
/*
144+
* When CALL_OPS is enabled (2 or 4) nops [8B] are placed before the
145+
* function entry, these are later overwritten with the pointer to the
146+
* associated struct ftrace_ops.
147+
*
148+
* -8: &ftrace_ops of the associated tracer function.
149+
*<ftrace enable>:
150+
* 0: auipc t0/ra, 0x?
151+
* 4: jalr t0/ra, ?(t0/ra)
152+
*
153+
* -8: &ftrace_nop_ops
154+
*<ftrace disable>:
155+
* 0: nop
156+
* 4: nop
157+
*
158+
* t0 is set to ip+8 after the jalr is executed at the callsite,
159+
* so we find the associated op at t0-16.
160+
*/
161+
mv a1, ra // parent_ip
162+
REG_L a2, -16(t0) // op
163+
REG_L ra, FTRACE_OPS_FUNC(a2) // op->func
164+
#else
142165
la a1, function_trace_op
143-
REG_L a2, 0(a1)
144-
mv a1, ra
145-
mv a3, sp
166+
REG_L a2, 0(a1) // op
167+
mv a1, ra // parent_ip
168+
#endif
169+
mv a3, sp // regs
146170
.endm
147171

148172
SYM_FUNC_START(ftrace_caller)
149173
mv t1, zero
150174
SAVE_ABI_REGS
151175
PREPARE_ARGS
152176

177+
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
178+
jalr ra
179+
#else
153180
SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
154181
REG_L ra, ftrace_call_dest
155182
jalr ra, 0(ra)
156-
183+
#endif
157184
RESTORE_ABI_REGS
158185
bnez t1, .Ldirect
159186
jr t0

0 commit comments

Comments
 (0)