1// SPDX-License-Identifier: GPL-2.0-only
2#include <linux/highmem.h>
3#include <linux/ptrace.h>
4#include <linux/sched.h>
5#include <linux/uprobes.h>
6#include <asm/cacheflush.h>
7
8#define UPROBE_TRAP_NR	UINT_MAX
9
10int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe,
11			     struct mm_struct *mm, unsigned long addr)
12{
13	int idx;
14	union loongarch_instruction insn;
15
16	if (addr & 0x3)
17		return -EILSEQ;
18
19	for (idx = ARRAY_SIZE(auprobe->insn) - 1; idx >= 0; idx--) {
20		insn.word = auprobe->insn[idx];
21		if (insns_not_supported(insn))
22			return -EINVAL;
23	}
24
25	if (insns_need_simulation(insn)) {
26		auprobe->ixol[0] = larch_insn_gen_nop();
27		auprobe->simulate = true;
28	} else {
29		auprobe->ixol[0] = auprobe->insn[0];
30		auprobe->simulate = false;
31	}
32
33	auprobe->ixol[1] = UPROBE_XOLBP_INSN;
34
35	return 0;
36}
37
38int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
39{
40	struct uprobe_task *utask = current->utask;
41
42	utask->autask.saved_trap_nr = current->thread.trap_nr;
43	current->thread.trap_nr = UPROBE_TRAP_NR;
44	instruction_pointer_set(regs, utask->xol_vaddr);
45	user_enable_single_step(current);
46
47	return 0;
48}
49
50int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
51{
52	struct uprobe_task *utask = current->utask;
53
54	WARN_ON_ONCE(current->thread.trap_nr != UPROBE_TRAP_NR);
55	current->thread.trap_nr = utask->autask.saved_trap_nr;
56
57	if (auprobe->simulate)
58		instruction_pointer_set(regs, auprobe->resume_era);
59	else
60		instruction_pointer_set(regs, utask->vaddr + LOONGARCH_INSN_SIZE);
61
62	user_disable_single_step(current);
63
64	return 0;
65}
66
67void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
68{
69	struct uprobe_task *utask = current->utask;
70
71	current->thread.trap_nr = utask->autask.saved_trap_nr;
72	instruction_pointer_set(regs, utask->vaddr);
73	user_disable_single_step(current);
74}
75
76bool arch_uprobe_xol_was_trapped(struct task_struct *t)
77{
78	if (t->thread.trap_nr != UPROBE_TRAP_NR)
79		return true;
80
81	return false;
82}
83
84bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
85{
86	union loongarch_instruction insn;
87
88	if (!auprobe->simulate)
89		return false;
90
91	insn.word = auprobe->insn[0];
92	arch_simulate_insn(insn, regs);
93	auprobe->resume_era = regs->csr_era;
94
95	return true;
96}
97
98unsigned long arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
99						struct pt_regs *regs)
100{
101	unsigned long ra = regs->regs[1];
102
103	regs->regs[1] = trampoline_vaddr;
104
105	return ra;
106}
107
108bool arch_uretprobe_is_alive(struct return_instance *ret,
109			     enum rp_check ctx, struct pt_regs *regs)
110{
111	if (ctx == RP_CHECK_CHAIN_CALL)
112		return regs->regs[3] <= ret->stack;
113	else
114		return regs->regs[3] < ret->stack;
115}
116
117int arch_uprobe_exception_notify(struct notifier_block *self,
118				 unsigned long val, void *data)
119{
120	return NOTIFY_DONE;
121}
122
123bool uprobe_breakpoint_handler(struct pt_regs *regs)
124{
125	if (uprobe_pre_sstep_notifier(regs))
126		return true;
127
128	return false;
129}
130
131bool uprobe_singlestep_handler(struct pt_regs *regs)
132{
133	if (uprobe_post_sstep_notifier(regs))
134		return true;
135
136	return false;
137}
138
139unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
140{
141	return instruction_pointer(regs);
142}
143
144void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
145			   void *src, unsigned long len)
146{
147	void *kaddr = kmap_local_page(page);
148	void *dst = kaddr + (vaddr & ~PAGE_MASK);
149
150	memcpy(dst, src, len);
151	flush_icache_range((unsigned long)dst, (unsigned long)dst + len);
152	kunmap_local(kaddr);
153}
154