Re: [PATCH v2 12/12] s390/percpu: Rework to simplify percpu_entry() and percpu_exit()

Mark Rutland <[email protected]>
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.linux-s390
Message-ID <aq0VFSS_81QTKjcj@J2N7QTR9R3>
On Fri, Sep 18, 2026 at 10:48:42AM +0200, Heiko Carstens wrote:
> The percpu code section functionality uses a rather complex method to
> figure out if the register, which contains the address of the current
> cpu's percpu variable, needs to be adjusted.
> 
> If an interrupt happens within a percpu code section (indicated by a
> lowcore field), the instruction at the interrupted location is
> checked. If it is not a specific AG instruction, the register needs to
> be updated. This mechanism needs to take kprobes into account, and
> enforces a specific instruction ordering.
> 
> Mark Rutland provided with a different solution for arm64 [1] which
> comes without such limitations, but requires to use one more
> instruction, and two more registers. Given that this simplifies
> percpu_entry() and percpu_exit() it seems to be worth to go that
> route.
> 
> Change s390 to implement a similar approach. This requires to encode
> three register numbers into the "percpu_register" field, which is used
> to indicate if a percpu code section is executed.
> 
> The used mviy instruction can write only one byte, which allows to
> encode only two register numbers. Use a register pair for the inline
> assemblies, and only encode the even register number of the register
> pair to work around this.

Neat trick!

I have one minor comment below, but this looks good to me regardless.

> -static __always_inline void percpu_exit(struct pt_regs *regs, bool needs_fixup)
> +static __always_inline void percpu_exit(struct pt_regs *regs)
>  {
> +	unsigned char regval, regpcp, regoff, regptr;
>  	struct lowcore *lc = get_lowcore();
> -	unsigned char reg;
>  
> -	if (user_mode(regs))
> +	if (!regs->percpu_register)
>  		return;
> -	reg = regs->percpu_register;
> -	lc->percpu_register = reg;
> -	if (likely(!needs_fixup))
> -		return;
> -	/* Check if process has been migrated to a different CPU. */
> +	regval = regs->percpu_register;
> +	lc->percpu_register = regval;
> +	/* Migrated to a different CPU? */
>  	if (regs->cpu == lc->cpu_nr)
>  		return;
> -	/* Fixup percpu base register */
> -	regs->gprs[reg] -= __per_cpu_offset[regs->cpu];
> -	regs->gprs[reg] += lc->percpu_offset;
> +	regpcp = FIELD_GET(PCPU_REG_PCP, regval);
> +	regoff = FIELD_GET(PCPU_REG_OFF, regval);
> +	regptr = regoff + 1;
> +	/*
> +	 * Update register 'regoff' which contains the current CPU's percpu
> +	 * offset, and recalculate and update current CPU's percpu variable
> +	 * address contained in register 'regptr'.
> +	 */
> +	regs->gprs[regoff] = lc->percpu_offset;
> +	regs->gprs[regptr] = regs->gprs[regpcp] + regs->gprs[regoff];
>  }

> +#define PCPU_REG_PCP_SHIFT		0
> +#define PCPU_REG_PCP			GENMASK(3, 0)
> +#define PCPU_REG_OFF_SHIFT		4
> +#define PCPU_REG_OFF			GENMASK(7, 4)
> +
>  #define __PCPU_MVIY(lcreg, imm)							\
>  	ALTERNATIVE("	mviy	" lcreg			"(%%r0)," imm "\n",	\
>  		    "	mviy	" lcreg "+" LC_ALT_ADDR "(%%r0)," imm "\n",	\
>  		    ALT_FEATURE(MFEATURE_LOWCORE))
>  
> -#define __PCPU_AG(reg, lcoff)							\
> -	ALTERNATIVE("	ag	" reg ", " lcoff		 "(%%r0)\n",	\
> -		    "	ag	" reg ", " lcoff "+" LC_ALT_ADDR "(%%r0)\n",	\

> +#define __PCPU_MVIY_REGS(lcreg, regpcp, regoff)					\
> +	DEFINE_GR_NUM								\
> +	"_GR_NUM .Lregpcp, " regpcp "\n"					\
> +	"_GR_NUM .Lregoff, " regoff "\n"					\
> +	UNDEF_GR_NUM								\
> +	".if .Lregpcp == .Lregoff\n"						\
> +	"	.error \"Registers must not be identical\"\n"			\
> +	".endif\n"								\
> +	".set .Lregval, ((.Lregpcp << " __stringify(PCPU_REG_PCP_SHIFT) ") |"	\
> +	"		 (.Lregoff << " __stringify(PCPU_REG_OFF_SHIFT) "))\n"	\
> +	__PCPU_MVIY(lcreg, ".Lregval")
> +
> +#define __PCPU_LG(regoff, lcoff)						\
> +	ALTERNATIVE("lg	" regoff ", " lcoff		    "(%%r0)\n",		\
> +		    "lg	" regoff ", " lcoff "+" LC_ALT_ADDR "(%%r0)\n",		\
>  		    ALT_FEATURE(MFEATURE_LOWCORE))
>  
> -#define __PCPU_BEGIN(lcreg, lcoff, reg)						\
> -	DEFINE_GR_NUM								\
> -	"_GR_NUM .Lreg, " reg "\n"						\
> -	UNDEF_GR_NUM								\
> -	__PCPU_MVIY(lcreg, ".Lreg")						\
> -	__PCPU_AG(reg, lcoff)
> +#define __PCPU_AGRK(regptr, regpcp, regoff)					\
> +	"	agrk	" regptr "," regpcp "," regoff "\n"
> +
> +#define __PCPU_BEGIN(lcreg, lcoff, regpcp, regoff, regptr)			\
> +	__PCPU_MVIY_REGS(lcreg, regpcp, regoff)					\
> +	__PCPU_LG(regoff, lcoff)						\
> +	__PCPU_AGRK(regptr, regpcp, regoff)

Since percpu_exit() relies on regptr being regoff + 1, maybe it's worth
having a check here to verify that? Perhaps in __PCPU_MVIY_REGS() with
the uniqueness check.

Hopefully that never goes wrong, but catching a violation at build time
might save some future pain.

Mark.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.