diff --git a/arch/parisc/Kconfig b/arch/parisc/Kconfig
index 0940c16..47fd966 100644
--- a/arch/parisc/Kconfig
+++ b/arch/parisc/Kconfig
@@ -31,6 +31,9 @@ config PARISC
 	select HAVE_KERNEL_UNCOMPRESSED
 	select HAVE_PCI
 	select HAVE_PERF_EVENTS
+	select HAVE_PERF_REGS
+	select HAVE_PERF_USER_STACK_DUMP
+	select PERF_USE_VMALLOC
 	select HAVE_KERNEL_BZIP2
 	select HAVE_KERNEL_GZIP
 	select HAVE_KERNEL_LZ4
diff --git a/arch/parisc/include/asm/perf_event.h b/arch/parisc/include/asm/perf_event.h
index 1e0fd8b..8a29250 100644
--- a/arch/parisc/include/asm/perf_event.h
+++ b/arch/parisc/include/asm/perf_event.h
@@ -1,6 +1,12 @@
 #ifndef __ASM_PARISC_PERF_EVENT_H
 #define __ASM_PARISC_PERF_EVENT_H
 
-/* Empty, just to avoid compiling error */
+#include <asm/psw.h>
+
+#define perf_arch_fetch_caller_regs(regs, __ip) { \
+	(regs)->gr[0] = KERNEL_PSW; \
+	(regs)->iaoq[0] = (__ip); \
+	asm volatile("copy %%sp, %0\n":"=r"((regs)->gr[30])); \
+}
 
 #endif /* __ASM_PARISC_PERF_EVENT_H */
diff --git a/arch/parisc/include/uapi/asm/ioctls.h b/arch/parisc/include/uapi/asm/ioctls.h
index 82d1148..74b4027 100644
--- a/arch/parisc/include/uapi/asm/ioctls.h
+++ b/arch/parisc/include/uapi/asm/ioctls.h
@@ -10,10 +10,10 @@
 #define TCSETS		_IOW('T', 17, struct termios) /* TCSETATTR */
 #define TCSETSW		_IOW('T', 18, struct termios) /* TCSETATTRD */
 #define TCSETSF		_IOW('T', 19, struct termios) /* TCSETATTRF */
-#define TCGETA		_IOR('T', 1, struct termio)
-#define TCSETA		_IOW('T', 2, struct termio)
-#define TCSETAW		_IOW('T', 3, struct termio)
-#define TCSETAF		_IOW('T', 4, struct termio)
+#define TCGETA          0x40125401
+#define TCSETA          0x80125402
+#define TCSETAW         0x80125403
+#define TCSETAF         0x80125404
 #define TCSBRK		_IO('T', 5)
 #define TCXONC		_IO('T', 6)
 #define TCFLSH		_IO('T', 7)
diff --git a/arch/parisc/include/uapi/asm/perf_regs.h b/arch/parisc/include/uapi/asm/perf_regs.h
new file mode 100644
index 0000000..1ae687b
--- /dev/null
+++ b/arch/parisc/include/uapi/asm/perf_regs.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_ASM_PARISC_PERF_REGS_H
+#define _UAPI_ASM_PARISC_PERF_REGS_H
+
+/* see struct user_regs_struct */
+enum perf_event_parisc_regs {
+	PERF_REG_PARISC_R0,	/* PSW is in gr[0] */
+	PERF_REG_PARISC_R1,
+	PERF_REG_PARISC_R2,
+	PERF_REG_PARISC_R3,
+	PERF_REG_PARISC_R4,
+	PERF_REG_PARISC_R5,
+	PERF_REG_PARISC_R6,
+	PERF_REG_PARISC_R7,
+	PERF_REG_PARISC_R8,
+	PERF_REG_PARISC_R9,
+	PERF_REG_PARISC_R10,
+	PERF_REG_PARISC_R11,
+	PERF_REG_PARISC_R12,
+	PERF_REG_PARISC_R13,
+	PERF_REG_PARISC_R14,
+	PERF_REG_PARISC_R15,
+	PERF_REG_PARISC_R16,
+	PERF_REG_PARISC_R17,
+	PERF_REG_PARISC_R18,
+	PERF_REG_PARISC_R19,
+	PERF_REG_PARISC_R20,
+	PERF_REG_PARISC_R21,
+	PERF_REG_PARISC_R22,
+	PERF_REG_PARISC_R23,
+	PERF_REG_PARISC_R24,
+	PERF_REG_PARISC_R25,
+	PERF_REG_PARISC_R26,
+	PERF_REG_PARISC_R27,
+	PERF_REG_PARISC_R28,
+	PERF_REG_PARISC_R29,
+	PERF_REG_PARISC_R30,
+	PERF_REG_PARISC_R31,
+
+	PERF_REG_PARISC_SR0,
+	PERF_REG_PARISC_SR1,
+	PERF_REG_PARISC_SR2,
+	PERF_REG_PARISC_SR3,
+	PERF_REG_PARISC_SR4,
+	PERF_REG_PARISC_SR5,
+	PERF_REG_PARISC_SR6,
+	PERF_REG_PARISC_SR7,
+
+	PERF_REG_PARISC_IAOQ0,
+	PERF_REG_PARISC_IAOQ1,
+	PERF_REG_PARISC_IASQ0,
+	PERF_REG_PARISC_IASQ1,
+
+	PERF_REG_PARISC_SAR,	/* CR11 */
+	PERF_REG_PARISC_IIR,	/* CR19 */
+	PERF_REG_PARISC_ISR,	/* CR20 */
+	PERF_REG_PARISC_IOR,	/* CR21 */
+	PERF_REG_PARISC_IPSW,	/* CR22 */
+
+	PERF_REG_PARISC_MAX
+};
+
+#endif /* _UAPI_ASM_PARISC_PERF_REGS_H */
diff --git a/arch/parisc/kernel/Makefile b/arch/parisc/kernel/Makefile
index d5055ba..9157bc8 100644
--- a/arch/parisc/kernel/Makefile
+++ b/arch/parisc/kernel/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY)	+= topology.o
 obj-$(CONFIG_FUNCTION_TRACER)		+= ftrace.o
 obj-$(CONFIG_FUNCTION_GRAPH_TRACER)	+= ftrace.o
 obj-$(CONFIG_JUMP_LABEL)		+= jump_label.o
+obj-$(CONFIG_PERF_EVENTS)		+= perf_event.o perf_regs.o
 obj-$(CONFIG_KGDB)			+= kgdb.o
 obj-$(CONFIG_KPROBES)			+= kprobes.o
 obj-$(CONFIG_KEXEC_CORE)		+= kexec.o relocate_kernel.o
diff --git a/arch/parisc/kernel/drivers.c b/arch/parisc/kernel/drivers.c
index 1e793f7..1f8936f 100644
--- a/arch/parisc/kernel/drivers.c
+++ b/arch/parisc/kernel/drivers.c
@@ -995,6 +995,7 @@ static __init int qemu_print_iodc_data(struct device *lin_dev, void *data)
 	struct pdc_system_map_mod_info pdc_mod_info;
 	struct pdc_module_path mod_path;
 
+	memset(&iodc_data, 0, sizeof(iodc_data));
 	status = pdc_iodc_read(&count, hpa, 0,
 		&iodc_data, sizeof(iodc_data));
 	if (status != PDC_OK) {
@@ -1012,6 +1013,11 @@ static __init int qemu_print_iodc_data(struct device *lin_dev, void *data)
 
 	mod_index = 0;
 	do {
+		/* initialize device path for old machines */
+		memset(&mod_path, 0xff, sizeof(mod_path));
+		get_node_path(dev->dev.parent, &mod_path.path);
+		mod_path.path.mod = dev->hw_path;
+		memset(&pdc_mod_info, 0, sizeof(pdc_mod_info));
 		status = pdc_system_map_find_mods(&pdc_mod_info,
 				&mod_path, mod_index++);
 	} while (status == PDC_OK && pdc_mod_info.mod_addr != hpa);
diff --git a/arch/parisc/kernel/firmware.c b/arch/parisc/kernel/firmware.c
index c69f6d5..0423434 100644
--- a/arch/parisc/kernel/firmware.c
+++ b/arch/parisc/kernel/firmware.c
@@ -464,7 +464,8 @@ int pdc_system_map_find_mods(struct pdc_system_map_mod_info *pdc_mod_info,
 	unsigned long flags;
 
 	spin_lock_irqsave(&pdc_lock, flags);
-	retval = mem_pdc_call(PDC_SYSTEM_MAP, PDC_FIND_MODULE, __pa(pdc_result), 
+	memcpy(pdc_result2, mod_path, sizeof(*mod_path));
+	retval = mem_pdc_call(PDC_SYSTEM_MAP, PDC_FIND_MODULE, __pa(pdc_result),
 			      __pa(pdc_result2), mod_index);
 	convert_to_wide(pdc_result);
 	memcpy(pdc_mod_info, pdc_result, sizeof(*pdc_mod_info));
diff --git a/arch/parisc/kernel/perf_event.c b/arch/parisc/kernel/perf_event.c
new file mode 100644
index 0000000..f90b838
--- /dev/null
+++ b/arch/parisc/kernel/perf_event.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Performance event support for parisc
+ *
+ * Copyright (C) 2025 by Helge Deller <deller@gmx.de>
+ */
+
+#include <linux/kernel.h>
+#include <linux/perf_event.h>
+#include <asm/unwind.h>
+
+void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
+			   struct pt_regs *regs)
+{
+
+	struct unwind_frame_info info;
+
+	unwind_frame_init_task(&info, current, NULL);
+	while (1) {
+		if (unwind_once(&info) < 0 || info.ip == 0)
+			break;
+
+		if (!__kernel_text_address(info.ip) ||
+			perf_callchain_store(entry, info.ip))
+				return;
+	}
+}
diff --git a/arch/parisc/kernel/perf_regs.c b/arch/parisc/kernel/perf_regs.c
new file mode 100644
index 0000000..68458e2
--- /dev/null
+++ b/arch/parisc/kernel/perf_regs.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (C) 2025 by Helge Deller <deller@gmx.de> */
+
+#include <linux/perf_event.h>
+#include <linux/perf_regs.h>
+#include <asm/ptrace.h>
+
+u64 perf_reg_value(struct pt_regs *regs, int idx)
+{
+	switch (idx) {
+	case PERF_REG_PARISC_R0 ... PERF_REG_PARISC_R31:
+		return regs->gr[idx - PERF_REG_PARISC_R0];
+	case PERF_REG_PARISC_SR0 ... PERF_REG_PARISC_SR7:
+		return regs->sr[idx - PERF_REG_PARISC_SR0];
+	case PERF_REG_PARISC_IASQ0 ... PERF_REG_PARISC_IASQ1:
+		return regs->iasq[idx - PERF_REG_PARISC_IASQ0];
+	case PERF_REG_PARISC_IAOQ0 ... PERF_REG_PARISC_IAOQ1:
+		return regs->iasq[idx - PERF_REG_PARISC_IAOQ0];
+	case PERF_REG_PARISC_SAR:	/* CR11 */
+		return regs->sar;
+	case PERF_REG_PARISC_IIR:	/* CR19 */
+		return regs->iir;
+	case PERF_REG_PARISC_ISR:	/* CR20 */
+		return regs->isr;
+	case PERF_REG_PARISC_IOR:	/* CR21 */
+		return regs->ior;
+	case PERF_REG_PARISC_IPSW:	/* CR22 */
+		return regs->ipsw;
+	};
+	WARN_ON_ONCE((u32)idx >= PERF_REG_PARISC_MAX);
+	return 0;
+}
+
+#define REG_RESERVED (~((1ULL << PERF_REG_PARISC_MAX) - 1))
+
+int perf_reg_validate(u64 mask)
+{
+	if (!mask || mask & REG_RESERVED)
+		return -EINVAL;
+
+	return 0;
+}
+
+u64 perf_reg_abi(struct task_struct *task)
+{
+	if (!IS_ENABLED(CONFIG_64BIT))
+		return PERF_SAMPLE_REGS_ABI_32;
+
+	if (test_tsk_thread_flag(task, TIF_32BIT))
+		return PERF_SAMPLE_REGS_ABI_32;
+
+	return PERF_SAMPLE_REGS_ABI_64;
+}
+
+void perf_get_regs_user(struct perf_regs *regs_user,
+			struct pt_regs *regs)
+{
+	regs_user->regs = task_pt_regs(current);
+	regs_user->abi = perf_reg_abi(current);
+}
diff --git a/arch/parisc/kernel/traps.c b/arch/parisc/kernel/traps.c
index b9b3d52..4c7c5df 100644
--- a/arch/parisc/kernel/traps.c
+++ b/arch/parisc/kernel/traps.c
@@ -31,6 +31,7 @@
 #include <linux/uaccess.h>
 #include <linux/kdebug.h>
 #include <linux/kfence.h>
+#include <linux/perf_event.h>
 
 #include <asm/assembly.h>
 #include <asm/io.h>
@@ -633,6 +634,7 @@ void notrace handle_interruption(int code, struct pt_regs *regs)
 		/* Assist Exception Trap, i.e. floating point exception. */
 		die_if_kernel("Floating point exception", regs, 0); /* quiet */
 		__inc_irq_stat(irq_fpassist_count);
+		perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, 0);
 		handle_fpe(regs);
 		return;
 
diff --git a/arch/parisc/kernel/unaligned.c b/arch/parisc/kernel/unaligned.c
index 00e9720..fb64d9c 100644
--- a/arch/parisc/kernel/unaligned.c
+++ b/arch/parisc/kernel/unaligned.c
@@ -13,6 +13,7 @@
 #include <linux/uaccess.h>
 #include <linux/sysctl.h>
 #include <linux/unaligned.h>
+#include <linux/perf_event.h>
 #include <asm/hardirq.h>
 #include <asm/traps.h>
 #include "unaligned.h"
@@ -378,6 +379,7 @@ void handle_unaligned(struct pt_regs *regs)
 	int ret = ERR_NOTHANDLED;
 
 	__inc_irq_stat(irq_unaligned_count);
+	perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, regs->ior);
 
 	/* log a message with pacing */
 	if (user_mode(regs)) {
diff --git a/arch/parisc/lib/memcpy.c b/arch/parisc/lib/memcpy.c
index 69d65ff..03165c8 100644
--- a/arch/parisc/lib/memcpy.c
+++ b/arch/parisc/lib/memcpy.c
@@ -41,7 +41,6 @@ unsigned long raw_copy_from_user(void *dst, const void __user *src,
 	mtsp(get_kernel_space(), SR_TEMP2);
 
 	/* Check region is user accessible */
-	if (start)
 	while (start < end) {
 		if (!prober_user(SR_TEMP1, start)) {
 			newlen = (start - (unsigned long) src);
