Skip to content

Commit 01eb018

Browse files
npigginmpe
authored andcommitted
powerpc/64s: Fix restore_math unnecessarily changing MSR
Before returning to user, if there are missing FP/VEC/VSX bits from the user MSR then those registers had been saved and must be restored again before use. restore_math will decide whether to restore immediately, or skip the restore and let fp/vec/vsx unavailable faults demand load the registers. Each time restore_math restores one of the FP/VSX or VEC register sets is loaded, an 8-bit counter is incremented (load_fp and load_vec). When these wrap to zero, restore_math no longer restores that register set until after they are next demand faulted. It's quite usual for those counters to have different values, so if one wraps to zero and restore_math no longer restores its registers or user MSR bit but the other is not zero yet does not need to be restored (because the kernel is not frequently using the FPU), then restore_math will be called and it will also not return in the early exit check. This causes msr_check_and_set to test and set the MSR at every kernel exit despite having no work to do. This can cause workloads (e.g., a NULL syscall microbenchmark) to run fast for a time while both counters are non-zero, then slow down when one of the counters reaches zero, then speed up again after the second counter reaches zero. The cost is significant, about 10% slowdown on a NULL syscall benchmark, and the jittery behaviour is very undesirable. Fix this by having restore_math test all conditions first, and only update MSR if we will be loading registers. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20200623234139.2262227-2-npiggin@gmail.com
1 parent 891b4fe commit 01eb018

File tree

2 files changed

+68
-40
lines changed

2 files changed

+68
-40
lines changed

arch/powerpc/kernel/process.c

+60-40
Original file line numberDiff line numberDiff line change
@@ -471,49 +471,58 @@ EXPORT_SYMBOL(giveup_all);
471471

472472
#ifdef CONFIG_PPC_BOOK3S_64
473473
#ifdef CONFIG_PPC_FPU
474-
static int restore_fp(struct task_struct *tsk)
474+
static bool should_restore_fp(void)
475475
{
476-
if (tsk->thread.load_fp) {
477-
load_fp_state(&current->thread.fp_state);
476+
if (current->thread.load_fp) {
478477
current->thread.load_fp++;
479-
return 1;
478+
return true;
480479
}
481-
return 0;
480+
return false;
481+
}
482+
483+
static void do_restore_fp(void)
484+
{
485+
load_fp_state(&current->thread.fp_state);
482486
}
483487
#else
484-
static int restore_fp(struct task_struct *tsk) { return 0; }
488+
static bool should_restore_fp(void) { return false; }
489+
static void do_restore_fp(void) { }
485490
#endif /* CONFIG_PPC_FPU */
486491

487492
#ifdef CONFIG_ALTIVEC
488-
#define loadvec(thr) ((thr).load_vec)
489-
static int restore_altivec(struct task_struct *tsk)
493+
static bool should_restore_altivec(void)
490494
{
491-
if (cpu_has_feature(CPU_FTR_ALTIVEC) && (tsk->thread.load_vec)) {
492-
load_vr_state(&tsk->thread.vr_state);
493-
tsk->thread.used_vr = 1;
494-
tsk->thread.load_vec++;
495-
496-
return 1;
495+
if (cpu_has_feature(CPU_FTR_ALTIVEC) && (current->thread.load_vec)) {
496+
current->thread.load_vec++;
497+
return true;
497498
}
498-
return 0;
499+
return false;
500+
}
501+
502+
static void do_restore_altivec(void)
503+
{
504+
load_vr_state(&current->thread.vr_state);
505+
current->thread.used_vr = 1;
499506
}
500507
#else
501-
#define loadvec(thr) 0
502-
static inline int restore_altivec(struct task_struct *tsk) { return 0; }
508+
static bool should_restore_altivec(void) { return false; }
509+
static void do_restore_altivec(void) { }
503510
#endif /* CONFIG_ALTIVEC */
504511

505512
#ifdef CONFIG_VSX
506-
static int restore_vsx(struct task_struct *tsk)
513+
static bool should_restore_vsx(void)
507514
{
508-
if (cpu_has_feature(CPU_FTR_VSX)) {
509-
tsk->thread.used_vsr = 1;
510-
return 1;
511-
}
512-
513-
return 0;
515+
if (cpu_has_feature(CPU_FTR_VSX))
516+
return true;
517+
return false;
518+
}
519+
static void do_restore_vsx(void)
520+
{
521+
current->thread.used_vsr = 1;
514522
}
515523
#else
516-
static inline int restore_vsx(struct task_struct *tsk) { return 0; }
524+
static bool should_restore_vsx(void) { return false; }
525+
static void do_restore_vsx(void) { }
517526
#endif /* CONFIG_VSX */
518527

519528
/*
@@ -529,31 +538,42 @@ static inline int restore_vsx(struct task_struct *tsk) { return 0; }
529538
void notrace restore_math(struct pt_regs *regs)
530539
{
531540
unsigned long msr;
532-
533-
if (!current->thread.load_fp && !loadvec(current->thread))
534-
return;
541+
unsigned long new_msr = 0;
535542

536543
msr = regs->msr;
537-
msr_check_and_set(msr_all_available);
538544

539545
/*
540-
* Only reload if the bit is not set in the user MSR, the bit BEING set
541-
* indicates that the registers are hot
546+
* new_msr tracks the facilities that are to be restored. Only reload
547+
* if the bit is not set in the user MSR (if it is set, the registers
548+
* are live for the user thread).
542549
*/
543-
if ((!(msr & MSR_FP)) && restore_fp(current))
544-
msr |= MSR_FP | current->thread.fpexc_mode;
550+
if ((!(msr & MSR_FP)) && should_restore_fp())
551+
new_msr |= MSR_FP | current->thread.fpexc_mode;
545552

546-
if ((!(msr & MSR_VEC)) && restore_altivec(current))
547-
msr |= MSR_VEC;
553+
if ((!(msr & MSR_VEC)) && should_restore_altivec())
554+
new_msr |= MSR_VEC;
548555

549-
if ((msr & (MSR_FP | MSR_VEC)) == (MSR_FP | MSR_VEC) &&
550-
restore_vsx(current)) {
551-
msr |= MSR_VSX;
556+
if ((!(msr & MSR_VSX)) && should_restore_vsx()) {
557+
if (((msr | new_msr) & (MSR_FP | MSR_VEC)) == (MSR_FP | MSR_VEC))
558+
new_msr |= MSR_VSX;
552559
}
553560

554-
msr_check_and_clear(msr_all_available);
561+
if (new_msr) {
562+
msr_check_and_set(new_msr);
563+
564+
if (new_msr & MSR_FP)
565+
do_restore_fp();
566+
567+
if (new_msr & MSR_VEC)
568+
do_restore_altivec();
555569

556-
regs->msr = msr;
570+
if (new_msr & MSR_VSX)
571+
do_restore_vsx();
572+
573+
msr_check_and_clear(new_msr);
574+
575+
regs->msr |= new_msr;
576+
}
557577
}
558578
#endif
559579

arch/powerpc/kernel/syscall_64.c

+8
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3,
206206
else if (cpu_has_feature(CPU_FTR_ALTIVEC))
207207
mathflags |= MSR_VEC;
208208

209+
/*
210+
* If userspace MSR has all available FP bits set,
211+
* then they are live and no need to restore. If not,
212+
* it means the regs were given up and restore_math
213+
* may decide to restore them (to avoid taking an FP
214+
* fault).
215+
*/
209216
if ((regs->msr & mathflags) != mathflags)
210217
restore_math(regs);
211218
}
@@ -277,6 +284,7 @@ notrace unsigned long interrupt_exit_user_prepare(struct pt_regs *regs, unsigned
277284
else if (cpu_has_feature(CPU_FTR_ALTIVEC))
278285
mathflags |= MSR_VEC;
279286

287+
/* See above restore_math comment */
280288
if ((regs->msr & mathflags) != mathflags)
281289
restore_math(regs);
282290
}

0 commit comments

Comments
 (0)