alpha: split the prefetch check in do_page_fault() by mode

Before EV6, a load into $31 or $f31 could fault, so do_page_fault()
reads the faulting instruction and skips it if it is such a prefetch.
The PC can be a user or a kernel address, since the check runs for
faults from either mode, and it reads it with __get_user() either way.
That only works because alpha's __get_user() does no address check.  It
also ignores the error, relying on the fault fixup zeroing the
instruction, which does not decode as a prefetch.

Move the check into is_prefetch(), and read the instruction with
get_user() for a user-mode PC and get_kernel_nofault() for a kernel one.
A failed read now explicitly means "not a prefetch".

This is part of getting rid of __get_user() and __put_user() entirely.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2026-10-20 09:41:32 -10:00
parent 5a36c24b0e
commit 7318a05d2f

View File

@ -81,6 +81,28 @@ __load_new_mm_context(struct mm_struct *next_mm)
(((unsigned long *)regs)[(r) <= 8 ? (r) : (r) <= 15 ? (r)-17 : \
(r) <= 18 ? (r)+11 : (r)-10])
/*
* As of EV6, a load into $31/$f31 is a prefetch, and never faults
* (or is suppressed by the PALcode). Support that for older CPUs
* by ignoring such an instruction.
*/
static bool is_prefetch(struct pt_regs *regs)
{
unsigned int insn;
int err;
if (user_mode(regs))
err = get_user(insn, (unsigned int __user *)regs->pc);
else
err = get_kernel_nofault(insn, (unsigned int *)regs->pc);
if (err)
return false;
/* ldq ldl ldt lds ldg ldf ldwu ldbu */
return (insn >> 21 & 0x1f) == 0x1f &&
(1ul << (insn >> 26) & 0x30f00001400ul);
}
asmlinkage void
do_page_fault(unsigned long address, unsigned long mmcsr,
long cause, struct pt_regs *regs)
@ -92,18 +114,9 @@ do_page_fault(unsigned long address, unsigned long mmcsr,
vm_fault_t fault;
unsigned int flags = FAULT_FLAG_DEFAULT;
/* As of EV6, a load into $31/$f31 is a prefetch, and never faults
(or is suppressed by the PALcode). Support that for older CPUs
by ignoring such an instruction. */
if (cause == 0) {
unsigned int insn;
__get_user(insn, (unsigned int __user *)regs->pc);
if ((insn >> 21 & 0x1f) == 0x1f &&
/* ldq ldl ldt lds ldg ldf ldwu ldbu */
(1ul << (insn >> 26) & 0x30f00001400ul)) {
regs->pc += 4;
return;
}
if (cause == 0 && is_prefetch(regs)) {
regs->pc += 4;
return;
}
/* If we're in an interrupt context, or have no user context,