185199Sdfr/*-
2121415Smarcel * Copyright (c) 2003 Marcel Moolenaar
385199Sdfr * Copyright (c) 2001 Doug Rabson
485199Sdfr * All rights reserved.
585199Sdfr *
685199Sdfr * Redistribution and use in source and binary forms, with or without
785199Sdfr * modification, are permitted provided that the following conditions
885199Sdfr * are met:
985199Sdfr * 1. Redistributions of source code must retain the above copyright
1085199Sdfr *    notice, this list of conditions and the following disclaimer.
1185199Sdfr * 2. Redistributions in binary form must reproduce the above copyright
1285199Sdfr *    notice, this list of conditions and the following disclaimer in the
1385199Sdfr *    documentation and/or other materials provided with the distribution.
1485199Sdfr *
1585199Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1685199Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1785199Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1885199Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1985199Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2085199Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2185199Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2285199Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2385199Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2485199Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2585199Sdfr * SUCH DAMAGE.
2685199Sdfr *
2785199Sdfr * $FreeBSD: releng/10.2/sys/ia64/ia64/unaligned.c 219741 2011-03-18 15:36:28Z marcel $
2885199Sdfr */
2985199Sdfr
3085199Sdfr#include <sys/param.h>
3185199Sdfr#include <sys/systm.h>
3285199Sdfr#include <sys/kernel.h>
3385199Sdfr#include <sys/proc.h>
34115378Smarcel#include <sys/sysctl.h>
3585199Sdfr#include <vm/vm.h>
3685199Sdfr#include <vm/vm_extern.h>
3785199Sdfr#include <machine/frame.h>
38121415Smarcel#include <machine/md_var.h>
39121415Smarcel#include <ia64/disasm/disasm.h>
4085199Sdfr
41139554Smarcelstatic int ia64_unaligned_print = 0;
42139554SmarcelSYSCTL_INT(_debug, OID_AUTO, unaligned_print, CTLFLAG_RW,
43115378Smarcel    &ia64_unaligned_print, 0, "warn about unaligned accesses");
44115378Smarcel
45139554Smarcelstatic int ia64_unaligned_test = 0;
46139554SmarcelSYSCTL_INT(_debug, OID_AUTO, unaligned_test, CTLFLAG_RW,
47139554Smarcel    &ia64_unaligned_test, 0, "test emulation when PSR.ac is set");
48115378Smarcel
49121415Smarcelstatic void *
50121415Smarcelfpreg_ptr(mcontext_t *mc, int fr)
5185199Sdfr{
52121415Smarcel	union _ia64_fpreg *p;
5385199Sdfr
54121415Smarcel	if (fr <= 1 || fr >= 128)
55121415Smarcel		return (NULL);
56121415Smarcel	if (fr >= 32) {
57121415Smarcel		p = &mc->mc_high_fp.fr32;
58121415Smarcel		fr -= 32;
59121415Smarcel	} else if (fr >= 16) {
60121415Smarcel		p = &mc->mc_preserved_fp.fr16;
61121415Smarcel		fr -= 16;
62121415Smarcel	} else if (fr >= 6) {
63121415Smarcel		p = &mc->mc_scratch_fp.fr6;
64121415Smarcel		fr -= 6;
65121415Smarcel	} else {
66121415Smarcel		p = &mc->mc_preserved_fp.fr2;
67121415Smarcel		fr -= 2;
68121415Smarcel	}
69121415Smarcel	return ((void*)(p + fr));
7085199Sdfr}
7185199Sdfr
72121415Smarcelstatic void *
73121415Smarcelgreg_ptr(mcontext_t *mc, int gr)
7485199Sdfr{
75121415Smarcel	uint64_t *p;
76133888Sarun	int nslots;
7785199Sdfr
78121415Smarcel	if (gr <= 0 || gr >= 32 + (mc->mc_special.cfm & 0x7f))
79121415Smarcel		return (NULL);
80121415Smarcel	if (gr >= 32) {
81133888Sarun	 	nslots = IA64_CFM_SOF(mc->mc_special.cfm) - gr + 32;
82133888Sarun		p = (void *)ia64_bsp_adjust(mc->mc_special.bspstore, -nslots);
83121415Smarcel		gr = 0;
84121415Smarcel	} else if (gr >= 14) {
85121415Smarcel		p = &mc->mc_scratch.gr14;
86121415Smarcel		gr -= 14;
87121415Smarcel	} else if (gr == 13) {
88121415Smarcel		p = &mc->mc_special.tp;
89121415Smarcel		gr = 0;
90121415Smarcel	} else if (gr == 12) {
91121415Smarcel		p = &mc->mc_special.sp;
92121415Smarcel		gr = 0;
93121415Smarcel	} else if (gr >= 8) {
94121415Smarcel		p = &mc->mc_scratch.gr8;
95121415Smarcel		gr -= 8;
96121415Smarcel	} else if (gr >= 4) {
97121415Smarcel		p = &mc->mc_preserved.gr4;
98121415Smarcel		gr -= 4;
99121415Smarcel	} else if (gr >= 2) {
100121415Smarcel		p = &mc->mc_scratch.gr2;
101121415Smarcel		gr -= 2;
102121415Smarcel	} else {
103121415Smarcel		p = &mc->mc_special.gp;
104121415Smarcel		gr = 0;
105121415Smarcel	}
106121415Smarcel	return ((void*)(p + gr));
10785199Sdfr}
10885199Sdfr
109121449Smarcelstatic uint64_t
110121449Smarcelrdreg(uint64_t *addr)
111121449Smarcel{
112219741Smarcel	if ((uintptr_t)addr < VM_MAXUSER_ADDRESS)
113121449Smarcel		return (fuword(addr));
114121449Smarcel	return (*addr);
115121449Smarcel}
116121449Smarcel
117121449Smarcelstatic void
118121449Smarcelwrreg(uint64_t *addr, uint64_t val)
119121449Smarcel{
120219741Smarcel	if ((uintptr_t)addr < VM_MAXUSER_ADDRESS)
121121449Smarcel		suword(addr, val);
122121449Smarcel	else
123121449Smarcel		*addr = val;
124121449Smarcel}
125121449Smarcel
12685199Sdfrstatic int
127121415Smarcelfixup(struct asm_inst *i, mcontext_t *mc, uint64_t va)
12885199Sdfr{
129121415Smarcel	union {
130121415Smarcel		double d;
131139554Smarcel		long double e;
132121415Smarcel		uint64_t i;
133139554Smarcel		float s;
134121415Smarcel	} buf;
135121415Smarcel	void *reg;
136121415Smarcel	uint64_t postinc;
13785199Sdfr
138121415Smarcel	switch (i->i_op) {
139122162Smarcel	case ASM_OP_LD2:
140122162Smarcel		copyin((void*)va, (void*)&buf.i, 2);
141122162Smarcel		reg = greg_ptr(mc, (int)i->i_oper[1].o_value);
142122162Smarcel		if (reg == NULL)
143122162Smarcel			return (EINVAL);
144122162Smarcel		wrreg(reg, buf.i & 0xffffU);
145122162Smarcel		break;
146121933Smarcel	case ASM_OP_LD4:
147121933Smarcel		copyin((void*)va, (void*)&buf.i, 4);
148121933Smarcel		reg = greg_ptr(mc, (int)i->i_oper[1].o_value);
149121933Smarcel		if (reg == NULL)
150121933Smarcel			return (EINVAL);
151121933Smarcel		wrreg(reg, buf.i & 0xffffffffU);
152121933Smarcel		break;
153121415Smarcel	case ASM_OP_LD8:
154121415Smarcel		copyin((void*)va, (void*)&buf.i, 8);
155121415Smarcel		reg = greg_ptr(mc, (int)i->i_oper[1].o_value);
156121415Smarcel		if (reg == NULL)
157115084Smarcel			return (EINVAL);
158121449Smarcel		wrreg(reg, buf.i);
159121415Smarcel		break;
160121415Smarcel	case ASM_OP_LDFD:
161139554Smarcel		copyin((void*)va, (void*)&buf.d, sizeof(buf.d));
162121415Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[1].o_value);
163121415Smarcel		if (reg == NULL)
164121415Smarcel			return (EINVAL);
165139554Smarcel		__asm("ldfd f6=%1;; stf.spill %0=f6" : "=m"(*(double *)reg) :
166139554Smarcel		    "m"(buf.d) : "f6");
167121415Smarcel		break;
168139554Smarcel	case ASM_OP_LDFE:
169139554Smarcel		copyin((void*)va, (void*)&buf.e, sizeof(buf.e));
170139554Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[1].o_value);
171139554Smarcel		if (reg == NULL)
172139554Smarcel			return (EINVAL);
173139554Smarcel		__asm("ldfe f6=%1;; stf.spill %0=f6" :
174139554Smarcel		    "=m"(*(long double *)reg) : "m"(buf.e) : "f6");
175139554Smarcel		break;
176139554Smarcel	case ASM_OP_LDFS:
177139554Smarcel		copyin((void*)va, (void*)&buf.s, sizeof(buf.s));
178139554Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[1].o_value);
179139554Smarcel		if (reg == NULL)
180139554Smarcel			return (EINVAL);
181139554Smarcel		__asm("ldfs f6=%1;; stf.spill %0=f6" : "=m"(*(float *)reg) :
182139554Smarcel		    "m"(buf.s) : "f6");
183139554Smarcel		break;
184122162Smarcel	case ASM_OP_ST2:
185122162Smarcel		reg = greg_ptr(mc, (int)i->i_oper[2].o_value);
186122162Smarcel		if (reg == NULL)
187122162Smarcel			return (EINVAL);
188122162Smarcel		buf.i = rdreg(reg);
189122162Smarcel		copyout((void*)&buf.i, (void*)va, 2);
190122162Smarcel		break;
191122162Smarcel	case ASM_OP_ST4:
192122162Smarcel		reg = greg_ptr(mc, (int)i->i_oper[2].o_value);
193122162Smarcel		if (reg == NULL)
194122162Smarcel			return (EINVAL);
195122162Smarcel		buf.i = rdreg(reg);
196122162Smarcel		copyout((void*)&buf.i, (void*)va, 4);
197122162Smarcel		break;
198122162Smarcel	case ASM_OP_ST8:
199122162Smarcel		reg = greg_ptr(mc, (int)i->i_oper[2].o_value);
200122162Smarcel		if (reg == NULL)
201122162Smarcel			return (EINVAL);
202122162Smarcel		buf.i = rdreg(reg);
203122162Smarcel		copyout((void*)&buf.i, (void*)va, 8);
204122162Smarcel		break;
205139554Smarcel	case ASM_OP_STFD:
206139554Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[2].o_value);
207139554Smarcel		if (reg == NULL)
208139554Smarcel			return (EINVAL);
209139554Smarcel		__asm("ldf.fill f6=%1;; stfd %0=f6" : "=m"(buf.d) :
210139554Smarcel		    "m"(*(double *)reg) : "f6");
211139554Smarcel		copyout((void*)&buf.d, (void*)va, sizeof(buf.d));
212139554Smarcel		break;
213139554Smarcel	case ASM_OP_STFE:
214139554Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[2].o_value);
215139554Smarcel		if (reg == NULL)
216139554Smarcel			return (EINVAL);
217139554Smarcel		__asm("ldf.fill f6=%1;; stfe %0=f6" : "=m"(buf.e) :
218139554Smarcel		    "m"(*(long double *)reg) : "f6");
219139554Smarcel		copyout((void*)&buf.e, (void*)va, sizeof(buf.e));
220139554Smarcel		break;
221139554Smarcel	case ASM_OP_STFS:
222139554Smarcel		reg = fpreg_ptr(mc, (int)i->i_oper[2].o_value);
223139554Smarcel		if (reg == NULL)
224139554Smarcel			return (EINVAL);
225139554Smarcel		__asm("ldf.fill f6=%1;; stfs %0=f6" : "=m"(buf.s) :
226139554Smarcel		    "m"(*(float *)reg) : "f6");
227139554Smarcel		copyout((void*)&buf.s, (void*)va, sizeof(buf.s));
228139554Smarcel		break;
229121415Smarcel	default:
230121415Smarcel		return (ENOENT);
23185199Sdfr	}
23285199Sdfr
233121415Smarcel	/* Handle post-increment. */
234121415Smarcel	if (i->i_oper[3].o_type == ASM_OPER_GREG) {
235121415Smarcel		reg = greg_ptr(mc, (int)i->i_oper[3].o_value);
236121415Smarcel		if (reg == NULL)
237115084Smarcel			return (EINVAL);
238121449Smarcel		postinc = rdreg(reg);
239121415Smarcel	} else
240121415Smarcel		postinc = (i->i_oper[3].o_type == ASM_OPER_IMM)
241121415Smarcel		    ? i->i_oper[3].o_value : 0;
242121415Smarcel	if (postinc != 0) {
243140891Smarcel		if (i->i_oper[1].o_type == ASM_OPER_MEM)
244140891Smarcel			reg = greg_ptr(mc, (int)i->i_oper[1].o_value);
245140891Smarcel		else
246140891Smarcel			reg = greg_ptr(mc, (int)i->i_oper[2].o_value);
247121415Smarcel		if (reg == NULL)
248121415Smarcel			return (EINVAL);
249121449Smarcel		postinc += rdreg(reg);
250121449Smarcel		wrreg(reg, postinc);
25185199Sdfr	}
252115084Smarcel	return (0);
25385199Sdfr}
25485199Sdfr
25585199Sdfrint
256121415Smarcelunaligned_fixup(struct trapframe *tf, struct thread *td)
25785199Sdfr{
258121415Smarcel	mcontext_t context;
259121415Smarcel	struct asm_bundle bundle;
260121415Smarcel	int error, slot;
26185199Sdfr
262121415Smarcel	slot = ((tf->tf_special.psr & IA64_PSR_RI) == IA64_PSR_RI_0) ? 0 :
263121415Smarcel	    ((tf->tf_special.psr & IA64_PSR_RI) == IA64_PSR_RI_1) ? 1 : 2;
26485199Sdfr
265121415Smarcel	if (ia64_unaligned_print) {
266121415Smarcel		uprintf("pid %d (%s): unaligned access: va=0x%lx, pc=0x%lx\n",
267121415Smarcel		    td->td_proc->p_pid, td->td_proc->p_comm,
268121415Smarcel		    tf->tf_special.ifa, tf->tf_special.iip + slot);
26985199Sdfr	}
27085199Sdfr
27185199Sdfr	/*
272139554Smarcel	 * If PSR.ac is set, the process wants to be signalled about mis-
273139554Smarcel	 * aligned loads and stores. Send it a SIGBUS. In order for us to
274139554Smarcel	 * test the emulation of misaligned loads and stores, we have a
275139554Smarcel	 * sysctl that tells us that we must emulate the load or store,
276139554Smarcel	 * instead of sending the signal. We need the sysctl because if
277139554Smarcel	 * PSR.ac is not set, the CPU may (and likely will) deal with the
278139554Smarcel	 * misaligned load or store itself. As such, we won't get the
279139554Smarcel	 * exception.
28085199Sdfr	 */
281139554Smarcel	if ((tf->tf_special.psr & IA64_PSR_AC) && !ia64_unaligned_test)
282121415Smarcel		return (SIGBUS);
28385199Sdfr
284121415Smarcel	if (!asm_decode(tf->tf_special.iip, &bundle))
285121415Smarcel		return (SIGILL);
28685199Sdfr
287121415Smarcel	get_mcontext(td, &context, 0);
28885199Sdfr
289121415Smarcel	error = fixup(bundle.b_inst + slot, &context, tf->tf_special.ifa);
290121415Smarcel	if (error == ENOENT) {
291121415Smarcel		printf("unhandled misaligned memory access:\n\t");
292121415Smarcel		asm_print_inst(&bundle, slot, tf->tf_special.iip);
293121415Smarcel		return (SIGILL);
294121415Smarcel	} else if (error != 0)
295121415Smarcel		return (SIGBUS);
29685199Sdfr
297121415Smarcel	set_mcontext(td, &context);
29885199Sdfr
299121415Smarcel	/* Advance to the next instruction. */
300121415Smarcel	if (slot == 2) {
301121415Smarcel		tf->tf_special.psr &= ~IA64_PSR_RI;
302121415Smarcel		tf->tf_special.iip += 16;
303121415Smarcel	} else
304121415Smarcel		tf->tf_special.psr += IA64_PSR_RI_1;
30585685Sdfr
306121415Smarcel	return (0);
30785199Sdfr}
308