Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for s390-specific library files..
#
# string.o implements standard library functions like memset/memcpy etc.
# Use -ffreestanding to ensure that the compiler does not try to "optimize"
# them into calls to themselves.
CFLAGS_string.o = -ffreestanding
lib-y += delay.o string.o uaccess.o find.o spinlock.o tishift.o
lib-y += csum-partial.o
lib-$(CONFIG_KPROBES) += probes.o
lib-$(CONFIG_UPROBES) += probes.o
obj-$(CONFIG_S390_KPROBES_SANITY_TEST) += test_kprobes_s390.o
test_kprobes_s390-objs += test_kprobes_asm.o test_kprobes.o
# Instrumenting memory accesses to __user data (in different address space)
# produce false positives
KASAN_SANITIZE_uaccess.o := n
obj-$(CONFIG_S390_UNWIND_SELFTEST) += test_unwind.o
CFLAGS_test_unwind.o += -fno-optimize-sibling-calls
obj-$(CONFIG_S390_MODULES_SANITY_TEST) += test_modules.o
obj-$(CONFIG_S390_MODULES_SANITY_TEST_HELPERS) += test_modules_helpers.o
lib-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
obj-$(CONFIG_EXPOLINE_EXTERN) += expoline.o
+91
View File
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/export.h>
#include <asm/checksum.h>
#include <asm/fpu.h>
/*
* Computes the checksum of a memory block at src, length len,
* and adds in "sum" (32-bit). If copy is true copies to dst.
*
* Returns a 32-bit number suitable for feeding into itself
* or csum_tcpudp_magic.
*
* This function must be called with even lengths, except
* for the last fragment, which may be odd.
*
* It's best to have src and dst aligned on a 64-bit boundary.
*/
static __always_inline __wsum csum_copy(void *dst, const void *src, int len, __wsum sum, bool copy)
{
DECLARE_KERNEL_FPU_ONSTACK8(vxstate);
if (!cpu_has_vx()) {
if (copy)
memcpy(dst, src, len);
return cksm(src, len, sum);
}
kernel_fpu_begin(&vxstate, KERNEL_VXR_V16V23);
fpu_vlvgf(16, (__force u32)sum, 1);
fpu_vzero(17);
fpu_vzero(18);
fpu_vzero(19);
while (len >= 64) {
fpu_vlm(20, 23, src);
if (copy) {
fpu_vstm(20, 23, dst);
dst += 64;
}
fpu_vcksm(16, 20, 16);
fpu_vcksm(17, 21, 17);
fpu_vcksm(18, 22, 18);
fpu_vcksm(19, 23, 19);
src += 64;
len -= 64;
}
while (len >= 32) {
fpu_vlm(20, 21, src);
if (copy) {
fpu_vstm(20, 21, dst);
dst += 32;
}
fpu_vcksm(16, 20, 16);
fpu_vcksm(17, 21, 17);
src += 32;
len -= 32;
}
while (len >= 16) {
fpu_vl(20, src);
if (copy) {
fpu_vst(20, dst);
dst += 16;
}
fpu_vcksm(16, 20, 16);
src += 16;
len -= 16;
}
if (len) {
fpu_vll(20, len - 1, src);
if (copy)
fpu_vstl(20, len - 1, dst);
fpu_vcksm(16, 20, 16);
}
fpu_vcksm(18, 19, 18);
fpu_vcksm(16, 17, 16);
fpu_vcksm(16, 18, 16);
sum = (__force __wsum)fpu_vlgvf(16, 1);
kernel_fpu_end(&vxstate, KERNEL_VXR_V16V23);
return sum;
}
__wsum csum_partial(const void *buff, int len, __wsum sum)
{
return csum_copy(NULL, buff, len, sum, false);
}
EXPORT_SYMBOL(csum_partial);
__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len)
{
return csum_copy(dst, src, len, 0, true);
}
EXPORT_SYMBOL(csum_partial_copy_nocheck);
+46
View File
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Precise Delay Loops for S390
*
* Copyright IBM Corp. 1999, 2008
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>,
*/
#include <linux/processor.h>
#include <linux/export.h>
#include <linux/delay.h>
#include <asm/div64.h>
#include <asm/timex.h>
void __delay(unsigned long loops)
{
/*
* Loop 'loops' times. Callers must not assume a specific
* amount of time passes before this function returns.
*/
asm volatile("0: brct %0,0b" : : "d" ((loops/2) + 1));
}
EXPORT_SYMBOL(__delay);
static void delay_loop(unsigned long delta)
{
unsigned long end;
end = get_tod_clock_monotonic() + delta;
while (!tod_after(get_tod_clock_monotonic(), end))
cpu_relax();
}
void __udelay(unsigned long usecs)
{
delay_loop(usecs << 12);
}
EXPORT_SYMBOL(__udelay);
void __ndelay(unsigned long nsecs)
{
nsecs <<= 9;
do_div(nsecs, 125);
delay_loop(nsecs);
}
EXPORT_SYMBOL(__ndelay);
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0+
#include <asm/ptrace.h>
#include <linux/error-injection.h>
#include <linux/kprobes.h>
void override_function_with_return(struct pt_regs *regs)
{
/*
* Emulate 'br 14'. 'regs' is captured by kprobes on entry to some
* kernel function.
*/
regs->psw.addr = regs->gprs[14];
}
NOKPROBE_SYMBOL(override_function_with_return);
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/nospec-insn.h>
#include <linux/linkage.h>
.macro GEN_ALL_BR_THUNK_EXTERN
.irp r1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
GEN_BR_THUNK_EXTERN %r\r1
.endr
.endm
GEN_ALL_BR_THUNK_EXTERN
+76
View File
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0
/*
* MSB0 numbered special bitops handling.
*
* The bits are numbered:
* |0..............63|64............127|128...........191|192...........255|
*
* The reason for this bit numbering is the fact that the hardware sets bits
* in a bitmap starting at bit 0 (MSB) and we don't want to scan the bitmap
* from the 'wrong end'.
*/
#include <linux/compiler.h>
#include <linux/bitops.h>
#include <linux/export.h>
unsigned long find_first_bit_inv(const unsigned long *addr, unsigned long size)
{
const unsigned long *p = addr;
unsigned long result = 0;
unsigned long tmp;
while (size & ~(BITS_PER_LONG - 1)) {
if ((tmp = *(p++)))
goto found;
result += BITS_PER_LONG;
size -= BITS_PER_LONG;
}
if (!size)
return result;
tmp = (*p) & (~0UL << (BITS_PER_LONG - size));
if (!tmp) /* Are any bits set? */
return result + size; /* Nope. */
found:
return result + (__fls(tmp) ^ (BITS_PER_LONG - 1));
}
EXPORT_SYMBOL(find_first_bit_inv);
unsigned long find_next_bit_inv(const unsigned long *addr, unsigned long size,
unsigned long offset)
{
const unsigned long *p = addr + (offset / BITS_PER_LONG);
unsigned long result = offset & ~(BITS_PER_LONG - 1);
unsigned long tmp;
if (offset >= size)
return size;
size -= result;
offset %= BITS_PER_LONG;
if (offset) {
tmp = *(p++);
tmp &= (~0UL >> offset);
if (size < BITS_PER_LONG)
goto found_first;
if (tmp)
goto found_middle;
size -= BITS_PER_LONG;
result += BITS_PER_LONG;
}
while (size & ~(BITS_PER_LONG-1)) {
if ((tmp = *(p++)))
goto found_middle;
result += BITS_PER_LONG;
size -= BITS_PER_LONG;
}
if (!size)
return result;
tmp = *p;
found_first:
tmp &= (~0UL << (BITS_PER_LONG - size));
if (!tmp) /* Are any bits set? */
return result + size; /* Nope. */
found_middle:
return result + (__fls(tmp) ^ (BITS_PER_LONG - 1));
}
EXPORT_SYMBOL(find_next_bit_inv);
+161
View File
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Common helper functions for kprobes and uprobes
*
* Copyright IBM Corp. 2014
*/
#include <linux/errno.h>
#include <asm/kprobes.h>
#include <asm/dis.h>
int probe_is_prohibited_opcode(u16 *insn)
{
if (!is_known_insn((unsigned char *)insn))
return -EINVAL;
switch (insn[0] >> 8) {
case 0x0c: /* bassm */
case 0x0b: /* bsm */
case 0x83: /* diag */
case 0x44: /* ex */
case 0xac: /* stnsm */
case 0xad: /* stosm */
return -EINVAL;
case 0xc6:
switch (insn[0] & 0x0f) {
case 0x00: /* exrl */
return -EINVAL;
}
}
switch (insn[0]) {
case 0x0101: /* pr */
case 0xb25a: /* bsa */
case 0xb240: /* bakr */
case 0xb258: /* bsg */
case 0xb218: /* pc */
case 0xb228: /* pt */
case 0xb98d: /* epsw */
case 0xe560: /* tbegin */
case 0xe561: /* tbeginc */
case 0xb2f8: /* tend */
return -EINVAL;
}
return 0;
}
int probe_get_fixup_type(u16 *insn)
{
/* default fixup method */
int fixup = FIXUP_PSW_NORMAL;
switch (insn[0] >> 8) {
case 0x05: /* balr */
case 0x0d: /* basr */
fixup = FIXUP_RETURN_REGISTER;
/* if r2 = 0, no branch will be taken */
if ((insn[0] & 0x0f) == 0)
fixup |= FIXUP_BRANCH_NOT_TAKEN;
break;
case 0x06: /* bctr */
case 0x07: /* bcr */
fixup = FIXUP_BRANCH_NOT_TAKEN;
break;
case 0x45: /* bal */
case 0x4d: /* bas */
fixup = FIXUP_RETURN_REGISTER;
break;
case 0x47: /* bc */
case 0x46: /* bct */
case 0x86: /* bxh */
case 0x87: /* bxle */
fixup = FIXUP_BRANCH_NOT_TAKEN;
break;
case 0x82: /* lpsw */
fixup = FIXUP_NOT_REQUIRED;
break;
case 0xb2: /* lpswe */
if ((insn[0] & 0xff) == 0xb2)
fixup = FIXUP_NOT_REQUIRED;
break;
case 0xa7: /* bras */
if ((insn[0] & 0x0f) == 0x05)
fixup |= FIXUP_RETURN_REGISTER;
break;
case 0xc0:
if ((insn[0] & 0x0f) == 0x05) /* brasl */
fixup |= FIXUP_RETURN_REGISTER;
break;
case 0xeb:
switch (insn[2] & 0xff) {
case 0x44: /* bxhg */
case 0x45: /* bxleg */
fixup = FIXUP_BRANCH_NOT_TAKEN;
break;
}
break;
case 0xe3: /* bctg */
if ((insn[2] & 0xff) == 0x46)
fixup = FIXUP_BRANCH_NOT_TAKEN;
break;
case 0xec:
switch (insn[2] & 0xff) {
case 0xe5: /* clgrb */
case 0xe6: /* cgrb */
case 0xf6: /* crb */
case 0xf7: /* clrb */
case 0xfc: /* cgib */
case 0xfd: /* cglib */
case 0xfe: /* cib */
case 0xff: /* clib */
fixup = FIXUP_BRANCH_NOT_TAKEN;
break;
}
break;
}
return fixup;
}
int probe_is_insn_relative_long(u16 *insn)
{
/* Check if we have a RIL-b or RIL-c format instruction which
* we need to modify in order to avoid instruction emulation. */
switch (insn[0] >> 8) {
case 0xc0:
if ((insn[0] & 0x0f) == 0x00) /* larl */
return true;
break;
case 0xc4:
switch (insn[0] & 0x0f) {
case 0x02: /* llhrl */
case 0x04: /* lghrl */
case 0x05: /* lhrl */
case 0x06: /* llghrl */
case 0x07: /* sthrl */
case 0x08: /* lgrl */
case 0x0b: /* stgrl */
case 0x0c: /* lgfrl */
case 0x0d: /* lrl */
case 0x0e: /* llgfrl */
case 0x0f: /* strl */
return true;
}
break;
case 0xc6:
switch (insn[0] & 0x0f) {
case 0x02: /* pfdrl */
case 0x04: /* cghrl */
case 0x05: /* chrl */
case 0x06: /* clghrl */
case 0x07: /* clhrl */
case 0x08: /* cgrl */
case 0x0a: /* clgrl */
case 0x0c: /* cgfrl */
case 0x0d: /* crl */
case 0x0e: /* clgfrl */
case 0x0f: /* clrl */
return true;
}
break;
}
return false;
}
+363
View File
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Out of line spinlock code.
*
* Copyright IBM Corp. 2004, 2006
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
*/
#include <linux/types.h>
#include <linux/export.h>
#include <linux/spinlock.h>
#include <linux/jiffies.h>
#include <linux/sysctl.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/percpu.h>
#include <linux/io.h>
#include <asm/alternative.h>
#include <asm/machine.h>
#include <asm/asm.h>
int spin_retry = -1;
static int __init spin_retry_init(void)
{
if (spin_retry < 0)
spin_retry = 1000;
return 0;
}
early_initcall(spin_retry_init);
/*
* spin_retry= parameter
*/
static int __init spin_retry_setup(char *str)
{
spin_retry = simple_strtoul(str, &str, 0);
return 1;
}
__setup("spin_retry=", spin_retry_setup);
static const struct ctl_table s390_spin_sysctl_table[] = {
{
.procname = "spin_retry",
.data = &spin_retry,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
};
static int __init init_s390_spin_sysctls(void)
{
register_sysctl_init("kernel", s390_spin_sysctl_table);
return 0;
}
arch_initcall(init_s390_spin_sysctls);
struct spin_wait {
struct spin_wait *next, *prev;
int node_id;
} __aligned(32);
static DEFINE_PER_CPU_ALIGNED(struct spin_wait, spin_wait[4]);
#define _Q_LOCK_CPU_OFFSET 0
#define _Q_LOCK_STEAL_OFFSET 16
#define _Q_TAIL_IDX_OFFSET 18
#define _Q_TAIL_CPU_OFFSET 20
#define _Q_LOCK_CPU_MASK 0x0000ffff
#define _Q_LOCK_STEAL_ADD 0x00010000
#define _Q_LOCK_STEAL_MASK 0x00030000
#define _Q_TAIL_IDX_MASK 0x000c0000
#define _Q_TAIL_CPU_MASK 0xfff00000
#define _Q_LOCK_MASK (_Q_LOCK_CPU_MASK | _Q_LOCK_STEAL_MASK)
#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)
void arch_spin_lock_setup(int cpu)
{
struct spin_wait *node;
int ix;
node = per_cpu_ptr(&spin_wait[0], cpu);
for (ix = 0; ix < 4; ix++, node++) {
memset(node, 0, sizeof(*node));
node->node_id = ((cpu + 1) << _Q_TAIL_CPU_OFFSET) +
(ix << _Q_TAIL_IDX_OFFSET);
}
}
static inline int arch_load_niai4(int *lock)
{
int owner;
asm_inline volatile(
ALTERNATIVE("nop", ".insn rre,0xb2fa0000,4,0", ALT_FACILITY(49)) /* NIAI 4 */
" l %[owner],%[lock]"
: [owner] "=d" (owner) : [lock] "R" (*lock) : "memory");
return owner;
}
#ifdef __HAVE_ASM_FLAG_OUTPUTS__
static inline int arch_try_cmpxchg_niai8(int *lock, int old, int new)
{
int cc;
asm_inline volatile(
ALTERNATIVE("nop", ".insn rre,0xb2fa0000,8,0", ALT_FACILITY(49)) /* NIAI 8 */
" cs %[old],%[new],%[lock]"
: [old] "+d" (old), [lock] "+Q" (*lock), "=@cc" (cc)
: [new] "d" (new)
: "memory");
return cc == 0;
}
#else /* __HAVE_ASM_FLAG_OUTPUTS__ */
static inline int arch_try_cmpxchg_niai8(int *lock, int old, int new)
{
int expected = old;
asm_inline volatile(
ALTERNATIVE("nop", ".insn rre,0xb2fa0000,8,0", ALT_FACILITY(49)) /* NIAI 8 */
" cs %[old],%[new],%[lock]"
: [old] "+d" (old), [lock] "+Q" (*lock)
: [new] "d" (new)
: "cc", "memory");
return expected == old;
}
#endif /* __HAVE_ASM_FLAG_OUTPUTS__ */
static inline struct spin_wait *arch_spin_decode_tail(int lock)
{
int ix, cpu;
ix = (lock & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
cpu = (lock & _Q_TAIL_CPU_MASK) >> _Q_TAIL_CPU_OFFSET;
return per_cpu_ptr(&spin_wait[ix], cpu - 1);
}
static inline int arch_spin_yield_target(int lock, struct spin_wait *node)
{
if (lock & _Q_LOCK_CPU_MASK)
return lock & _Q_LOCK_CPU_MASK;
if (node == NULL || node->prev == NULL)
return 0; /* 0 -> no target cpu */
while (node->prev)
node = node->prev;
return node->node_id >> _Q_TAIL_CPU_OFFSET;
}
static inline void arch_spin_lock_queued(arch_spinlock_t *lp)
{
struct spin_wait *node, *next;
int lockval, ix, node_id, tail_id, old, new, owner, count;
ix = get_lowcore()->spinlock_index++;
barrier();
lockval = spinlock_lockval(); /* cpu + 1 */
node = this_cpu_ptr(&spin_wait[ix]);
node->prev = node->next = NULL;
node_id = node->node_id;
/* Enqueue the node for this CPU in the spinlock wait queue */
old = READ_ONCE(lp->lock);
while (1) {
if ((old & _Q_LOCK_CPU_MASK) == 0 &&
(old & _Q_LOCK_STEAL_MASK) != _Q_LOCK_STEAL_MASK) {
/*
* The lock is free but there may be waiters.
* With no waiters simply take the lock, if there
* are waiters try to steal the lock. The lock may
* be stolen three times before the next queued
* waiter will get the lock.
*/
new = (old ? (old + _Q_LOCK_STEAL_ADD) : 0) | lockval;
if (arch_try_cmpxchg(&lp->lock, &old, new))
/* Got the lock */
goto out;
/* lock passing in progress */
continue;
}
/* Make the node of this CPU the new tail. */
new = node_id | (old & _Q_LOCK_MASK);
if (arch_try_cmpxchg(&lp->lock, &old, new))
break;
}
/* Set the 'next' pointer of the tail node in the queue */
tail_id = old & _Q_TAIL_MASK;
if (tail_id != 0) {
node->prev = arch_spin_decode_tail(tail_id);
WRITE_ONCE(node->prev->next, node);
}
/* Pass the virtual CPU to the lock holder if it is not running */
owner = arch_spin_yield_target(old, node);
if (owner && arch_vcpu_is_preempted(owner - 1))
smp_yield_cpu(owner - 1);
/* Spin on the CPU local node->prev pointer */
if (tail_id != 0) {
count = spin_retry;
while (READ_ONCE(node->prev) != NULL) {
if (count-- >= 0)
continue;
count = spin_retry;
/* Query running state of lock holder again. */
owner = arch_spin_yield_target(old, node);
if (owner && arch_vcpu_is_preempted(owner - 1))
smp_yield_cpu(owner - 1);
}
}
/* Spin on the lock value in the spinlock_t */
count = spin_retry;
while (1) {
old = READ_ONCE(lp->lock);
owner = old & _Q_LOCK_CPU_MASK;
if (!owner) {
tail_id = old & _Q_TAIL_MASK;
new = ((tail_id != node_id) ? tail_id : 0) | lockval;
if (arch_try_cmpxchg(&lp->lock, &old, new))
/* Got the lock */
break;
continue;
}
if (count-- >= 0)
continue;
count = spin_retry;
if (!machine_is_lpar() || arch_vcpu_is_preempted(owner - 1))
smp_yield_cpu(owner - 1);
}
/* Pass lock_spin job to next CPU in the queue */
if (node_id && tail_id != node_id) {
/* Wait until the next CPU has set up the 'next' pointer */
while ((next = READ_ONCE(node->next)) == NULL)
;
next->prev = NULL;
}
out:
get_lowcore()->spinlock_index--;
}
static inline void arch_spin_lock_classic(arch_spinlock_t *lp)
{
int lockval, old, new, owner, count;
lockval = spinlock_lockval(); /* cpu + 1 */
/* Pass the virtual CPU to the lock holder if it is not running */
owner = arch_spin_yield_target(READ_ONCE(lp->lock), NULL);
if (owner && arch_vcpu_is_preempted(owner - 1))
smp_yield_cpu(owner - 1);
count = spin_retry;
while (1) {
old = arch_load_niai4(&lp->lock);
owner = old & _Q_LOCK_CPU_MASK;
/* Try to get the lock if it is free. */
if (!owner) {
new = (old & _Q_TAIL_MASK) | lockval;
if (arch_try_cmpxchg_niai8(&lp->lock, old, new)) {
/* Got the lock */
return;
}
continue;
}
if (count-- >= 0)
continue;
count = spin_retry;
if (!machine_is_lpar() || arch_vcpu_is_preempted(owner - 1))
smp_yield_cpu(owner - 1);
}
}
void arch_spin_lock_wait(arch_spinlock_t *lp)
{
if (test_cpu_flag(CIF_DEDICATED_CPU))
arch_spin_lock_queued(lp);
else
arch_spin_lock_classic(lp);
}
EXPORT_SYMBOL(arch_spin_lock_wait);
int arch_spin_trylock_retry(arch_spinlock_t *lp)
{
int cpu = spinlock_lockval();
int owner, count;
for (count = spin_retry; count > 0; count--) {
owner = READ_ONCE(lp->lock);
/* Try to get the lock if it is free. */
if (!owner) {
if (arch_try_cmpxchg(&lp->lock, &owner, cpu))
return 1;
}
}
return 0;
}
EXPORT_SYMBOL(arch_spin_trylock_retry);
void arch_read_lock_wait(arch_rwlock_t *rw)
{
if (unlikely(in_interrupt())) {
while (READ_ONCE(rw->cnts) & 0x10000)
barrier();
return;
}
/* Remove this reader again to allow recursive read locking */
__atomic_add_const(-1, &rw->cnts);
/* Put the reader into the wait queue */
arch_spin_lock(&rw->wait);
/* Now add this reader to the count value again */
__atomic_add_const(1, &rw->cnts);
/* Loop until the writer is done */
while (READ_ONCE(rw->cnts) & 0x10000)
barrier();
arch_spin_unlock(&rw->wait);
}
EXPORT_SYMBOL(arch_read_lock_wait);
void arch_write_lock_wait(arch_rwlock_t *rw)
{
int old;
/* Add this CPU to the write waiters */
__atomic_add(0x20000, &rw->cnts);
/* Put the writer into the wait queue */
arch_spin_lock(&rw->wait);
while (1) {
old = READ_ONCE(rw->cnts);
if ((old & 0x1ffff) == 0 &&
arch_try_cmpxchg(&rw->cnts, &old, old | 0x10000))
/* Got the lock */
break;
barrier();
}
arch_spin_unlock(&rw->wait);
}
EXPORT_SYMBOL(arch_write_lock_wait);
void arch_spin_relax(arch_spinlock_t *lp)
{
int cpu;
cpu = READ_ONCE(lp->lock) & _Q_LOCK_CPU_MASK;
if (!cpu)
return;
if (machine_is_lpar() && !arch_vcpu_is_preempted(cpu - 1))
return;
smp_yield_cpu(cpu - 1);
}
EXPORT_SYMBOL(arch_spin_relax);
+485
View File
@@ -0,0 +1,485 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Optimized string functions
*
* S390 version
* Copyright IBM Corp. 2004
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
*/
#define IN_ARCH_STRING_C 1
#ifndef __NO_FORTIFY
# define __NO_FORTIFY
#endif
#include <linux/types.h>
#include <linux/string.h>
#include <linux/export.h>
#include <asm/facility.h>
#include <asm/asm.h>
#define SYMBOL_FUNCTION_ALIAS(alias, name) \
asm(".globl " __stringify(alias) "\n\t" \
".set " __stringify(alias) "," __stringify(name))
#ifdef __HAVE_ARCH_MEMMOVE
noinstr void *__memmove(void *dest, const void *src, size_t n)
{
const char *s = src;
char *d = dest;
if (!n)
return dest;
if ((d <= s || d >= s + n)) {
/* Forward copy */
while (n >= 256) {
asm volatile(
" mvc 0(256,%[d]),0(%[s])\n"
:
: [d] "a" (d), [s] "a" (s)
: "memory");
d += 256;
s += 256;
n -= 256;
}
if (n) {
asm volatile(
" exrl %[n],0f\n"
" j 1f\n"
"0: mvc 0(1,%[d]),0(%[s])\n"
"1:"
:
: [d] "a" (d), [s] "a" (s), [n] "a" (n - 1)
: "memory");
}
return dest;
}
/* Backward copy */
if (test_facility(61)) {
/* Use mvcrl instruction if available */
while (n >= 256) {
asm volatile(
" lghi %%r0,255\n"
" .insn sse,0xe50a00000000,%[d],%[s]\n"
: [d] "=Q" (*(d + n - 256))
: [s] "Q" (*(s + n - 256))
: "0", "memory");
n -= 256;
}
if (n) {
asm volatile(
" lgr %%r0,%[n]\n"
" .insn sse,0xe50a00000000,%[d],%[s]\n"
: [d] "=Q" (*d)
: [s] "Q" (*s), [n] "d" (n - 1)
: "0", "memory");
}
} else {
while (n--)
d[n] = s[n];
}
return dest;
}
SYMBOL_FUNCTION_ALIAS(memmove, __memmove);
EXPORT_SYMBOL(__memmove);
EXPORT_SYMBOL(memmove);
#endif
#ifdef __HAVE_ARCH_MEMSET
noinstr void *__memset(void *s, int c, size_t n)
{
char *xs = s;
if (!n)
return s;
if (!c) {
/* Clear memory */
while (n >= 256) {
asm volatile(
" xc 0(256,%[xs]),0(%[xs])"
:
: [xs] "a" (xs)
: "cc", "memory");
xs += 256;
n -= 256;
}
if (!n)
return s;
asm volatile(
" exrl %[n],0f\n"
" j 1f\n"
"0: xc 0(1,%[xs]),0(%[xs])\n"
"1:"
:
: [xs] "a" (xs), [n] "a" (n - 1)
: "cc", "memory");
} else {
/* Fill memory */
while (n >= 256) {
*xs = c;
asm volatile(
" mvc 1(255,%[xs]),0(%[xs])"
:
: [xs] "a" (xs)
: "memory");
xs += 256;
n -= 256;
}
if (!n)
return s;
*xs = c;
if (n == 1)
return s;
asm volatile(
" exrl %[n],0f\n"
" j 1f\n"
"0: mvc 1(1,%[xs]),0(%[xs])\n"
"1:"
:
: [xs] "a" (xs), [n] "a" (n - 2)
: "memory");
}
return s;
}
SYMBOL_FUNCTION_ALIAS(memset, __memset);
EXPORT_SYMBOL(__memset);
EXPORT_SYMBOL(memset);
#endif
#ifdef __HAVE_ARCH_MEMCPY
noinstr void *__memcpy(void *dest, const void *src, size_t n)
{
void *d = dest;
if (!n)
return d;
while (n >= 256) {
asm volatile(
" mvc 0(256,%[dest]),0(%[src])"
:
: [dest] "a" (dest), [src] "a" (src)
: "memory");
dest += 256;
src += 256;
n -= 256;
}
if (!n)
return d;
asm volatile(
" exrl %[n],1f\n"
" j 2f\n"
"1: mvc 0(1,%[dest]),0(%[src])\n"
"2:"
:
: [dest] "a" (dest), [src] "a" (src), [n] "a" (n - 1)
: "memory");
return d;
}
SYMBOL_FUNCTION_ALIAS(memcpy, __memcpy);
EXPORT_SYMBOL(__memcpy);
EXPORT_SYMBOL(memcpy);
#endif
#define DEFINE_MEMSET(_bits, _bytes, _type) \
void *__memset##_bits(_type *s, _type v, size_t n) \
{ \
_type *xs = s; \
\
if (!n) \
return s; \
while (n >= 256) { \
*xs = v; \
asm volatile( \
" mvc %[_b](256-%[_b],%[xs]),0(%[xs])\n" \
: \
: [xs] "a" (xs), [_b] "i" (_bytes) \
: "memory"); \
xs = (_type *)((char *)xs + 256); \
n -= 256; \
} \
if (!n) \
return s; \
*xs = v; \
if (n == _bytes) \
return s; \
n -= _bytes + 1; \
asm volatile( \
" exrl %[n],1f\n" \
" j 2f\n" \
"1: mvc %[_b](1,%[xs]),0(%[xs])\n" \
"2:" \
: \
: [n] "a" (n), [xs] "a" (xs), [_b] "i" (_bytes) \
: "memory"); \
return s; \
} \
EXPORT_SYMBOL(__memset##_bits)
#ifdef __HAVE_ARCH_MEMSET16
DEFINE_MEMSET(16, 2, uint16_t);
#endif
#ifdef __HAVE_ARCH_MEMSET32
DEFINE_MEMSET(32, 4, uint32_t);
#endif
#ifdef __HAVE_ARCH_MEMSET64
DEFINE_MEMSET(64, 8, uint64_t);
#endif
/*
* Helper functions to find the end of a string
*/
static inline char *__strend(const char *s)
{
unsigned long e = 0;
asm volatile(
" lghi 0,0\n"
"0: srst %[e],%[s]\n"
" jo 0b"
: [e] "+&a" (e), [s] "+&a" (s)
:
: "cc", "memory", "0");
return (char *)e;
}
static inline char *__strnend(const char *s, size_t n)
{
const char *p = s + n;
asm volatile(
" lghi 0,0\n"
"0: srst %[p],%[s]\n"
" jo 0b"
: [p] "+&d" (p), [s] "+&a" (s)
:
: "cc", "memory", "0");
return (char *)p;
}
/**
* strlen - Find the length of a string
* @s: The string to be sized
*
* returns the length of @s
*/
#ifdef __HAVE_ARCH_STRLEN
size_t strlen(const char *s)
{
return __strend(s) - s;
}
EXPORT_SYMBOL(strlen);
#endif
/**
* strnlen - Find the length of a length-limited string
* @s: The string to be sized
* @n: The maximum number of bytes to search
*
* returns the minimum of the length of @s and @n
*/
#ifdef __HAVE_ARCH_STRNLEN
size_t strnlen(const char *s, size_t n)
{
return __strnend(s, n) - s;
}
EXPORT_SYMBOL(strnlen);
#endif
/**
* strcat - Append one %NUL-terminated string to another
* @dest: The string to be appended to
* @src: The string to append to it
*
* returns a pointer to @dest
*/
#ifdef __HAVE_ARCH_STRCAT
char *strcat(char *dest, const char *src)
{
unsigned long dummy = 0;
char *ret = dest;
asm volatile(
" lghi 0,0\n"
"0: srst %[dummy],%[dest]\n"
" jo 0b\n"
"1: mvst %[dummy],%[src]\n"
" jo 1b"
: [dummy] "+&a" (dummy), [dest] "+&a" (dest), [src] "+&a" (src)
:
: "cc", "memory", "0");
return ret;
}
EXPORT_SYMBOL(strcat);
#endif
/**
* strncat - Append a length-limited, %NUL-terminated string to another
* @dest: The string to be appended to
* @src: The string to append to it
* @n: The maximum numbers of bytes to copy
*
* returns a pointer to @dest
*/
#ifdef __HAVE_ARCH_STRNCAT
char *strncat(char *dest, const char *src, size_t n)
{
size_t len = __strnend(src, n) - src;
char *p = __strend(dest);
p[len] = '\0';
memcpy(p, src, len);
return dest;
}
EXPORT_SYMBOL(strncat);
#endif
/**
* strcmp - Compare two strings
* @s1: One string
* @s2: Another string
*
* returns 0 if @s1 and @s2 are equal,
* < 0 if @s1 is less than @s2
* > 0 if @s1 is greater than @s2
*/
#ifdef __HAVE_ARCH_STRCMP
int strcmp(const char *s1, const char *s2)
{
int ret = 0;
asm volatile(
" lghi 0,0\n"
"0: clst %[s1],%[s2]\n"
" jo 0b\n"
" je 1f\n"
" ic %[ret],0(%[s1])\n"
" ic 0,0(%[s2])\n"
" sr %[ret],0\n"
"1:"
: [ret] "+&d" (ret), [s1] "+&a" (s1), [s2] "+&a" (s2)
:
: "cc", "memory", "0");
return ret;
}
EXPORT_SYMBOL(strcmp);
#endif
static inline int clcle(const char *s1, unsigned long l1,
const char *s2, unsigned long l2)
{
union register_pair r1 = { .even = (unsigned long)s1, .odd = l1, };
union register_pair r3 = { .even = (unsigned long)s2, .odd = l2, };
int cc;
asm volatile(
"0: clcle %[r1],%[r3],0\n"
" jo 0b\n"
CC_IPM(cc)
: CC_OUT(cc, cc), [r1] "+d" (r1.pair), [r3] "+d" (r3.pair)
:
: CC_CLOBBER_LIST("memory"));
return CC_TRANSFORM(cc);
}
/**
* strstr - Find the first substring in a %NUL terminated string
* @s1: The string to be searched
* @s2: The string to search for
*/
#ifdef __HAVE_ARCH_STRSTR
char *strstr(const char *s1, const char *s2)
{
int l1, l2;
l2 = __strend(s2) - s2;
if (!l2)
return (char *) s1;
l1 = __strend(s1) - s1;
while (l1-- >= l2) {
int cc;
cc = clcle(s1, l2, s2, l2);
if (!cc)
return (char *) s1;
s1++;
}
return NULL;
}
EXPORT_SYMBOL(strstr);
#endif
/**
* memchr - Find a character in an area of memory.
* @s: The memory area
* @c: The byte to search for
* @n: The size of the area.
*
* returns the address of the first occurrence of @c, or %NULL
* if @c is not found
*/
#ifdef __HAVE_ARCH_MEMCHR
void *memchr(const void *s, int c, size_t n)
{
const void *ret = s + n;
asm volatile(
" lgr 0,%[c]\n"
"0: srst %[ret],%[s]\n"
" jo 0b\n"
" jl 1f\n"
" la %[ret],0\n"
"1:"
: [ret] "+&a" (ret), [s] "+&a" (s)
: [c] "d" (c)
: "cc", "memory", "0");
return (void *) ret;
}
EXPORT_SYMBOL(memchr);
#endif
/**
* memcmp - Compare two areas of memory
* @s1: One area of memory
* @s2: Another area of memory
* @n: The size of the area.
*/
#ifdef __HAVE_ARCH_MEMCMP
int memcmp(const void *s1, const void *s2, size_t n)
{
int ret;
ret = clcle(s1, n, s2, n);
if (ret)
ret = ret == 1 ? -1 : 1;
return ret;
}
EXPORT_SYMBOL(memcmp);
#endif
/**
* memscan - Find a character in an area of memory.
* @s: The memory area
* @c: The byte to search for
* @n: The size of the area.
*
* returns the address of the first occurrence of @c, or 1 byte past
* the area if @c is not found
*/
#ifdef __HAVE_ARCH_MEMSCAN
void *memscan(void *s, int c, size_t n)
{
const void *ret = s + n;
asm volatile(
" lgr 0,%[c]\n"
"0: srst %[ret],%[s]\n"
" jo 0b"
: [ret] "+&a" (ret), [s] "+&a" (s)
: [c] "d" (c)
: "cc", "memory", "0");
return (void *)ret;
}
EXPORT_SYMBOL(memscan);
#endif
+76
View File
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/random.h>
#include <kunit/test.h>
#include "test_kprobes.h"
static struct kprobe kp;
static void setup_kprobe(struct kunit *test, struct kprobe *kp,
const char *symbol, int offset)
{
kp->offset = offset;
kp->addr = NULL;
kp->symbol_name = symbol;
}
static void test_kprobe_offset(struct kunit *test, struct kprobe *kp,
const char *target, int offset)
{
int ret;
setup_kprobe(test, kp, target, 0);
ret = register_kprobe(kp);
if (!ret)
unregister_kprobe(kp);
KUNIT_EXPECT_EQ(test, 0, ret);
setup_kprobe(test, kp, target, offset);
ret = register_kprobe(kp);
KUNIT_EXPECT_EQ(test, -EINVAL, ret);
if (!ret)
unregister_kprobe(kp);
}
static void test_kprobe_odd(struct kunit *test)
{
test_kprobe_offset(test, &kp, "kprobes_target_odd",
kprobes_target_odd_offs);
}
static void test_kprobe_in_insn4(struct kunit *test)
{
test_kprobe_offset(test, &kp, "kprobes_target_in_insn4",
kprobes_target_in_insn4_offs);
}
static void test_kprobe_in_insn6_lo(struct kunit *test)
{
test_kprobe_offset(test, &kp, "kprobes_target_in_insn6_lo",
kprobes_target_in_insn6_lo_offs);
}
static void test_kprobe_in_insn6_hi(struct kunit *test)
{
test_kprobe_offset(test, &kp, "kprobes_target_in_insn6_hi",
kprobes_target_in_insn6_hi_offs);
}
static struct kunit_case kprobes_testcases[] = {
KUNIT_CASE(test_kprobe_odd),
KUNIT_CASE(test_kprobe_in_insn4),
KUNIT_CASE(test_kprobe_in_insn6_lo),
KUNIT_CASE(test_kprobe_in_insn6_hi),
{}
};
static struct kunit_suite kprobes_test_suite = {
.name = "kprobes_test_s390",
.test_cases = kprobes_testcases,
};
kunit_test_suites(&kprobes_test_suite);
MODULE_DESCRIPTION("KUnit tests for kprobes");
MODULE_LICENSE("GPL");
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef TEST_KPROBES_H
#define TEST_KPROBES_H
extern unsigned long kprobes_target_odd_offs;
extern unsigned long kprobes_target_in_insn4_offs;
extern unsigned long kprobes_target_in_insn6_lo_offs;
extern unsigned long kprobes_target_in_insn6_hi_offs;
#endif
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#include <linux/linkage.h>
#include <asm/ftrace.h>
#define KPROBES_TARGET_START(name) \
SYM_FUNC_START(name); \
FTRACE_GEN_NOP_ASM(name)
#define KPROBES_TARGET_END(name) \
SYM_FUNC_END(name); \
SYM_DATA(name##_offs, .quad 1b - name)
KPROBES_TARGET_START(kprobes_target_in_insn4)
.word 0x4700 // bc 0,0
1: .word 0x0000
br %r14
KPROBES_TARGET_END(kprobes_target_in_insn4)
KPROBES_TARGET_START(kprobes_target_in_insn6_lo)
.word 0xe310 // ly 1,0
1: .word 0x0000
.word 0x0058
br %r14
KPROBES_TARGET_END(kprobes_target_in_insn6_lo)
KPROBES_TARGET_START(kprobes_target_in_insn6_hi)
.word 0xe310 // ly 1,0
.word 0x0000
1: .word 0x0058
br %r14
KPROBES_TARGET_END(kprobes_target_in_insn6_hi)
KPROBES_TARGET_START(kprobes_target_bp)
nop
.word 0x0000
nop
1: br %r14
KPROBES_TARGET_END(kprobes_target_bp)
KPROBES_TARGET_START(kprobes_target_odd)
.byte 0x07
1: .byte 0x07
br %r14
KPROBES_TARGET_END(kprobes_target_odd)
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0+
#include <kunit/test.h>
#include <linux/module.h>
#include "test_modules.h"
/*
* Test that modules with many relocations are loaded properly.
*/
static void test_modules_many_vmlinux_relocs(struct kunit *test)
{
int result = 0;
#define CALL_RETURN(i) result += test_modules_return_ ## i()
REPEAT_10000(CALL_RETURN);
KUNIT_ASSERT_EQ(test, result, 49995000);
}
static struct kunit_case modules_testcases[] = {
KUNIT_CASE(test_modules_many_vmlinux_relocs),
{}
};
static struct kunit_suite modules_test_suite = {
.name = "modules_test_s390",
.test_cases = modules_testcases,
};
kunit_test_suites(&modules_test_suite);
MODULE_DESCRIPTION("KUnit test that modules with many relocations are loaded properly");
MODULE_LICENSE("GPL");
+53
View File
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef TEST_MODULES_H
#define TEST_MODULES_H
#define __REPEAT_10000_3(f, x) \
f(x ## 0); \
f(x ## 1); \
f(x ## 2); \
f(x ## 3); \
f(x ## 4); \
f(x ## 5); \
f(x ## 6); \
f(x ## 7); \
f(x ## 8); \
f(x ## 9)
#define __REPEAT_10000_2(f, x) \
__REPEAT_10000_3(f, x ## 0); \
__REPEAT_10000_3(f, x ## 1); \
__REPEAT_10000_3(f, x ## 2); \
__REPEAT_10000_3(f, x ## 3); \
__REPEAT_10000_3(f, x ## 4); \
__REPEAT_10000_3(f, x ## 5); \
__REPEAT_10000_3(f, x ## 6); \
__REPEAT_10000_3(f, x ## 7); \
__REPEAT_10000_3(f, x ## 8); \
__REPEAT_10000_3(f, x ## 9)
#define __REPEAT_10000_1(f, x) \
__REPEAT_10000_2(f, x ## 0); \
__REPEAT_10000_2(f, x ## 1); \
__REPEAT_10000_2(f, x ## 2); \
__REPEAT_10000_2(f, x ## 3); \
__REPEAT_10000_2(f, x ## 4); \
__REPEAT_10000_2(f, x ## 5); \
__REPEAT_10000_2(f, x ## 6); \
__REPEAT_10000_2(f, x ## 7); \
__REPEAT_10000_2(f, x ## 8); \
__REPEAT_10000_2(f, x ## 9)
#define REPEAT_10000(f) \
__REPEAT_10000_1(f, 0); \
__REPEAT_10000_1(f, 1); \
__REPEAT_10000_1(f, 2); \
__REPEAT_10000_1(f, 3); \
__REPEAT_10000_1(f, 4); \
__REPEAT_10000_1(f, 5); \
__REPEAT_10000_1(f, 6); \
__REPEAT_10000_1(f, 7); \
__REPEAT_10000_1(f, 8); \
__REPEAT_10000_1(f, 9)
#define DECLARE_RETURN(i) int test_modules_return_ ## i(void)
REPEAT_10000(DECLARE_RETURN);
#endif
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/export.h>
#include "test_modules.h"
#define DEFINE_RETURN(i) \
int test_modules_return_ ## i(void) \
{ \
return 1 ## i - 10000; \
} \
EXPORT_SYMBOL_GPL(test_modules_return_ ## i)
REPEAT_10000(DEFINE_RETURN);
+523
View File
@@ -0,0 +1,523 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test module for unwind_for_each_frame
*/
#include <kunit/test.h>
#include <asm/unwind.h>
#include <linux/completion.h>
#include <linux/kallsyms.h>
#include <linux/kthread.h>
#include <linux/ftrace.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/kprobes.h>
#include <linux/wait.h>
#include <asm/irq.h>
static struct kunit *current_test;
#define BT_BUF_SIZE (PAGE_SIZE * 4)
static bool force_bt;
module_param_named(backtrace, force_bt, bool, 0444);
MODULE_PARM_DESC(backtrace, "print backtraces for all tests");
/*
* To avoid printk line limit split backtrace by lines
*/
static void print_backtrace(char *bt)
{
char *p;
while (true) {
p = strsep(&bt, "\n");
if (!p)
break;
kunit_err(current_test, "%s\n", p);
}
}
/*
* Calls unwind_for_each_frame(task, regs, sp) and verifies that the result
* contains unwindme_func2 followed by unwindme_func1.
*/
static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs,
unsigned long sp)
{
int frame_count, prev_is_func2, seen_func2_func1, seen_arch_rethook_trampoline;
const int max_frames = 128;
struct unwind_state state;
size_t bt_pos = 0;
int ret = 0;
char *bt;
bt = kmalloc(BT_BUF_SIZE, GFP_ATOMIC);
if (!bt) {
kunit_err(current_test, "failed to allocate backtrace buffer\n");
return -ENOMEM;
}
/* Unwind. */
frame_count = 0;
prev_is_func2 = 0;
seen_func2_func1 = 0;
seen_arch_rethook_trampoline = 0;
unwind_for_each_frame(&state, task, regs, sp) {
unsigned long addr = unwind_get_return_address(&state);
char sym[KSYM_SYMBOL_LEN];
if (frame_count++ == max_frames)
break;
if (state.reliable && !addr) {
kunit_err(current_test, "unwind state reliable but addr is 0\n");
ret = -EINVAL;
break;
}
sprint_symbol(sym, addr);
if (bt_pos < BT_BUF_SIZE) {
bt_pos += snprintf(bt + bt_pos, BT_BUF_SIZE - bt_pos,
state.reliable ? " [%-7s%px] %pSR\n" :
"([%-7s%px] %pSR)\n",
stack_type_name(state.stack_info.type),
(void *)state.sp, (void *)state.ip);
if (bt_pos >= BT_BUF_SIZE)
kunit_err(current_test, "backtrace buffer is too small\n");
}
frame_count += 1;
if (prev_is_func2 && str_has_prefix(sym, "unwindme_func1"))
seen_func2_func1 = 1;
prev_is_func2 = str_has_prefix(sym, "unwindme_func2");
if (str_has_prefix(sym, "arch_rethook_trampoline+0x0/"))
seen_arch_rethook_trampoline = 1;
}
/* Check the results. */
if (unwind_error(&state)) {
kunit_err(current_test, "unwind error\n");
ret = -EINVAL;
}
if (!seen_func2_func1) {
kunit_err(current_test, "unwindme_func2 and unwindme_func1 not found\n");
ret = -EINVAL;
}
if (frame_count == max_frames) {
kunit_err(current_test, "Maximum number of frames exceeded\n");
ret = -EINVAL;
}
if (seen_arch_rethook_trampoline) {
kunit_err(current_test, "arch_rethook_trampoline+0x0 in unwinding results\n");
ret = -EINVAL;
}
if (ret || force_bt)
print_backtrace(bt);
kfree(bt);
return ret;
}
/* State of the task being unwound. */
struct unwindme {
int flags;
int ret;
struct task_struct *task;
struct completion task_ready;
wait_queue_head_t task_wq;
unsigned long sp;
};
static struct unwindme *unwindme;
/* Values of unwindme.flags. */
#define UWM_DEFAULT 0x0
#define UWM_THREAD 0x1 /* Unwind a separate task. */
#define UWM_REGS 0x2 /* Pass regs to test_unwind(). */
#define UWM_SP 0x4 /* Pass sp to test_unwind(). */
#define UWM_CALLER 0x8 /* Unwind starting from caller. */
#define UWM_SWITCH_STACK 0x10 /* Use call_on_stack. */
#define UWM_IRQ 0x20 /* Unwind from irq context. */
#define UWM_PGM 0x40 /* Unwind from program check handler */
#define UWM_KPROBE_ON_FTRACE 0x80 /* Unwind from kprobe handler called via ftrace. */
#define UWM_FTRACE 0x100 /* Unwind from ftrace handler. */
#define UWM_KRETPROBE 0x200 /* Unwind through kretprobed function. */
#define UWM_KRETPROBE_HANDLER 0x400 /* Unwind from kretprobe handler. */
static __always_inline struct pt_regs fake_pt_regs(void)
{
struct pt_regs regs;
memset(&regs, 0, sizeof(regs));
regs.gprs[15] = current_stack_pointer;
asm volatile(
"basr %[psw_addr],0"
: [psw_addr] "=d" (regs.psw.addr));
return regs;
}
static int kretprobe_ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct unwindme *u = unwindme;
if (!(u->flags & UWM_KRETPROBE_HANDLER))
return 0;
u->ret = test_unwind(NULL, (u->flags & UWM_REGS) ? regs : NULL,
(u->flags & UWM_SP) ? u->sp : 0);
return 0;
}
static noinline notrace int test_unwind_kretprobed_func(struct unwindme *u)
{
struct pt_regs regs;
if (!(u->flags & UWM_KRETPROBE))
return 0;
regs = fake_pt_regs();
return test_unwind(NULL, (u->flags & UWM_REGS) ? &regs : NULL,
(u->flags & UWM_SP) ? u->sp : 0);
}
static noinline int test_unwind_kretprobed_func_caller(struct unwindme *u)
{
return test_unwind_kretprobed_func(u);
}
static int test_unwind_kretprobe(struct unwindme *u)
{
int ret;
struct kretprobe my_kretprobe;
if (!IS_ENABLED(CONFIG_KPROBES))
kunit_skip(current_test, "requires CONFIG_KPROBES");
u->ret = -1; /* make sure kprobe is called */
unwindme = u;
memset(&my_kretprobe, 0, sizeof(my_kretprobe));
my_kretprobe.handler = kretprobe_ret_handler;
my_kretprobe.maxactive = 1;
my_kretprobe.kp.addr = (kprobe_opcode_t *)test_unwind_kretprobed_func;
ret = register_kretprobe(&my_kretprobe);
if (ret < 0) {
kunit_err(current_test, "register_kretprobe failed %d\n", ret);
return -EINVAL;
}
ret = test_unwind_kretprobed_func_caller(u);
unregister_kretprobe(&my_kretprobe);
unwindme = NULL;
if (u->flags & UWM_KRETPROBE_HANDLER)
ret = u->ret;
return ret;
}
static int kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
struct unwindme *u = unwindme;
u->ret = test_unwind(NULL, (u->flags & UWM_REGS) ? regs : NULL,
(u->flags & UWM_SP) ? u->sp : 0);
return 0;
}
extern const char test_unwind_kprobed_insn[];
static noinline void test_unwind_kprobed_func(void)
{
asm volatile(
" nopr %%r7\n"
"test_unwind_kprobed_insn:\n"
" nopr %%r7"
:);
}
static int test_unwind_kprobe(struct unwindme *u)
{
struct kprobe kp;
int ret;
if (!IS_ENABLED(CONFIG_KPROBES))
kunit_skip(current_test, "requires CONFIG_KPROBES");
if (!IS_ENABLED(CONFIG_KPROBES_ON_FTRACE) && u->flags & UWM_KPROBE_ON_FTRACE)
kunit_skip(current_test, "requires CONFIG_KPROBES_ON_FTRACE");
u->ret = -1; /* make sure kprobe is called */
unwindme = u;
memset(&kp, 0, sizeof(kp));
kp.pre_handler = kprobe_pre_handler;
kp.addr = u->flags & UWM_KPROBE_ON_FTRACE ?
(kprobe_opcode_t *)test_unwind_kprobed_func :
(kprobe_opcode_t *)test_unwind_kprobed_insn;
ret = register_kprobe(&kp);
if (ret < 0) {
kunit_err(current_test, "register_kprobe failed %d\n", ret);
return -EINVAL;
}
test_unwind_kprobed_func();
unregister_kprobe(&kp);
unwindme = NULL;
return u->ret;
}
static void notrace __used test_unwind_ftrace_handler(unsigned long ip,
unsigned long parent_ip,
struct ftrace_ops *fops,
struct ftrace_regs *fregs)
{
struct unwindme *u = (struct unwindme *)arch_ftrace_regs(fregs)->regs.gprs[2];
u->ret = test_unwind(NULL, (u->flags & UWM_REGS) ? &arch_ftrace_regs(fregs)->regs : NULL,
(u->flags & UWM_SP) ? u->sp : 0);
}
static noinline int test_unwind_ftraced_func(struct unwindme *u)
{
return READ_ONCE(u)->ret;
}
static int test_unwind_ftrace(struct unwindme *u)
{
int ret;
#ifdef CONFIG_DYNAMIC_FTRACE
struct ftrace_ops *fops;
fops = kunit_kzalloc(current_test, sizeof(*fops), GFP_KERNEL);
fops->func = test_unwind_ftrace_handler;
fops->flags = FTRACE_OPS_FL_DYNAMIC |
FTRACE_OPS_FL_RECURSION |
FTRACE_OPS_FL_SAVE_REGS |
FTRACE_OPS_FL_PERMANENT;
#else
kunit_skip(current_test, "requires CONFIG_DYNAMIC_FTRACE");
#endif
ret = ftrace_set_filter_ip(fops, (unsigned long)test_unwind_ftraced_func, 0, 0);
if (ret) {
kunit_err(current_test, "failed to set ftrace filter (%d)\n", ret);
return -1;
}
ret = register_ftrace_function(fops);
if (!ret) {
ret = test_unwind_ftraced_func(u);
unregister_ftrace_function(fops);
} else {
kunit_err(current_test, "failed to register ftrace handler (%d)\n", ret);
}
ftrace_set_filter_ip(fops, (unsigned long)test_unwind_ftraced_func, 1, 0);
return ret;
}
/* This function may or may not appear in the backtrace. */
static noinline int unwindme_func4(struct unwindme *u)
{
if (!(u->flags & UWM_CALLER))
u->sp = current_frame_address();
if (u->flags & UWM_THREAD) {
complete(&u->task_ready);
wait_event(u->task_wq, kthread_should_park());
kthread_parkme();
return 0;
} else if (u->flags & (UWM_PGM | UWM_KPROBE_ON_FTRACE)) {
return test_unwind_kprobe(u);
} else if (u->flags & (UWM_KRETPROBE | UWM_KRETPROBE_HANDLER)) {
return test_unwind_kretprobe(u);
} else if (u->flags & UWM_FTRACE) {
return test_unwind_ftrace(u);
} else {
struct pt_regs regs = fake_pt_regs();
return test_unwind(NULL,
(u->flags & UWM_REGS) ? &regs : NULL,
(u->flags & UWM_SP) ? u->sp : 0);
}
}
/* This function may or may not appear in the backtrace. */
static noinline int unwindme_func3(struct unwindme *u)
{
u->sp = current_frame_address();
return unwindme_func4(u);
}
/* This function must appear in the backtrace. */
static noinline int unwindme_func2(struct unwindme *u)
{
unsigned long flags, mflags;
int rc;
if (u->flags & UWM_SWITCH_STACK) {
local_irq_save(flags);
local_mcck_save(mflags);
rc = call_on_stack(1, get_lowcore()->nodat_stack,
int, unwindme_func3, struct unwindme *, u);
local_mcck_restore(mflags);
local_irq_restore(flags);
return rc;
} else {
return unwindme_func3(u);
}
}
/* This function must follow unwindme_func2 in the backtrace. */
static noinline int unwindme_func1(void *u)
{
return unwindme_func2((struct unwindme *)u);
}
static void unwindme_timer_fn(struct timer_list *unused)
{
struct unwindme *u = READ_ONCE(unwindme);
if (u) {
unwindme = NULL;
u->task = NULL;
u->ret = unwindme_func1(u);
complete(&u->task_ready);
}
}
static struct timer_list unwind_timer;
static int test_unwind_irq(struct unwindme *u)
{
unwindme = u;
init_completion(&u->task_ready);
timer_setup(&unwind_timer, unwindme_timer_fn, 0);
mod_timer(&unwind_timer, jiffies + 1);
wait_for_completion(&u->task_ready);
return u->ret;
}
/* Spawns a task and passes it to test_unwind(). */
static int test_unwind_task(struct unwindme *u)
{
struct task_struct *task;
int ret;
/* Initialize thread-related fields. */
init_completion(&u->task_ready);
init_waitqueue_head(&u->task_wq);
/*
* Start the task and wait until it reaches unwindme_func4() and sleeps
* in (task_ready, unwind_done] range.
*/
task = kthread_run(unwindme_func1, u, "%s", __func__);
if (IS_ERR(task)) {
kunit_err(current_test, "kthread_run() failed\n");
return PTR_ERR(task);
}
/*
* Make sure task reaches unwindme_func4 before parking it,
* we might park it before kthread function has been executed otherwise
*/
wait_for_completion(&u->task_ready);
kthread_park(task);
/* Unwind. */
ret = test_unwind(task, NULL, (u->flags & UWM_SP) ? u->sp : 0);
kthread_stop(task);
return ret;
}
struct test_params {
int flags;
char *name;
};
/*
* Create required parameter list for tests
*/
#define TEST_WITH_FLAGS(f) { .flags = f, .name = #f }
static const struct test_params param_list[] = {
TEST_WITH_FLAGS(UWM_DEFAULT),
TEST_WITH_FLAGS(UWM_SP),
TEST_WITH_FLAGS(UWM_REGS),
TEST_WITH_FLAGS(UWM_SWITCH_STACK),
TEST_WITH_FLAGS(UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_CALLER | UWM_SP),
TEST_WITH_FLAGS(UWM_CALLER | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_CALLER | UWM_SP | UWM_REGS | UWM_SWITCH_STACK),
TEST_WITH_FLAGS(UWM_THREAD),
TEST_WITH_FLAGS(UWM_THREAD | UWM_SP),
TEST_WITH_FLAGS(UWM_THREAD | UWM_CALLER | UWM_SP),
TEST_WITH_FLAGS(UWM_IRQ),
TEST_WITH_FLAGS(UWM_IRQ | UWM_SWITCH_STACK),
TEST_WITH_FLAGS(UWM_IRQ | UWM_SP),
TEST_WITH_FLAGS(UWM_IRQ | UWM_REGS),
TEST_WITH_FLAGS(UWM_IRQ | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_IRQ | UWM_CALLER | UWM_SP),
TEST_WITH_FLAGS(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_IRQ | UWM_CALLER | UWM_SP | UWM_REGS | UWM_SWITCH_STACK),
TEST_WITH_FLAGS(UWM_PGM),
TEST_WITH_FLAGS(UWM_PGM | UWM_SP),
TEST_WITH_FLAGS(UWM_PGM | UWM_REGS),
TEST_WITH_FLAGS(UWM_PGM | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE),
TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_SP),
TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_REGS),
TEST_WITH_FLAGS(UWM_KPROBE_ON_FTRACE | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_FTRACE),
TEST_WITH_FLAGS(UWM_FTRACE | UWM_SP),
TEST_WITH_FLAGS(UWM_FTRACE | UWM_REGS),
TEST_WITH_FLAGS(UWM_FTRACE | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_KRETPROBE),
TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_SP),
TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_REGS),
TEST_WITH_FLAGS(UWM_KRETPROBE | UWM_SP | UWM_REGS),
TEST_WITH_FLAGS(UWM_KRETPROBE_HANDLER),
TEST_WITH_FLAGS(UWM_KRETPROBE_HANDLER | UWM_SP),
TEST_WITH_FLAGS(UWM_KRETPROBE_HANDLER | UWM_REGS),
TEST_WITH_FLAGS(UWM_KRETPROBE_HANDLER | UWM_SP | UWM_REGS),
};
/*
* Parameter description generator: required for KUNIT_ARRAY_PARAM()
*/
static void get_desc(const struct test_params *params, char *desc)
{
strscpy(desc, params->name, KUNIT_PARAM_DESC_SIZE);
}
/*
* Create test_unwind_gen_params
*/
KUNIT_ARRAY_PARAM(test_unwind, param_list, get_desc);
static void test_unwind_flags(struct kunit *test)
{
struct unwindme u;
const struct test_params *params;
current_test = test;
params = (const struct test_params *)test->param_value;
u.flags = params->flags;
if (u.flags & UWM_THREAD)
KUNIT_EXPECT_EQ(test, 0, test_unwind_task(&u));
else if (u.flags & UWM_IRQ)
KUNIT_EXPECT_EQ(test, 0, test_unwind_irq(&u));
else
KUNIT_EXPECT_EQ(test, 0, unwindme_func1(&u));
}
static struct kunit_case unwind_test_cases[] = {
KUNIT_CASE_PARAM(test_unwind_flags, test_unwind_gen_params),
{}
};
static struct kunit_suite test_unwind_suite = {
.name = "test_unwind",
.test_cases = unwind_test_cases,
};
kunit_test_suites(&test_unwind_suite);
MODULE_DESCRIPTION("KUnit test for unwind_for_each_frame");
MODULE_LICENSE("GPL");
+64
View File
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/export.h>
#include <linux/types.h>
#include "tishift.h"
union ti {
__int128_t val;
struct {
u64 high;
u64 low;
};
};
noinstr __int128_t __ashlti3(__int128_t a, int shift)
{
union ti ti = { .val = a };
if (!shift)
return ti.val;
if (shift < 64) {
ti.high = (ti.high << shift) | (ti.low >> (64 - shift));
ti.low = ti.low << shift;
} else {
ti.high = ti.low << (shift - 64);
ti.low = 0;
}
return ti.val;
}
EXPORT_SYMBOL(__ashlti3);
noinstr __int128_t __ashrti3(__int128_t a, int shift)
{
union ti ti = { .val = a };
if (!shift)
return ti.val;
if (shift < 64) {
ti.low = (ti.low >> shift) | (ti.high << (64 - shift));
ti.high = (int64_t)ti.high >> shift;
} else {
ti.low = (int64_t)ti.high >> (shift - 64);
ti.high = (int64_t)ti.high >> 63;
}
return ti.val;
}
EXPORT_SYMBOL(__ashrti3);
noinstr __int128_t __lshrti3(__int128_t a, int shift)
{
union ti ti = { .val = a };
if (!shift)
return ti.val;
if (shift < 64) {
ti.low = (ti.low >> shift) | (ti.high << (64 - shift));
ti.high = ti.high >> shift;
} else {
ti.low = ti.high >> (shift - 64);
ti.high = 0;
}
return ti.val;
}
EXPORT_SYMBOL(__lshrti3);
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _S390_LIB_TISHIFT_H
#define _S390_LIB_TISHIFT_H
__int128_t __ashlti3(__int128_t a, int b);
__int128_t __ashrti3(__int128_t a, int b);
__int128_t __lshrti3(__int128_t a, int b);
#endif /* _S390_LIB_TISHIFT_H */
+203
View File
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Standard user space access functions based on mvcp/mvcs and doing
* interesting things in the secondary space mode.
*
* Copyright IBM Corp. 2006,2014
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
* Gerald Schaefer (gerald.schaefer@de.ibm.com)
*/
#include <linux/kprobes.h>
#include <linux/uaccess.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <asm/asm-extable.h>
#include <asm/ctlreg.h>
#include <asm/skey.h>
#ifdef CONFIG_DEBUG_ENTRY
void debug_user_asce(int exit)
{
struct lowcore *lc = get_lowcore();
struct ctlreg cr1, cr7;
local_ctl_store(1, &cr1);
local_ctl_store(7, &cr7);
if (cr1.val == lc->user_asce.val && cr7.val == lc->user_asce.val)
return;
panic("incorrect ASCE on kernel %s\n"
"cr1: %016lx cr7: %016lx\n"
"kernel: %016lx user: %016lx\n",
exit ? "exit" : "entry", cr1.val, cr7.val,
lc->kernel_asce.val, lc->user_asce.val);
}
#endif /*CONFIG_DEBUG_ENTRY */
#define CMPXCHG_USER_KEY_MAX_LOOPS 128
static nokprobe_inline int __cmpxchg_key_small(void *address, unsigned int *uval,
unsigned int old, unsigned int new,
unsigned int mask, unsigned long key)
{
unsigned long count;
unsigned int prev;
int rc = 0;
skey_regions_initialize();
asm_inline volatile(
"20: spka 0(%[key])\n"
" llill %[count],%[max_loops]\n"
"0: l %[prev],%[address]\n"
"1: nr %[prev],%[mask]\n"
" xilf %[mask],0xffffffff\n"
" or %[new],%[prev]\n"
" or %[prev],%[tmp]\n"
"2: lr %[tmp],%[prev]\n"
"3: cs %[prev],%[new],%[address]\n"
"4: jnl 5f\n"
" xr %[tmp],%[prev]\n"
" xr %[new],%[tmp]\n"
" nr %[tmp],%[mask]\n"
" jnz 5f\n"
" brct %[count],2b\n"
"5: spka %[default_key]\n"
"21:\n"
EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev])
SKEY_REGION(20b, 21b)
: [rc] "+&d" (rc),
[prev] "=&d" (prev),
[address] "+Q" (*(int *)address),
[tmp] "+&d" (old),
[new] "+&d" (new),
[mask] "+&d" (mask),
[count] "=a" (count)
: [key] "%[count]" (key << 4),
[default_key] "J" (PAGE_DEFAULT_KEY),
[max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS)
: "memory", "cc");
*uval = prev;
if (!count)
rc = -EAGAIN;
return rc;
}
int __kprobes __cmpxchg_key1(void *addr, unsigned char *uval, unsigned char old,
unsigned char new, unsigned long key)
{
unsigned long address = (unsigned long)addr;
unsigned int prev, shift, mask, _old, _new;
int rc;
shift = (3 ^ (address & 3)) << 3;
address ^= address & 3;
_old = (unsigned int)old << shift;
_new = (unsigned int)new << shift;
mask = ~(0xff << shift);
rc = __cmpxchg_key_small((void *)address, &prev, _old, _new, mask, key);
*uval = prev >> shift;
return rc;
}
EXPORT_SYMBOL(__cmpxchg_key1);
int __kprobes __cmpxchg_key2(void *addr, unsigned short *uval, unsigned short old,
unsigned short new, unsigned long key)
{
unsigned long address = (unsigned long)addr;
unsigned int prev, shift, mask, _old, _new;
int rc;
shift = (2 ^ (address & 2)) << 3;
address ^= address & 2;
_old = (unsigned int)old << shift;
_new = (unsigned int)new << shift;
mask = ~(0xffff << shift);
rc = __cmpxchg_key_small((void *)address, &prev, _old, _new, mask, key);
*uval = prev >> shift;
return rc;
}
EXPORT_SYMBOL(__cmpxchg_key2);
int __kprobes __cmpxchg_key4(void *address, unsigned int *uval, unsigned int old,
unsigned int new, unsigned long key)
{
unsigned int prev = old;
int rc = 0;
skey_regions_initialize();
asm_inline volatile(
"20: spka 0(%[key])\n"
"0: cs %[prev],%[new],%[address]\n"
"1: spka %[default_key]\n"
"21:\n"
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
SKEY_REGION(20b, 21b)
: [rc] "+&d" (rc),
[prev] "+&d" (prev),
[address] "+Q" (*(int *)address)
: [new] "d" (new),
[key] "a" (key << 4),
[default_key] "J" (PAGE_DEFAULT_KEY)
: "memory", "cc");
*uval = prev;
return rc;
}
EXPORT_SYMBOL(__cmpxchg_key4);
int __kprobes __cmpxchg_key8(void *address, unsigned long *uval, unsigned long old,
unsigned long new, unsigned long key)
{
unsigned long prev = old;
int rc = 0;
skey_regions_initialize();
asm_inline volatile(
"20: spka 0(%[key])\n"
"0: csg %[prev],%[new],%[address]\n"
"1: spka %[default_key]\n"
"21:\n"
EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
SKEY_REGION(20b, 21b)
: [rc] "+&d" (rc),
[prev] "+&d" (prev),
[address] "+QS" (*(long *)address)
: [new] "d" (new),
[key] "a" (key << 4),
[default_key] "J" (PAGE_DEFAULT_KEY)
: "memory", "cc");
*uval = prev;
return rc;
}
EXPORT_SYMBOL(__cmpxchg_key8);
int __kprobes __cmpxchg_key16(void *address, __uint128_t *uval, __uint128_t old,
__uint128_t new, unsigned long key)
{
__uint128_t prev = old;
int rc = 0;
skey_regions_initialize();
asm_inline volatile(
"20: spka 0(%[key])\n"
"0: cdsg %[prev],%[new],%[address]\n"
"1: spka %[default_key]\n"
"21:\n"
EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev])
EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev])
SKEY_REGION(20b, 21b)
: [rc] "+&d" (rc),
[prev] "+&d" (prev),
[address] "+QS" (*(__int128_t *)address)
: [new] "d" (new),
[key] "a" (key << 4),
[default_key] "J" (PAGE_DEFAULT_KEY)
: "memory", "cc");
*uval = prev;
return rc;
}
EXPORT_SYMBOL(__cmpxchg_key16);