Re: [RFC PATCH v3 06/11] powerpc64/ftrace: Move ftrace sequence out of line

Naveen N Rao
2024-07-01T19:50:16Z

On Mon, Jul 01, 2024 at 08:39:03PM GMT, Nicholas Piggin wrote:
> On Fri Jun 21, 2024 at 4:54 AM AEST, Naveen N Rao wrote:
> > Function profile sequence on powerpc includes two instructions at the
> > beginning of each function:
> > mflr r0
> > bl ftrace_caller
> >
> > The call to ftrace_caller() gets nop'ed out during kernel boot and is
> > patched in when ftrace is enabled.
> >
> > Given the sequence, we cannot return from ftrace_caller with 'blr' as we
> > need to keep LR and r0 intact. This results in link stack imbalance when
>
> (link stack is IBMese for "return address predictor", if that wasn't
> obvious)
>
> > ftrace is enabled. To address that, we would like to use a three
> > instruction sequence:
> > mflr r0
> > bl ftrace_caller
> > mtlr r0
> >
> > Further more, to support DYNAMIC_FTRACE_WITH_CALL_OPS, we need to
> > reserve two instruction slots before the function. This results in a
> > total of five instruction slots to be reserved for ftrace use on each
> > function that is traced.
> >
> > Move the function profile sequence out-of-line to minimize its impact.
> > To do this, we reserve a single nop at function entry using
> > -fpatchable-function-entry=1 and add a pass on vmlinux.o to determine
>
> What's the need to do this on vmlinux.o rather than vmlinux? We have
> all function syms?

We want to be able to build and include another .o file to be linked
into vmlinux. That file contains symbols (pfe_stub_text, et al) used by
vmlinux.o

>
> > the total number of functions that can be traced. This is then used to
> > generate a .S file reserving the appropriate amount of space for use as
> > ftrace stubs, which is built and linked into vmlinux.
>
> An example instruction listing for the "after" case would be nice too.

Sure.

>
> Is this all ftrace stubs in the one place? And how do you deal with
> kernel size exceeding the limit, if so?

Yes, all at the end. Ftrace init fails on bootup if text size exceeds
branch range. I should really be putting in a post-link script to detect
and break the build in that case.

>
> >
> > On bootup, the stub space is split into separate stubs per function and
> > populated with the proper instruction sequence. A pointer to the
> > associated stub is maintained in dyn_arch_ftrace.
> >
> > For modules, space for ftrace stubs is reserved from the generic module
> > stub space.
> >
> > This is restricted to and enabled by default only on 64-bit powerpc.
>
> This is cool.
>
> [...]
>
> > --- a/arch/powerpc/Kconfig
> > +++ b/arch/powerpc/Kconfig
> > @@ -568,6 +568,11 @@ config ARCH_USING_PATCHABLE_FUNCTION_ENTRY
> > def_bool $(success,$(srctree)/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh $(CC) -mlittle-endian) if PPC64 && CPU_LITTLE_ENDIAN
> > def_bool $(success,$(srctree)/arch/powerpc/tools/gcc-check-fpatchable-function-entry.sh $(CC) -mbig-endian) if PPC64 && CPU_BIG_ENDIAN
> >
> > +config FTRACE_PFE_OUT_OF_LINE
> > + def_bool PPC64 && ARCH_USING_PATCHABLE_FUNCTION_ENTRY
> > + depends on PPC64
> > + select ARCH_WANTS_PRE_LINK_VMLINUX
>
> This remains powerpc specific? Maybe add a PPC_ prefix to the config
> option?
>
> Bikeshed - should PFE be expanded to be consistent with the ARCH_
> option?

I agree. PFE isn't immediately obvious. Now that I think about it, not
sure it really matters that this uses -fpatchable-function-entry. I'll
call this PPC_FTRACE_SEQUENCE_OUT_OF_LINE. Suggestions welcome :)

>
> [...]
>
> > diff --git a/arch/powerpc/include/asm/ftrace.h b/arch/powerpc/include/asm/ftrace.h
> > index 201f9d15430a..9da1da0f87b4 100644
> > --- a/arch/powerpc/include/asm/ftrace.h
> > +++ b/arch/powerpc/include/asm/ftrace.h
> > @@ -26,6 +26,9 @@ unsigned long prepare_ftrace_return(unsigned long parent, unsigned long ip,
> > struct module;
> > struct dyn_ftrace;
> > struct dyn_arch_ftrace {
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + unsigned long pfe_stub;
> > +#endif
> > };
>
> Ah, we put something else in here. This is the offset to the
> stub? Maybe call it pfe_stub_offset?

Ack.

>
> [...]
>
> > diff --git a/arch/powerpc/kernel/trace/ftrace.c b/arch/powerpc/kernel/trace/ftrace.c
> > index 2cff37b5fd2c..9f3c10307331 100644
> > --- a/arch/powerpc/kernel/trace/ftrace.c
> > +++ b/arch/powerpc/kernel/trace/ftrace.c
> > @@ -37,7 +37,8 @@ unsigned long ftrace_call_adjust(unsigned long addr)
> > if (addr >= (unsigned long)__exittext_begin && addr < (unsigned long)__exittext_end)
> > return 0;
> >
> > - if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY))
> > + if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY) &&
> > + !IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE))
> > addr += MCOUNT_INSN_SIZE;
> >
> > return addr;
>
> I don't understand what this is doing acutally (before this patch
> even). We still emit one/two patchable intsructions at entry, so
> why do we only need to adjust by zero/one instruction here?

ftrace wants the address of the instruction that calls into
ftrace_caller. We emit 2 nops with -fpatchable-function-entry, so we
"adjust" the ftrace location to be the second nop.

>
>
> > @@ -82,7 +83,7 @@ static inline int ftrace_modify_code(unsigned long ip, ppc_inst_t old, ppc_inst_
> > {
> > int ret = ftrace_validate_inst(ip, old);
> >
> > - if (!ret)
> > + if (!ret && !ppc_inst_equal(old, new))
> > ret = patch_instruction((u32 *)ip, new);
> >
> > return ret;
>
> Is this leftover debugging stuff or should it be in a different patch?

It is intentional to simplify further patching and checks. I will move
this to a separate patch.

>
> > @@ -132,11 +133,23 @@ static unsigned long ftrace_lookup_module_stub(unsigned long ip, unsigned long a
> > }
> > #endif
> >
> > +static unsigned long ftrace_get_pfe_stub(struct dyn_ftrace *rec)
> > +{
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + return rec->arch.pfe_stub;
> > +#else
> > + BUILD_BUG();
> > +#endif
> > +}
> > +
> > static int ftrace_get_call_inst(struct dyn_ftrace *rec, unsigned long addr, ppc_inst_t *call_inst)
> > {
> > unsigned long ip = rec->ip;
> > unsigned long stub;
> >
> > + if (IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE))
> > + ip = ftrace_get_pfe_stub(rec) + MCOUNT_INSN_SIZE; /* second instruction in stub */
>
> Maybe put the ip = rec->ip; into an else case here.

Ok.

>
> [...]
>
> > @@ -155,6 +168,79 @@ static int ftrace_get_call_inst(struct dyn_ftrace *rec, unsigned long addr, ppc_
> > return 0;
> > }
> >
> > +static int ftrace_init_pfe_stub(struct module *mod, struct dyn_ftrace *rec)
> > +{
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + static int pfe_stub_text_index, pfe_stub_inittext_index;
> > + int ret = 0, pfe_stub_count, *pfe_stub_index;
> > + ppc_inst_t inst;
> > + struct ftrace_pfe_stub *pfe_stub, pfe_stub_template = {
> > + .insn = {
> > + PPC_RAW_MFLR(_R0),
> > + PPC_RAW_NOP(), /* bl ftrace_caller */
> > + PPC_RAW_MTLR(_R0),
> > + PPC_RAW_NOP() /* b rec->ip + 4 */
> > + }
> > + };
> > +
> > + WARN_ON(rec->arch.pfe_stub);
> > +
> > + if (is_kernel_inittext(rec->ip)) {
> > + pfe_stub = ftrace_pfe_stub_inittext;
> > + pfe_stub_index = &pfe_stub_inittext_index;
> > + pfe_stub_count = ftrace_pfe_stub_inittext_count;
> > + } else if (is_kernel_text(rec->ip)) {
> > + pfe_stub = ftrace_pfe_stub_text;
> > + pfe_stub_index = &pfe_stub_text_index;
> > + pfe_stub_count = ftrace_pfe_stub_text_count;
> > +#ifdef CONFIG_MODULES
> > + } else if (mod) {
> > + pfe_stub = mod->arch.pfe_stubs;
> > + pfe_stub_index = &mod->arch.pfe_stub_index;
> > + pfe_stub_count = mod->arch.pfe_stub_count;
> > +#endif
> > + } else {
> > + return -EINVAL;
> > + }
> > +
> > + pfe_stub += (*pfe_stub_index)++;
> > +
> > + if (WARN_ON(*pfe_stub_index > pfe_stub_count))
> > + return -EINVAL;
> > +
> > + if (!is_offset_in_branch_range((long)rec->ip - (long)&pfe_stub->insn[0]) ||
> > + !is_offset_in_branch_range((long)(rec->ip + MCOUNT_INSN_SIZE) - (long)&pfe_stub->insn[3])) {
> > + pr_err("%s: ftrace pfe stub out of range (%p -> %p).\n",
> > + __func__, (void *)rec->ip, (void *)&pfe_stub->insn[0]);
> > + return -EINVAL;
> > + }
> > +
> > + rec->arch.pfe_stub = (unsigned long)&pfe_stub->insn[0];
> > +
> > + /* bl ftrace_caller */
> > + if (mod)
> > + /* We can't lookup the module since it is not fully formed yet */
>
> What do you mean here, what would a lookup look like if we could do it?

I originally had ftrace_get_call_inst() here, which does a
__module_text_address() lookup. It didn't work since the module is not
yet fully loaded when this is called. I can expand the comment.

>
> > + inst = ftrace_create_branch_inst(ftrace_get_pfe_stub(rec) + MCOUNT_INSN_SIZE,
> > + mod->arch.tramp, 1);
> > + else
> > + ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &inst);
> > + pfe_stub_template.insn[1] = ppc_inst_val(inst);
> > +
> > + /* b rec->ip + 4 */
> > + if (!ret && create_branch(&inst, &pfe_stub->insn[3], rec->ip + MCOUNT_INSN_SIZE, 0))
> > + return -EINVAL;
> > + pfe_stub_template.insn[3] = ppc_inst_val(inst);
> > +
> > + if (!ret)
> > + ret = patch_instructions((u32 *)pfe_stub, (u32 *)&pfe_stub_template,
> > + sizeof(pfe_stub_template), false);
> > +
> > + return ret;
> > +#else /* !CONFIG_FTRACE_PFE_OUT_OF_LINE */
> > + BUILD_BUG();
> > +#endif
> > +}
> > +
> > #ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
> > int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned long addr)
> > {
> > @@ -167,18 +253,29 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned
> > int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
> > {
> > ppc_inst_t old, new;
> > - int ret;
> > + unsigned long ip = rec->ip;
> > + int ret = 0;
> >
> > /* This can only ever be called during module load */
> > - if (WARN_ON(!IS_ENABLED(CONFIG_MODULES) || core_kernel_text(rec->ip)))
> > + if (WARN_ON(!IS_ENABLED(CONFIG_MODULES) || core_kernel_text(ip)))
> > return -EINVAL;
> >
> > old = ppc_inst(PPC_RAW_NOP());
> > - ret = ftrace_get_call_inst(rec, addr, &new);
> > - if (ret)
> > - return ret;
> > + if (IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE)) {
> > + ip = ftrace_get_pfe_stub(rec) + MCOUNT_INSN_SIZE; /* second instruction in stub */
> > + ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &old);
> > + }
> >
> > - return ftrace_modify_code(rec->ip, old, new);
> > + ret |= ftrace_get_call_inst(rec, addr, &new);
> > +
> > + if (!ret)
> > + ret = ftrace_modify_code(ip, old, new);
> > +
> > + if (!ret && IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE))
> > + ret = ftrace_modify_code(rec->ip, ppc_inst(PPC_RAW_NOP()),
> > + ppc_inst(PPC_RAW_BRANCH((long)ftrace_get_pfe_stub(rec) - (long)rec->ip)));
> > +
> > + return ret;
> > }
> >
> > int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr)
> > @@ -211,6 +308,13 @@ void ftrace_replace_code(int enable)
> > new_addr = ftrace_get_addr_new(rec);
> > update = ftrace_update_record(rec, enable);
> >
> > + if (IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE) && update != FTRACE_UPDATE_IGNORE) {
> > + ip = ftrace_get_pfe_stub(rec) + MCOUNT_INSN_SIZE;
> > + ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &nop_inst);
> > + if (ret)
> > + goto out;
> > + }
> > +
> > switch (update) {
> > case FTRACE_UPDATE_IGNORE:
> > default:
> > @@ -235,6 +339,24 @@ void ftrace_replace_code(int enable)
> >
> > if (!ret)
> > ret = ftrace_modify_code(ip, old, new);
> > +
> > + if (!ret && IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE) &&
> > + (update == FTRACE_UPDATE_MAKE_NOP || update == FTRACE_UPDATE_MAKE_CALL)) {
> > + /* Update the actual ftrace location */
> > + call_inst = ppc_inst(PPC_RAW_BRANCH((long)ftrace_get_pfe_stub(rec) -
> > + (long)rec->ip));
> > + nop_inst = ppc_inst(PPC_RAW_NOP());
> > + ip = rec->ip;
> > +
> > + if (update == FTRACE_UPDATE_MAKE_NOP)
> > + ret = ftrace_modify_code(ip, call_inst, nop_inst);
> > + else
> > + ret = ftrace_modify_code(ip, nop_inst, call_inst);
> > +
> > + if (ret)
> > + goto out;
> > + }
> > +
> > if (ret)
> > goto out;
> > }
> > @@ -254,7 +376,8 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
> > /* Verify instructions surrounding the ftrace location */
> > if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY)) {
> > /* Expect nops */
> > - ret = ftrace_validate_inst(ip - 4, ppc_inst(PPC_RAW_NOP()));
> > + if (!IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE))
> > + ret = ftrace_validate_inst(ip - 4, ppc_inst(PPC_RAW_NOP()));
> > if (!ret)
> > ret = ftrace_validate_inst(ip, ppc_inst(PPC_RAW_NOP()));
> > } else if (IS_ENABLED(CONFIG_PPC32)) {
> > @@ -278,6 +401,10 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
> > if (ret)
> > return ret;
> >
> > + /* Set up out-of-line stub */
> > + if (IS_ENABLED(CONFIG_FTRACE_PFE_OUT_OF_LINE))
> > + return ftrace_init_pfe_stub(mod, rec);
> > +
> > /* Nop-out the ftrace location */
> > new = ppc_inst(PPC_RAW_NOP());
> > addr = MCOUNT_ADDR;
> > diff --git a/arch/powerpc/kernel/trace/ftrace_entry.S b/arch/powerpc/kernel/trace/ftrace_entry.S
> > index 244a1c7bb1e8..b1cbef24f18f 100644
> > --- a/arch/powerpc/kernel/trace/ftrace_entry.S
> > +++ b/arch/powerpc/kernel/trace/ftrace_entry.S
> > @@ -78,10 +78,6 @@
> >
> > /* Get the _mcount() call site out of LR */
> > mflr r7
> > - /* Save it as pt_regs->nip */
> > - PPC_STL r7, _NIP(r1)
> > - /* Also save it in B's stackframe header for proper unwind */
> > - PPC_STL r7, LRSAVE+SWITCH_FRAME_SIZE(r1)
> > /* Save the read LR in pt_regs->link */
> > PPC_STL r0, _LINK(r1)
> >
> > @@ -96,16 +92,6 @@
> > lwz r5,function_trace_op@l(r3)
> > #endif
> >
> > -#ifdef CONFIG_LIVEPATCH_64
> > - mr r14, r7 /* remember old NIP */
> > -#endif
> > -
> > - /* Calculate ip from nip-4 into r3 for call below */
> > - subi r3, r7, MCOUNT_INSN_SIZE
> > -
> > - /* Put the original return address in r4 as parent_ip */
> > - mr r4, r0
> > -
> > /* Save special regs */
> > PPC_STL r8, _MSR(r1)
> > .if \allregs == 1
> > @@ -114,17 +100,64 @@
> > PPC_STL r11, _CCR(r1)
> > .endif
> >
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + /* Save our real return address locally for return */
> > + PPC_STL r7, STACK_INT_FRAME_MARKER(r1)
>
> Hmm, should you be using STACK_INT_FRAME_MARKER in a
> non-INT_FRAME? I actually wanted to turn the int marker
> into a 4 byte word and move it into a reserved space in
> the frame too. Could it go in pt_regs somewhere?

I can use a nvr.

>
> > + /*
> > + * We want the ftrace location in the function, but our lr (in r7)
> > + * points at the 'mtlr r0' instruction in the out of line stub. To
> > + * recover the ftrace location, we read the branch instruction in the
> > + * stub, and adjust our lr by the branch offset.
> > + */
> > + lwz r8, MCOUNT_INSN_SIZE(r7)
> > + slwi r8, r8, 6
> > + srawi r8, r8, 6
> > + add r3, r7, r8
>
> Clever. Maybe a comment in ftrace_init_pfe_stub() that says to
> keep that last instruction in synch with this?

Sure.

>
> > + /*
> > + * Override our nip to point past the branch in the original function.
> > + * This allows reliable stack trace and the ftrace stack tracer to work as-is.
> > + */
> > + add r7, r3, MCOUNT_INSN_SIZE
> > +#else
> > + /* Calculate ip from nip-4 into r3 for call below */
> > + subi r3, r7, MCOUNT_INSN_SIZE
> > +#endif
> > +
> > + /* Save NIP as pt_regs->nip */
> > + PPC_STL r7, _NIP(r1)
> > + /* Also save it in B's stackframe header for proper unwind */
> > + PPC_STL r7, LRSAVE+SWITCH_FRAME_SIZE(r1)
> > +#ifdef CONFIG_LIVEPATCH_64
> > + mr r14, r7 /* remember old NIP */
> > +#endif
> > +
> > + /* Put the original return address in r4 as parent_ip */
> > + mr r4, r0
> > +
> > /* Load &pt_regs in r6 for call below */
> > addi r6, r1, STACK_INT_FRAME_REGS
> > .endm
> >
> > .macro ftrace_regs_exit allregs
> > +#ifndef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > /* Load ctr with the possibly modified NIP */
> > PPC_LL r3, _NIP(r1)
> > mtctr r3
> >
> > #ifdef CONFIG_LIVEPATCH_64
> > cmpd r14, r3 /* has NIP been altered? */
> > +#endif
> > +#else /* !CONFIG_FTRACE_PFE_OUT_OF_LINE */
> > +#ifdef CONFIG_LIVEPATCH_64
> > + /* Load ctr with the possibly modified NIP */
>
> Comment doesn't apply to this leg of the ifdef AFAIKS.
> We load the original NIP into LR, and set CR0 for
> livepatch branch.

Indeed. Will update.

>
> > + PPC_LL r3, _NIP(r1)
> > +
> > + cmpd r14, r3 /* has NIP been altered? */
> > + bne- 1f
> > +#endif
> > +
> > + PPC_LL r3, STACK_INT_FRAME_MARKER(r1)
> > +1: mtlr r3
> > #endif
> >
> > /* Restore gprs */
> > @@ -139,7 +172,9 @@
> >
> > /* Restore possibly modified LR */
> > PPC_LL r0, _LINK(r1)
> > +#ifndef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > mtlr r0
> > +#endif
> >
> > #ifdef CONFIG_PPC64
> > /* Restore callee's TOC */
> > @@ -153,7 +188,12 @@
> > /* Based on the cmpd above, if the NIP was altered handle livepatch */
> > bne- livepatch_handler
> > #endif
> > - bctr /* jump after _mcount site */
> > + /* jump after _mcount site */
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + blr
> > +#else
> > + bctr
> > +#endif
>
> Here is the crux of it all, we return here with a blr that matches
> the return address of the bl which it was called with, so the CPU
> can predict it.
>
> I think it would be worth a comment here to explain why you go to
> so much effort to add the case that uses LR here. Because the out
> of line stub itself could pretty well have the same calling convention
> as the original mcount.

Sure. FWIW, null_syscall showed an improvement of ~22% with ftrace
enabled with this patch going down from ~520 cycles to ~400 cycles.

>
> Actually that's a thought too. Could you split this patch in two?
> First just the patch to add the out of line call but use the same
> calling convention as mprofile-kernel. Second which changes it to
> use the balanced call/return. Would that be a lot of extra work?

I'll check. My primary motive was to ensure there would only ever be two
options:
- the existing -mprofile-kernel sequence, primarily for ppc32
- the new ool sequence with a third instruction to balance the link
stack.

Though I agree splitting this makes the code easier to follow.

>
> > .endm
> >
> > _GLOBAL(ftrace_regs_caller)
> > @@ -177,6 +217,11 @@ _GLOBAL(ftrace_stub)
> >
> > #ifdef CONFIG_PPC64
> > ftrace_no_trace:
> > +#ifdef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + REST_GPR(3, r1)
> > + addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
> > + blr
> > +#else
> > mflr r3
> > mtctr r3
> > REST_GPR(3, r1)
> > @@ -184,6 +229,7 @@ ftrace_no_trace:
> > mtlr r0
> > bctr
> > #endif
> > +#endif
> >
> > #ifdef CONFIG_LIVEPATCH_64
> > /*
> > @@ -196,9 +242,9 @@ ftrace_no_trace:
> > *
> > * On entry:
> > * - we have no stack frame and can not allocate one
> > - * - LR points back to the original caller (in A)
> > - * - CTR holds the new NIP in C
> > - * - r0, r11 & r12 are free
> > + * - LR/r0 points back to the original caller (in A)
> > + * - CTR/LR holds the new NIP in C
> > + * - r11 & r12 are free
>
> Could you explain the added case here, e.g.,
>
> On entry, depending on CONFIG_FTRACE_PFE_OUT_OF_LINE (=n/=y)

Sure.

>
> > */
> > livepatch_handler:
> > ld r12, PACA_THREAD_INFO(r13)
> > @@ -208,18 +254,23 @@ livepatch_handler:
> > addi r11, r11, 24
> > std r11, TI_livepatch_sp(r12)
> >
> > - /* Save toc & real LR on livepatch stack */
> > - std r2, -24(r11)
> > - mflr r12
> > - std r12, -16(r11)
> > -
> > /* Store stack end marker */
> > lis r12, STACK_END_MAGIC@h
> > ori r12, r12, STACK_END_MAGIC@l
> > std r12, -8(r11)
> >
> > - /* Put ctr in r12 for global entry and branch there */
> > + /* Save toc & real LR on livepatch stack */
> > + std r2, -24(r11)
> > +#ifndef CONFIG_FTRACE_PFE_OUT_OF_LINE
> > + mflr r12
> > + std r12, -16(r11)
> > mfctr r12
> > +#else
> > + std r0, -16(r11)
> > + mflr r12
> > + /* Put ctr in r12 for global entry and branch there */
> > + mtctr r12
> > +#endif
> > bctrl
> >
> > /*
> > diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S
> > index f420df7888a7..0aef9959f2cd 100644
> > --- a/arch/powerpc/kernel/vmlinux.lds.S
> > +++ b/arch/powerpc/kernel/vmlinux.lds.S
> > @@ -267,14 +267,13 @@ SECTIONS
> > .init.text : AT(ADDR(.init.text) - LOAD_OFFSET) {
> > _sinittext = .;
> > INIT_TEXT
> > -
> > + *(.tramp.ftrace.init);
> > /*
> > *.init.text might be RO so we must ensure this section ends on
> > * a page boundary.
> > */
> > . = ALIGN(PAGE_SIZE);
> > _einittext = .;
> > - *(.tramp.ftrace.init);
> > } :text
> >
> > /* .exit.text is discarded at runtime, not link time,
>
> Why this change?

I should have explained in the commit log. Will add.
Without this change, core_kernel_text() test was failing in
ftrace_init_pfe_stub() I think.

>
> > diff --git a/arch/powerpc/tools/Makefile b/arch/powerpc/tools/Makefile
> > new file mode 100644
> > index 000000000000..9e2ba9a85baa
> > --- /dev/null
> > +++ b/arch/powerpc/tools/Makefile
> > @@ -0,0 +1,10 @@
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +quiet_cmd_gen_ftrace_pfe_stubs = FTRACE $@
> > + cmd_gen_ftrace_pfe_stubs = $< $(objtree)/vmlinux.o $@
> > +
> > +targets += .arch.vmlinux.o
> > +.arch.vmlinux.o: $(srctree)/arch/powerpc/tools/gen-ftrace-pfe-stubs.sh $(objtree)/vmlinux.o FORCE
> > + $(call if_changed,gen_ftrace_pfe_stubs)
> > +
> > +clean-files += $(objtree)/.arch.vmlinux.S $(objtree)/.arch.vmlinux.o
> > diff --git a/arch/powerpc/tools/gen-ftrace-pfe-stubs.sh b/arch/powerpc/tools/gen-ftrace-pfe-stubs.sh
> > new file mode 100755
> > index 000000000000..ec95e99aff14
> > --- /dev/null
> > +++ b/arch/powerpc/tools/gen-ftrace-pfe-stubs.sh
> > @@ -0,0 +1,48 @@
> > +#!/bin/sh
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +# Error out on error
> > +set -e
> > +
> > +is_enabled() {
> > + grep -q "^$1=y" include/config/auto.conf
> > +}
> > +
> > +vmlinux_o=${1}
> > +arch_vmlinux_o=${2}
> > +arch_vmlinux_S=$(dirname ${arch_vmlinux_o})/$(basename ${arch_vmlinux_o} .o).S
> > +
> > +RELOCATION=R_PPC64_ADDR64
> > +if is_enabled CONFIG_PPC32; then
> > + RELOCATION=R_PPC_ADDR32
> > +fi
>
> Started PPC32 support?

Yes, except perhaps the module code. The intent was to enable as much of
it as I could so that Christophe could try and see if this would be
useful on 32-bit. The config option is intentionally neutral.

>
> > +
> > +num_pfe_stubs_text=$(${CROSS_COMPILE}objdump -r -j __patchable_function_entries ${vmlinux_o} |
> > + grep -v ".init.text" | grep "${RELOCATION}" | wc -l)
> > +num_pfe_stubs_inittext=$(${CROSS_COMPILE}objdump -r -j __patchable_function_entries ${vmlinux_o} |
> > + grep ".init.text" | grep "${RELOCATION}" | wc -l)
> > +
> > +cat > ${arch_vmlinux_S} <<EOF
> > +#include <asm/asm-offsets.h>
> > +#include <linux/linkage.h>
> > +
> > +.pushsection .tramp.ftrace.text,"aw"
> > +SYM_DATA(ftrace_pfe_stub_text_count, .long ${num_pfe_stubs_text})
> > +
> > +SYM_CODE_START(ftrace_pfe_stub_text)
> > + .space ${num_pfe_stubs_text} * FTRACE_PFE_STUB_SIZE
> > +SYM_CODE_END(ftrace_pfe_stub_text)
> > +.popsection
> > +
> > +.pushsection .tramp.ftrace.init,"aw"
> > +SYM_DATA(ftrace_pfe_stub_inittext_count, .long ${num_pfe_stubs_inittext})
> > +
> > +SYM_CODE_START(ftrace_pfe_stub_inittext)
> > + .space ${num_pfe_stubs_inittext} * FTRACE_PFE_STUB_SIZE
> > +SYM_CODE_END(ftrace_pfe_stub_inittext)
> > +.popsection
> > +EOF
> > +
> > +${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
> > + ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
> > + -c -o ${arch_vmlinux_o} ${arch_vmlinux_S}
>
> Looking pretty good. I don't know the livepatch stuff well though.

Thanks for the detailed review!

- Naveen