summaryrefslogtreecommitdiffstats
path: root/arch/s390/purgatory/head.S
blob: 9b2d7a71fd1bd24cc0ebceef80b2031fb8b17b03 (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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Purgatory setup code
 *
 * Copyright IBM Corp. 2018
 *
 * Author(s): Philipp Rudo <prudo@linux.vnet.ibm.com>
 */

#include <linux/linkage.h>
#include <asm/asm-offsets.h>
#include <asm/page.h>
#include <asm/sigp.h>

/* The purgatory is the code running between two kernels. It's main purpose
 * is to verify that the next kernel was not corrupted after load and to
 * start it.
 *
 * If the next kernel is a crash kernel there are some peculiarities to
 * consider:
 *
 * First the purgatory is called twice. Once only to verify the
 * sha digest. So if the crash kernel got corrupted the old kernel can try
 * to trigger a stand-alone dumper. And once to actually load the crash kernel.
 *
 * Second the purgatory also has to swap the crash memory region with its
 * destination at address 0. As the purgatory is part of crash memory this
 * requires some finesse. The tactic here is that the purgatory first copies
 * itself to the end of the destination and then swaps the rest of the
 * memory running from there.
 */

#define bufsz purgatory_end-stack

.macro MEMCPY dst,src,len
	lgr	%r0,\dst
	lgr	%r1,\len
	lgr	%r2,\src
	lgr	%r3,\len

20:	mvcle	%r0,%r2,0
	jo	20b
.endm

.macro MEMSWAP dst,src,buf,len
10:	cghi	\len,bufsz
	jh	11f
	lgr	%r4,\len
	j	12f
11:	lghi	%r4,bufsz

12:	MEMCPY	\buf,\dst,%r4
	MEMCPY	\dst,\src,%r4
	MEMCPY	\src,\buf,%r4

	agr	\dst,%r4
	agr	\src,%r4
	sgr	\len,%r4

	cghi	\len,0
	jh	10b
.endm

.macro START_NEXT_KERNEL base subcode
	lg	%r4,kernel_entry-\base(%r13)
	lg	%r5,load_psw_mask-\base(%r13)
	ogr	%r4,%r5
	stg	%r4,0(%r0)

	xgr	%r0,%r0
	lghi	%r1,\subcode
	diag	%r0,%r1,0x308
.endm

.text
.align PAGE_SIZE
ENTRY(purgatory_start)
	/* The purgatory might be called after a diag308 so better set
	 * architecture and addressing mode.
	 */
	lhi	%r1,1
	sigp	%r1,%r0,SIGP_SET_ARCHITECTURE
	sam64

	larl	%r5,gprregs
	stmg	%r6,%r15,0(%r5)

	basr	%r13,0
.base_crash:

	/* Setup stack */
	larl	%r15,purgatory_end
	aghi	%r15,-160

	/* If the next kernel is KEXEC_TYPE_CRASH the purgatory is called
	 * directly with a flag passed in %r2 whether the purgatory shall do
	 * checksum verification only (%r2 = 0 -> verification only).
	 *
	 * Check now and preserve over C function call by storing in
	 * %r10 whith
	 *	1 -> checksum verification only
	 *	0 -> load new kernel
	 */
	lghi	%r10,0
	lg	%r11,kernel_type-.base_crash(%r13)
	cghi	%r11,1		/* KEXEC_TYPE_CRASH */
	jne	.do_checksum_verification
	cghi	%r2,0		/* checksum verification only */
	jne	.do_checksum_verification
	lghi	%r10,1

.do_checksum_verification:
	brasl	%r14,verify_sha256_digest

	cghi	%r10,1		/* checksum verification only */
	je	.return_old_kernel
	cghi	%r2,0		/* checksum match */
	jne	.disabled_wait

	/* If the next kernel is a crash kernel the purgatory has to swap
	 * the mem regions first.
	 */
	cghi	%r11,1 /* KEXEC_TYPE_CRASH */
	je	.start_crash_kernel

	/* start normal kernel */
	START_NEXT_KERNEL .base_crash 0

.return_old_kernel:
	lmg	%r6,%r15,gprregs-.base_crash(%r13)
	br	%r14

.disabled_wait:
	lpswe	disabled_wait_psw-.base_crash(%r13)

.start_crash_kernel:
	/* Location of purgatory_start in crash memory */
	lgr	%r8,%r13
	aghi	%r8,-(.base_crash-purgatory_start)

	/* Destination for this code i.e. end of memory to be swapped. */
	lg	%r9,crash_size-.base_crash(%r13)
	aghi	%r9,-(purgatory_end-purgatory_start)

	/* Destination in crash memory, i.e. same as r9 but in crash memory. */
	lg	%r10,crash_start-.base_crash(%r13)
	agr	%r10,%r9

	/* Buffer location (in crash memory) and size. As the purgatory is
	 * behind the point of no return it can re-use the stack as buffer.
	 */
	lghi	%r11,bufsz
	larl	%r12,stack

	MEMCPY	%r12,%r9,%r11	/* dst	-> (crash) buf */
	MEMCPY	%r9,%r8,%r11	/* self -> dst */

	/* Jump to new location. */
	lgr	%r7,%r9
	aghi	%r7,.jump_to_dst-purgatory_start
	br	%r7

.jump_to_dst:
	basr	%r13,0
.base_dst:

	/* clear buffer */
	MEMCPY	%r12,%r10,%r11	/* (crash) buf -> (crash) dst */

	/* Load new buffer location after jump */
	larl	%r7,stack
	aghi	%r10,stack-purgatory_start
	MEMCPY	%r10,%r7,%r11	/* (new) buf -> (crash) buf */

	/* Now the code is set up to run from its designated location. Start
	 * swapping the rest of crash memory now.
	 *
	 * The registers will be used as follow:
	 *
	 *	%r0-%r4	reserved for macros defined above
	 *	%r5-%r6 tmp registers
	 *	%r7	pointer to current struct sha region
	 *	%r8	index to iterate over all sha regions
	 *	%r9	pointer in crash memory
	 *	%r10	pointer in old kernel
	 *	%r11	total size (still) to be moved
	 *	%r12	pointer to buffer
	 */
	lgr	%r12,%r7
	lgr	%r11,%r9
	lghi	%r10,0
	lg	%r9,crash_start-.base_dst(%r13)
	lghi	%r8,16	/* KEXEC_SEGMENTS_MAX */
	larl	%r7,purgatory_sha_regions

	j .loop_first

	/* Loop over all purgatory_sha_regions. */
.loop_next:
	aghi	%r8,-1
	cghi	%r8,0
	je	.loop_out

	aghi	%r7,__KEXEC_SHA_REGION_SIZE

.loop_first:
	lg	%r5,__KEXEC_SHA_REGION_START(%r7)
	cghi	%r5,0
	je	.loop_next

	/* Copy [end last sha region, start current sha region) */
	/* Note: kexec_sha_region->start points in crash memory */
	sgr	%r5,%r9
	MEMCPY	%r9,%r10,%r5

	agr	%r9,%r5
	agr	%r10,%r5
	sgr	%r11,%r5

	/* Swap sha region */
	lg	%r6,__KEXEC_SHA_REGION_LEN(%r7)
	MEMSWAP	%r9,%r10,%r12,%r6
	sg	%r11,__KEXEC_SHA_REGION_LEN(%r7)
	j	.loop_next

.loop_out:
	/* Copy rest of crash memory */
	MEMCPY	%r9,%r10,%r11

	/* start crash kernel */
	START_NEXT_KERNEL .base_dst 1


load_psw_mask:
	.long	0x00080000,0x80000000

	.align	8
disabled_wait_psw:
	.quad	0x0002000180000000
	.quad	0x0000000000000000 + .do_checksum_verification

gprregs:
	.rept	10
	.quad	0
	.endr

/* Macro to define a global variable with name and size (in bytes) to be
 * shared with C code.
 *
 * Add the .size and .type attribute to satisfy checks on the Elf_Sym during
 * purgatory load.
 */
.macro GLOBAL_VARIABLE name,size
\name:
	.global \name
	.size	\name,\size
	.type	\name,object
	.skip	\size,0
.endm

GLOBAL_VARIABLE purgatory_sha256_digest,32
GLOBAL_VARIABLE purgatory_sha_regions,16*__KEXEC_SHA_REGION_SIZE
GLOBAL_VARIABLE kernel_entry,8
GLOBAL_VARIABLE kernel_type,8
GLOBAL_VARIABLE crash_start,8
GLOBAL_VARIABLE crash_size,8

	.align	PAGE_SIZE
stack:
	/* The buffer to move this code must be as big as the code. */
	.skip	stack-purgatory_start
	.align	PAGE_SIZE
purgatory_end: