summaryrefslogtreecommitdiffstats
path: root/arch/mips/lib/copy_user.S
blob: a7fdc74e5a3d15b712630c37de0ead03142fc69d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * arch/mips/lib/copy_user.S
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (c) 1996, 1997 by Ralf Baechle
 *
 * Less stupid user_copy implementation for 32 bit MIPS CPUs.
 *
 * $Id: copy_user.S,v 1.2 1997/08/11 04:26:12 ralf Exp $
 */
#include <asm/asm.h>
#include <asm/regdef.h>
#include <asm/mipsregs.h>

#define BLOCK_SIZE	16

#define EX(addr,handler)                        \
		.section	__ex_table,"a"; \
		PTR		addr, handler;  \
		.previous
#define UEX(addr,handler)                       \
		EX(addr,handler);               \
		EX(addr+4,handler)

		.set		noreorder
		.set		noat

/* ---------------------------------------------------------------------- */

/*
 * Bad.  We can't fix the alignment for both address parts.
 * Align the source address and copy slowly ...
 */
not_even_the_same_alignment:
		LONG_SUBU	v1,zero,a1
		andi		v1,3
		sltu		t0,v0,v1
		MOVN(v1,v0,t0)
		beqz		v1,src_aligned
		 LONG_ADDU	v1,a0
1:		lb		$1,(a1)
		EX(1b, fault)
		LONG_ADDIU	a1,1
2:		sb		$1,(a0)
		EX(2b, fault)
		LONG_ADDIU	a0,1
		bne		a0,v1,1b
		 LONG_SUBU	v0,1
src_aligned:

/*
 * Ok.  We've fixed the alignment of the copy src for this case.
 * Now let's copy in the usual BLOCK_SIZE byte blocks using unaligned
 * stores.
 * XXX Align the destination address.  This is better if the __copy_user
 *     encounters an access fault because we never have to deal with an
 *     only partially modified destination word.  This is required to
 *     keep the semantics of the result of copy_*_user().
 */
		ori		v1,v0,BLOCK_SIZE-1
		xori		v1,BLOCK_SIZE-1
		beqz		v1,copy_left_over
		 nop
		LONG_SUBU	v0,v1
		LONG_ADDU	v1,a0

1:		lw		t0,(a1)		# Can cause tlb fault
		EX(1b, fault)
2:		lw		t1,4(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		lw		t2,8(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		lw		t3,12(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		usw		t0,(a0)		# Can cause tlb faults
		UEX(2b, fault)
2:		usw		t1,4(a0)	# Can cause tlb faults
		UEX(2b, fault_plus_4)
2:		usw		t2,8(a0)	# Can cause tlb faults
		UEX(2b, fault_plus_8)
2:		usw		t3,12(a0)	# Can cause tlb faults
		UEX(2b, fault_plus_12)
		LONG_ADDIU	a0,BLOCK_SIZE
		bne		a0,v1,1b
		 LONG_ADDIU	a1,BLOCK_SIZE
9:
		b		copy_left_over	# < BLOCK_SIZE bytes left
		 nop

/* ---------------------------------------------------------------------- */

not_w_aligned:
/*
 * Ok, src or destination are not 4-byte aligned.
 * Try to fix that.  Do at least both addresses have the same alignment?
 */
		xor		t0,a0,a1
		andi		t0,3
		bnez		t0,not_even_the_same_alignment
		nop					# delay slot

/*
 * Ok, we can fix the alignment for both operands and go back to the
 * fast path.  We have to copy at least one byte, on average 3 bytes
 * bytewise.
 */
		LONG_SUBU	v1,zero,a0
		andi		v1,3
		sltu		t0,v0,v1
		MOVN(v1,v0,t0)
		beqz		v1,__copy_user
		 LONG_ADDU	v1,a0
1:		lb		$1,(a1)
		EX(1b, fault)
		LONG_ADDIU	a1,1
2:		sb		$1,(a0)
		EX(2b, fault)
		LONG_ADDIU	a0,1
		bne		a0,v1,1b
		 LONG_SUBU	v0,1
		b		align4
		 nop

/* ---------------------------------------------------------------------- */

LEAF(__copy_user)
		or		t1,a0,a1
		andi		t1,3
		bnez		t1,not_w_aligned	# not word alignment
		 move		v0,a2

align4:
		ori		v1,v0,BLOCK_SIZE-1
		xori		v1,BLOCK_SIZE-1
		beqz		v1,copy_left_over
		 nop
		LONG_SUBU	v0,v1
		LONG_ADDU	v1,a0

1:		lw		t0,(a1)		# Can cause tlb fault
		EX(1b, fault)
2:		lw		t1,4(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		lw		t2,8(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		lw		t3,12(a1)	# Can cause tlb fault
		EX(2b, fault)
2:		sw		t0,(a0)		# Can cause tlb fault
		EX(2b, fault)
2:		sw		t1,4(a0)	# Can cause tlb fault
		EX(2b, fault_plus_4)
2:		sw		t2,8(a0)	# Can cause tlb fault
		EX(2b, fault_plus_8)
2:		sw		t3,12(a0)	# Can cause tlb fault
		EX(2b, fault_plus_12)
		LONG_ADDIU	a0,BLOCK_SIZE
		bne		a0,v1,1b
		 LONG_ADDIU	a1,BLOCK_SIZE
9:

/*
 * XXX Tune me ...
 */
copy_left_over:
		beqz		v0,3f
		 nop
1:		lb		$1,(a1)
		EX(1b, fault)
		LONG_ADDIU	a1,1
2:		sb		$1,(a0)
		EX(2b, fault)
		LONG_SUBU	v0,1
		bnez		v0,1b
		 LONG_ADDIU	a0,1
3:

done:		jr		ra
		 nop

		END(__copy_user)
		.set		at
		.set		reorder

/* ---------------------------------------------------------------------- */

/*
 * Access fault.  The number of not copied bytes is in v0.  We have to
 * correct the number of the not copied bytes in v0 in case of a access
 * fault in an unrolled loop, then return.
 */

fault:			jr	ra
fault_plus_4:		LONG_ADDIU	v0,4
			jr	ra
fault_plus_8:		LONG_ADDIU	v0,8
			jr	ra
fault_plus_12:		LONG_ADDIU	v0,12
			jr	ra