// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include const struct exception_table_entry *s390_search_extables(unsigned long addr) { const struct exception_table_entry *fixup; size_t num; fixup = search_exception_tables(addr); if (fixup) return fixup; num = __stop_amode31_ex_table - __start_amode31_ex_table; return search_extable(__start_amode31_ex_table, num, addr); } static bool ex_handler_fixup(const struct exception_table_entry *ex, struct pt_regs *regs) { regs->psw.addr = extable_fixup(ex); return true; } static bool ex_handler_ua_fault(const struct exception_table_entry *ex, struct pt_regs *regs) { unsigned int reg_err = FIELD_GET(EX_DATA_REG_ERR, ex->data); regs->gprs[reg_err] = -EFAULT; regs->psw.addr = extable_fixup(ex); return true; } static bool ex_handler_ua_load_reg(const struct exception_table_entry *ex, bool pair, struct pt_regs *regs) { unsigned int reg_zero = FIELD_GET(EX_DATA_REG_ADDR, ex->data); unsigned int reg_err = FIELD_GET(EX_DATA_REG_ERR, ex->data); regs->gprs[reg_err] = -EFAULT; regs->gprs[reg_zero] = 0; if (pair) regs->gprs[reg_zero + 1] = 0; regs->psw.addr = extable_fixup(ex); return true; } static bool ex_handler_zeropad(const struct exception_table_entry *ex, struct pt_regs *regs) { unsigned int reg_addr = FIELD_GET(EX_DATA_REG_ADDR, ex->data); unsigned int reg_data = FIELD_GET(EX_DATA_REG_ERR, ex->data); unsigned long data, addr, offset; addr = regs->gprs[reg_addr]; offset = addr & (sizeof(unsigned long) - 1); addr &= ~(sizeof(unsigned long) - 1); data = *(unsigned long *)addr; data <<= BITS_PER_BYTE * offset; regs->gprs[reg_data] = data; regs->psw.addr = extable_fixup(ex); return true; } static bool ex_handler_fpc(const struct exception_table_entry *ex, struct pt_regs *regs) { fpu_sfpc(0); regs->psw.addr = extable_fixup(ex); return true; } struct insn_ssf { u64 opc1 : 8; u64 r3 : 4; u64 opc2 : 4; u64 b1 : 4; u64 d1 : 12; u64 b2 : 4; u64 d2 : 12; } __packed; static bool ex_handler_ua_mvcos(const struct exception_table_entry *ex, bool from, struct pt_regs *regs) { unsigned long uaddr, remainder; struct insn_ssf *insn; /* * If the faulting user space access crossed a page boundary retry by * limiting the access to the first page (adjust length accordingly). * Then the mvcos instruction will either complete with condition code * zero, or generate another fault where the user space access did not * cross a page boundary. * If the faulting user space access did not cross a page boundary set * length to zero and retry. In this case no user space access will * happen, and the mvcos instruction will complete with condition code * zero. * In both cases the instruction will complete with condition code * zero (copying finished), and the register which contains the * length, indicates the number of bytes copied. */ regs->psw.addr = extable_fixup(ex); insn = (struct insn_ssf *)regs->psw.addr; if (from) uaddr = regs->gprs[insn->b2] + insn->d2; else uaddr = regs->gprs[insn->b1] + insn->d1; remainder = PAGE_SIZE - (uaddr & (PAGE_SIZE - 1)); if (regs->gprs[insn->r3] <= remainder) remainder = 0; regs->gprs[insn->r3] = remainder; return true; } bool fixup_exception(struct pt_regs *regs) { const struct exception_table_entry *ex; ex = s390_search_extables(instruction_pointer(regs)); if (!ex) return false; switch (ex->type) { case EX_TYPE_FIXUP: return ex_handler_fixup(ex, regs); case EX_TYPE_BPF: return ex_handler_bpf(ex, regs); case EX_TYPE_UA_FAULT: return ex_handler_ua_fault(ex, regs); case EX_TYPE_UA_LOAD_REG: return ex_handler_ua_load_reg(ex, false, regs); case EX_TYPE_UA_LOAD_REGPAIR: return ex_handler_ua_load_reg(ex, true, regs); case EX_TYPE_ZEROPAD: return ex_handler_zeropad(ex, regs); case EX_TYPE_FPC: return ex_handler_fpc(ex, regs); case EX_TYPE_UA_MVCOS_TO: return ex_handler_ua_mvcos(ex, false, regs); case EX_TYPE_UA_MVCOS_FROM: return ex_handler_ua_mvcos(ex, true, regs); } panic("invalid exception table entry"); }