Commit | Line | Data |
---|---|---|
a17ae4c3 | 1 | // SPDX-License-Identifier: GPL-2.0 |
ff6b8ea6 | 2 | /* |
ff6b8ea6 MH |
3 | * ipl/reipl/dump support for Linux on s390. |
4 | * | |
a53c8fab | 5 | * Copyright IBM Corp. 2005, 2012 |
ff6b8ea6 MH |
6 | * Author(s): Michael Holzheu <holzheu@de.ibm.com> |
7 | * Heiko Carstens <heiko.carstens@de.ibm.com> | |
8 | * Volker Sameske <sameske@de.ibm.com> | |
9 | */ | |
10 | ||
11 | #include <linux/types.h> | |
3994a52b PG |
12 | #include <linux/export.h> |
13 | #include <linux/init.h> | |
ff6b8ea6 MH |
14 | #include <linux/device.h> |
15 | #include <linux/delay.h> | |
16 | #include <linux/reboot.h> | |
03a4d208 | 17 | #include <linux/ctype.h> |
0788fea4 | 18 | #include <linux/fs.h> |
5a0e3ad6 | 19 | #include <linux/gfp.h> |
60a0c68d | 20 | #include <linux/crash_dump.h> |
3ab121ab | 21 | #include <linux/debug_locks.h> |
1ec2772e | 22 | #include <asm/diag.h> |
46b05d26 | 23 | #include <asm/ipl.h> |
ff6b8ea6 MH |
24 | #include <asm/smp.h> |
25 | #include <asm/setup.h> | |
26 | #include <asm/cpcmd.h> | |
03a4d208 | 27 | #include <asm/ebcdic.h> |
ab14de6c | 28 | #include <asm/sclp.h> |
159d1ff8 | 29 | #include <asm/checksum.h> |
3ab121ab | 30 | #include <asm/debug.h> |
4857d4bb | 31 | #include <asm/os_info.h> |
49698745 VG |
32 | #include <asm/sections.h> |
33 | #include <asm/boot_data.h> | |
db9492ce | 34 | #include <asm/uv.h> |
638ad34a | 35 | #include "entry.h" |
ff6b8ea6 MH |
36 | |
37 | #define IPL_PARM_BLOCK_VERSION 0 | |
03a4d208 | 38 | |
411ed322 MH |
39 | #define IPL_UNKNOWN_STR "unknown" |
40 | #define IPL_CCW_STR "ccw" | |
41 | #define IPL_FCP_STR "fcp" | |
42 | #define IPL_FCP_DUMP_STR "fcp_dump" | |
43 | #define IPL_NSS_STR "nss" | |
615b04b3 | 44 | |
99ca4e58 MH |
45 | #define DUMP_CCW_STR "ccw" |
46 | #define DUMP_FCP_STR "fcp" | |
47 | #define DUMP_NONE_STR "none" | |
48 | ||
49 | /* | |
50 | * Four shutdown trigger types are supported: | |
51 | * - panic | |
52 | * - halt | |
53 | * - power off | |
54 | * - reipl | |
7dd6b334 | 55 | * - restart |
99ca4e58 MH |
56 | */ |
57 | #define ON_PANIC_STR "on_panic" | |
58 | #define ON_HALT_STR "on_halt" | |
59 | #define ON_POFF_STR "on_poff" | |
60 | #define ON_REIPL_STR "on_reboot" | |
7dd6b334 | 61 | #define ON_RESTART_STR "on_restart" |
99ca4e58 MH |
62 | |
63 | struct shutdown_action; | |
64 | struct shutdown_trigger { | |
65 | char *name; | |
66 | struct shutdown_action *action; | |
67 | }; | |
68 | ||
69 | /* | |
099b7651 | 70 | * The following shutdown action types are supported: |
99ca4e58 MH |
71 | */ |
72 | #define SHUTDOWN_ACTION_IPL_STR "ipl" | |
73 | #define SHUTDOWN_ACTION_REIPL_STR "reipl" | |
74 | #define SHUTDOWN_ACTION_DUMP_STR "dump" | |
75 | #define SHUTDOWN_ACTION_VMCMD_STR "vmcmd" | |
76 | #define SHUTDOWN_ACTION_STOP_STR "stop" | |
099b7651 | 77 | #define SHUTDOWN_ACTION_DUMP_REIPL_STR "dump_reipl" |
99ca4e58 MH |
78 | |
79 | struct shutdown_action { | |
80 | char *name; | |
81 | void (*fn) (struct shutdown_trigger *trigger); | |
82 | int (*init) (void); | |
81088819 | 83 | int init_rc; |
99ca4e58 MH |
84 | }; |
85 | ||
ff6b8ea6 MH |
86 | static char *ipl_type_str(enum ipl_type type) |
87 | { | |
88 | switch (type) { | |
ff6b8ea6 MH |
89 | case IPL_TYPE_CCW: |
90 | return IPL_CCW_STR; | |
91 | case IPL_TYPE_FCP: | |
92 | return IPL_FCP_STR; | |
411ed322 MH |
93 | case IPL_TYPE_FCP_DUMP: |
94 | return IPL_FCP_DUMP_STR; | |
fe355b7f HY |
95 | case IPL_TYPE_NSS: |
96 | return IPL_NSS_STR; | |
ff6b8ea6 MH |
97 | case IPL_TYPE_UNKNOWN: |
98 | default: | |
99 | return IPL_UNKNOWN_STR; | |
100 | } | |
101 | } | |
102 | ||
411ed322 MH |
103 | enum dump_type { |
104 | DUMP_TYPE_NONE = 1, | |
105 | DUMP_TYPE_CCW = 2, | |
106 | DUMP_TYPE_FCP = 4, | |
107 | }; | |
108 | ||
411ed322 MH |
109 | static char *dump_type_str(enum dump_type type) |
110 | { | |
111 | switch (type) { | |
112 | case DUMP_TYPE_NONE: | |
113 | return DUMP_NONE_STR; | |
114 | case DUMP_TYPE_CCW: | |
115 | return DUMP_CCW_STR; | |
116 | case DUMP_TYPE_FCP: | |
117 | return DUMP_FCP_STR; | |
118 | default: | |
119 | return NULL; | |
120 | } | |
121 | } | |
122 | ||
1e941d39 VG |
123 | int __bootdata_preserved(ipl_block_valid); |
124 | struct ipl_parameter_block __bootdata_preserved(ipl_block); | |
9641b8cc MS |
125 | int __bootdata_preserved(ipl_secure_flag); |
126 | ||
127 | unsigned long __bootdata_preserved(ipl_cert_list_addr); | |
128 | unsigned long __bootdata_preserved(ipl_cert_list_size); | |
129 | ||
130 | unsigned long __bootdata(early_ipl_comp_list_addr); | |
131 | unsigned long __bootdata(early_ipl_comp_list_size); | |
a0443fbb | 132 | |
ff6b8ea6 | 133 | static int reipl_capabilities = IPL_TYPE_UNKNOWN; |
fe355b7f | 134 | |
ff6b8ea6 | 135 | static enum ipl_type reipl_type = IPL_TYPE_UNKNOWN; |
ff6b8ea6 MH |
136 | static struct ipl_parameter_block *reipl_block_fcp; |
137 | static struct ipl_parameter_block *reipl_block_ccw; | |
a0443fbb | 138 | static struct ipl_parameter_block *reipl_block_nss; |
099b7651 | 139 | static struct ipl_parameter_block *reipl_block_actual; |
fe355b7f | 140 | |
411ed322 MH |
141 | static int dump_capabilities = DUMP_TYPE_NONE; |
142 | static enum dump_type dump_type = DUMP_TYPE_NONE; | |
ff6b8ea6 MH |
143 | static struct ipl_parameter_block *dump_block_fcp; |
144 | static struct ipl_parameter_block *dump_block_ccw; | |
145 | ||
05dd2530 HC |
146 | static struct sclp_ipl_info sclp_ipl_info; |
147 | ||
1ec2772e | 148 | static inline int __diag308(unsigned long subcode, void *addr) |
ff6b8ea6 | 149 | { |
94c12cc7 | 150 | register unsigned long _addr asm("0") = (unsigned long) addr; |
ff6b8ea6 MH |
151 | register unsigned long _rc asm("1") = 0; |
152 | ||
94c12cc7 MS |
153 | asm volatile( |
154 | " diag %0,%2,0x308\n" | |
6c22c986 | 155 | "0: nopr %%r7\n" |
94c12cc7 | 156 | EX_TABLE(0b,0b) |
ff6b8ea6 | 157 | : "+d" (_addr), "+d" (_rc) |
94c12cc7 | 158 | : "d" (subcode) : "cc", "memory"); |
ff6b8ea6 MH |
159 | return _rc; |
160 | } | |
1ec2772e MS |
161 | |
162 | int diag308(unsigned long subcode, void *addr) | |
163 | { | |
ac1256f8 VG |
164 | if (IS_ENABLED(CONFIG_KASAN)) |
165 | __arch_local_irq_stosm(0x04); /* enable DAT */ | |
1ec2772e MS |
166 | diag_stat_inc(DIAG_STAT_X308); |
167 | return __diag308(subcode, addr); | |
168 | } | |
411ed322 | 169 | EXPORT_SYMBOL_GPL(diag308); |
ff6b8ea6 MH |
170 | |
171 | /* SYSFS */ | |
172 | ||
3be7ae63 | 173 | #define IPL_ATTR_SHOW_FN(_prefix, _name, _format, args...) \ |
9b949165 GKH |
174 | static ssize_t sys_##_prefix##_##_name##_show(struct kobject *kobj, \ |
175 | struct kobj_attribute *attr, \ | |
ff6b8ea6 MH |
176 | char *page) \ |
177 | { \ | |
3be7ae63 SO |
178 | return snprintf(page, PAGE_SIZE, _format, ##args); \ |
179 | } | |
180 | ||
18e22a17 SO |
181 | #define IPL_ATTR_CCW_STORE_FN(_prefix, _name, _ipl_blk) \ |
182 | static ssize_t sys_##_prefix##_##_name##_store(struct kobject *kobj, \ | |
183 | struct kobj_attribute *attr, \ | |
184 | const char *buf, size_t len) \ | |
185 | { \ | |
186 | unsigned long long ssid, devno; \ | |
187 | \ | |
188 | if (sscanf(buf, "0.%llx.%llx\n", &ssid, &devno) != 2) \ | |
189 | return -EINVAL; \ | |
190 | \ | |
191 | if (ssid > __MAX_SSID || devno > __MAX_SUBCHANNEL) \ | |
192 | return -EINVAL; \ | |
193 | \ | |
194 | _ipl_blk.ssid = ssid; \ | |
195 | _ipl_blk.devno = devno; \ | |
196 | return len; \ | |
197 | } | |
198 | ||
199 | #define DEFINE_IPL_CCW_ATTR_RW(_prefix, _name, _ipl_blk) \ | |
200 | IPL_ATTR_SHOW_FN(_prefix, _name, "0.%x.%04x\n", \ | |
201 | _ipl_blk.ssid, _ipl_blk.devno); \ | |
202 | IPL_ATTR_CCW_STORE_FN(_prefix, _name, _ipl_blk); \ | |
203 | static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ | |
204 | __ATTR(_name, (S_IRUGO | S_IWUSR), \ | |
205 | sys_##_prefix##_##_name##_show, \ | |
206 | sys_##_prefix##_##_name##_store) \ | |
207 | ||
3be7ae63 SO |
208 | #define DEFINE_IPL_ATTR_RO(_prefix, _name, _format, _value) \ |
209 | IPL_ATTR_SHOW_FN(_prefix, _name, _format, _value) \ | |
9b949165 | 210 | static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ |
3be7ae63 | 211 | __ATTR(_name, S_IRUGO, sys_##_prefix##_##_name##_show, NULL) |
ff6b8ea6 MH |
212 | |
213 | #define DEFINE_IPL_ATTR_RW(_prefix, _name, _fmt_out, _fmt_in, _value) \ | |
3be7ae63 | 214 | IPL_ATTR_SHOW_FN(_prefix, _name, _fmt_out, (unsigned long long) _value) \ |
9b949165 GKH |
215 | static ssize_t sys_##_prefix##_##_name##_store(struct kobject *kobj, \ |
216 | struct kobj_attribute *attr, \ | |
ff6b8ea6 MH |
217 | const char *buf, size_t len) \ |
218 | { \ | |
219 | unsigned long long value; \ | |
220 | if (sscanf(buf, _fmt_in, &value) != 1) \ | |
221 | return -EINVAL; \ | |
222 | _value = value; \ | |
223 | return len; \ | |
224 | } \ | |
9b949165 | 225 | static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ |
ff6b8ea6 MH |
226 | __ATTR(_name,(S_IRUGO | S_IWUSR), \ |
227 | sys_##_prefix##_##_name##_show, \ | |
3be7ae63 | 228 | sys_##_prefix##_##_name##_store) |
ff6b8ea6 | 229 | |
fe355b7f | 230 | #define DEFINE_IPL_ATTR_STR_RW(_prefix, _name, _fmt_out, _fmt_in, _value)\ |
3be7ae63 | 231 | IPL_ATTR_SHOW_FN(_prefix, _name, _fmt_out, _value) \ |
9b949165 GKH |
232 | static ssize_t sys_##_prefix##_##_name##_store(struct kobject *kobj, \ |
233 | struct kobj_attribute *attr, \ | |
fe355b7f HY |
234 | const char *buf, size_t len) \ |
235 | { \ | |
99ca4e58 | 236 | strncpy(_value, buf, sizeof(_value) - 1); \ |
1d802e24 | 237 | strim(_value); \ |
fe355b7f HY |
238 | return len; \ |
239 | } \ | |
9b949165 | 240 | static struct kobj_attribute sys_##_prefix##_##_name##_attr = \ |
fe355b7f HY |
241 | __ATTR(_name,(S_IRUGO | S_IWUSR), \ |
242 | sys_##_prefix##_##_name##_show, \ | |
3be7ae63 | 243 | sys_##_prefix##_##_name##_store) |
fe355b7f | 244 | |
ff6b8ea6 MH |
245 | /* |
246 | * ipl section | |
247 | */ | |
248 | ||
411ed322 | 249 | static __init enum ipl_type get_ipl_type(void) |
ff6b8ea6 | 250 | { |
d08091ac | 251 | if (!ipl_block_valid) |
ff6b8ea6 | 252 | return IPL_TYPE_UNKNOWN; |
d08091ac | 253 | |
5f1207fb MS |
254 | switch (ipl_block.pb0_hdr.pbt) { |
255 | case IPL_PBT_CCW: | |
ff6b8ea6 | 256 | return IPL_TYPE_CCW; |
5f1207fb MS |
257 | case IPL_PBT_FCP: |
258 | if (ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP) | |
d08091ac VG |
259 | return IPL_TYPE_FCP_DUMP; |
260 | else | |
261 | return IPL_TYPE_FCP; | |
262 | } | |
263 | return IPL_TYPE_UNKNOWN; | |
ff6b8ea6 MH |
264 | } |
265 | ||
411ed322 MH |
266 | struct ipl_info ipl_info; |
267 | EXPORT_SYMBOL_GPL(ipl_info); | |
268 | ||
9b949165 GKH |
269 | static ssize_t ipl_type_show(struct kobject *kobj, struct kobj_attribute *attr, |
270 | char *page) | |
ff6b8ea6 | 271 | { |
411ed322 | 272 | return sprintf(page, "%s\n", ipl_type_str(ipl_info.type)); |
ff6b8ea6 MH |
273 | } |
274 | ||
9b949165 | 275 | static struct kobj_attribute sys_ipl_type_attr = __ATTR_RO(ipl_type); |
ff6b8ea6 | 276 | |
9641b8cc MS |
277 | static ssize_t ipl_secure_show(struct kobject *kobj, |
278 | struct kobj_attribute *attr, char *page) | |
279 | { | |
280 | return sprintf(page, "%i\n", !!ipl_secure_flag); | |
281 | } | |
282 | ||
283 | static struct kobj_attribute sys_ipl_secure_attr = | |
284 | __ATTR(secure, 0444, ipl_secure_show, NULL); | |
285 | ||
a0443fbb HB |
286 | static ssize_t ipl_vm_parm_show(struct kobject *kobj, |
287 | struct kobj_attribute *attr, char *page) | |
288 | { | |
289 | char parm[DIAG308_VMPARM_SIZE + 1] = {}; | |
290 | ||
5f1207fb | 291 | if (ipl_block_valid && (ipl_block.pb0_hdr.pbt == IPL_PBT_CCW)) |
49698745 | 292 | ipl_block_get_ascii_vmparm(parm, sizeof(parm), &ipl_block); |
a0443fbb HB |
293 | return sprintf(page, "%s\n", parm); |
294 | } | |
295 | ||
296 | static struct kobj_attribute sys_ipl_vm_parm_attr = | |
297 | __ATTR(parm, S_IRUGO, ipl_vm_parm_show, NULL); | |
298 | ||
9b949165 GKH |
299 | static ssize_t sys_ipl_device_show(struct kobject *kobj, |
300 | struct kobj_attribute *attr, char *page) | |
ff6b8ea6 | 301 | { |
411ed322 | 302 | switch (ipl_info.type) { |
ff6b8ea6 | 303 | case IPL_TYPE_CCW: |
86c74d86 MS |
304 | return sprintf(page, "0.%x.%04x\n", ipl_block.ccw.ssid, |
305 | ipl_block.ccw.devno); | |
ff6b8ea6 | 306 | case IPL_TYPE_FCP: |
411ed322 | 307 | case IPL_TYPE_FCP_DUMP: |
86c74d86 | 308 | return sprintf(page, "0.0.%04x\n", ipl_block.fcp.devno); |
ff6b8ea6 MH |
309 | default: |
310 | return 0; | |
311 | } | |
312 | } | |
313 | ||
9b949165 | 314 | static struct kobj_attribute sys_ipl_device_attr = |
ff6b8ea6 MH |
315 | __ATTR(device, S_IRUGO, sys_ipl_device_show, NULL); |
316 | ||
2c3c8bea CW |
317 | static ssize_t ipl_parameter_read(struct file *filp, struct kobject *kobj, |
318 | struct bin_attribute *attr, char *buf, | |
319 | loff_t off, size_t count) | |
ff6b8ea6 | 320 | { |
bdbfe185 VG |
321 | return memory_read_from_buffer(buf, count, &off, &ipl_block, |
322 | ipl_block.hdr.len); | |
ff6b8ea6 | 323 | } |
22d557ab SO |
324 | static struct bin_attribute ipl_parameter_attr = |
325 | __BIN_ATTR(binary_parameter, S_IRUGO, ipl_parameter_read, NULL, | |
326 | PAGE_SIZE); | |
ff6b8ea6 | 327 | |
2c3c8bea CW |
328 | static ssize_t ipl_scp_data_read(struct file *filp, struct kobject *kobj, |
329 | struct bin_attribute *attr, char *buf, | |
330 | loff_t off, size_t count) | |
ff6b8ea6 | 331 | { |
86c74d86 MS |
332 | unsigned int size = ipl_block.fcp.scp_data_len; |
333 | void *scp_data = &ipl_block.fcp.scp_data; | |
ff6b8ea6 | 334 | |
0788fea4 | 335 | return memory_read_from_buffer(buf, count, &off, scp_data, size); |
ff6b8ea6 | 336 | } |
22d557ab SO |
337 | static struct bin_attribute ipl_scp_data_attr = |
338 | __BIN_ATTR(scp_data, S_IRUGO, ipl_scp_data_read, NULL, PAGE_SIZE); | |
ff6b8ea6 | 339 | |
22d557ab SO |
340 | static struct bin_attribute *ipl_fcp_bin_attrs[] = { |
341 | &ipl_parameter_attr, | |
342 | &ipl_scp_data_attr, | |
343 | NULL, | |
ff6b8ea6 MH |
344 | }; |
345 | ||
346 | /* FCP ipl device attributes */ | |
347 | ||
bdbfe185 | 348 | DEFINE_IPL_ATTR_RO(ipl_fcp, wwpn, "0x%016llx\n", |
86c74d86 | 349 | (unsigned long long)ipl_block.fcp.wwpn); |
bdbfe185 | 350 | DEFINE_IPL_ATTR_RO(ipl_fcp, lun, "0x%016llx\n", |
86c74d86 | 351 | (unsigned long long)ipl_block.fcp.lun); |
bdbfe185 | 352 | DEFINE_IPL_ATTR_RO(ipl_fcp, bootprog, "%lld\n", |
86c74d86 | 353 | (unsigned long long)ipl_block.fcp.bootprog); |
bdbfe185 | 354 | DEFINE_IPL_ATTR_RO(ipl_fcp, br_lba, "%lld\n", |
86c74d86 | 355 | (unsigned long long)ipl_block.fcp.br_lba); |
ff6b8ea6 | 356 | |
9b949165 GKH |
357 | static ssize_t ipl_ccw_loadparm_show(struct kobject *kobj, |
358 | struct kobj_attribute *attr, char *page) | |
03a4d208 MH |
359 | { |
360 | char loadparm[LOADPARM_LEN + 1] = {}; | |
361 | ||
05dd2530 | 362 | if (!sclp_ipl_info.is_valid) |
03a4d208 | 363 | return sprintf(page, "#unknown#\n"); |
05dd2530 | 364 | memcpy(loadparm, &sclp_ipl_info.loadparm, LOADPARM_LEN); |
03a4d208 | 365 | EBCASC(loadparm, LOADPARM_LEN); |
1d802e24 | 366 | strim(loadparm); |
03a4d208 MH |
367 | return sprintf(page, "%s\n", loadparm); |
368 | } | |
369 | ||
9b949165 | 370 | static struct kobj_attribute sys_ipl_ccw_loadparm_attr = |
03a4d208 MH |
371 | __ATTR(loadparm, 0444, ipl_ccw_loadparm_show, NULL); |
372 | ||
69928601 MH |
373 | static struct attribute *ipl_fcp_attrs[] = { |
374 | &sys_ipl_type_attr.attr, | |
375 | &sys_ipl_device_attr.attr, | |
376 | &sys_ipl_fcp_wwpn_attr.attr, | |
377 | &sys_ipl_fcp_lun_attr.attr, | |
378 | &sys_ipl_fcp_bootprog_attr.attr, | |
379 | &sys_ipl_fcp_br_lba_attr.attr, | |
380 | &sys_ipl_ccw_loadparm_attr.attr, | |
9641b8cc | 381 | &sys_ipl_secure_attr.attr, |
69928601 MH |
382 | NULL, |
383 | }; | |
384 | ||
385 | static struct attribute_group ipl_fcp_attr_group = { | |
386 | .attrs = ipl_fcp_attrs, | |
22d557ab | 387 | .bin_attrs = ipl_fcp_bin_attrs, |
69928601 MH |
388 | }; |
389 | ||
390 | /* CCW ipl device attributes */ | |
391 | ||
a0443fbb HB |
392 | static struct attribute *ipl_ccw_attrs_vm[] = { |
393 | &sys_ipl_type_attr.attr, | |
394 | &sys_ipl_device_attr.attr, | |
395 | &sys_ipl_ccw_loadparm_attr.attr, | |
396 | &sys_ipl_vm_parm_attr.attr, | |
9641b8cc | 397 | &sys_ipl_secure_attr.attr, |
a0443fbb HB |
398 | NULL, |
399 | }; | |
400 | ||
401 | static struct attribute *ipl_ccw_attrs_lpar[] = { | |
ff6b8ea6 MH |
402 | &sys_ipl_type_attr.attr, |
403 | &sys_ipl_device_attr.attr, | |
03a4d208 | 404 | &sys_ipl_ccw_loadparm_attr.attr, |
9641b8cc | 405 | &sys_ipl_secure_attr.attr, |
ff6b8ea6 MH |
406 | NULL, |
407 | }; | |
408 | ||
a0443fbb HB |
409 | static struct attribute_group ipl_ccw_attr_group_vm = { |
410 | .attrs = ipl_ccw_attrs_vm, | |
411 | }; | |
412 | ||
413 | static struct attribute_group ipl_ccw_attr_group_lpar = { | |
414 | .attrs = ipl_ccw_attrs_lpar | |
ff6b8ea6 MH |
415 | }; |
416 | ||
417 | /* UNKNOWN ipl device attributes */ | |
418 | ||
419 | static struct attribute *ipl_unknown_attrs[] = { | |
420 | &sys_ipl_type_attr.attr, | |
421 | NULL, | |
422 | }; | |
423 | ||
424 | static struct attribute_group ipl_unknown_attr_group = { | |
425 | .attrs = ipl_unknown_attrs, | |
426 | }; | |
427 | ||
d91885be | 428 | static struct kset *ipl_kset; |
ff6b8ea6 | 429 | |
2c2df118 | 430 | static void __ipl_run(void *unused) |
99ca4e58 | 431 | { |
d768bd89 | 432 | __bpon(); |
0599eead | 433 | diag308(DIAG308_LOAD_CLEAR, NULL); |
99ca4e58 MH |
434 | } |
435 | ||
2c2df118 HC |
436 | static void ipl_run(struct shutdown_trigger *trigger) |
437 | { | |
8b646bd7 | 438 | smp_call_ipl_cpu(__ipl_run, NULL); |
2c2df118 HC |
439 | } |
440 | ||
2bc89b5e | 441 | static int __init ipl_init(void) |
99ca4e58 MH |
442 | { |
443 | int rc; | |
444 | ||
445 | ipl_kset = kset_create_and_add("ipl", NULL, firmware_kobj); | |
446 | if (!ipl_kset) { | |
447 | rc = -ENOMEM; | |
448 | goto out; | |
449 | } | |
450 | switch (ipl_info.type) { | |
451 | case IPL_TYPE_CCW: | |
a0443fbb HB |
452 | if (MACHINE_IS_VM) |
453 | rc = sysfs_create_group(&ipl_kset->kobj, | |
454 | &ipl_ccw_attr_group_vm); | |
455 | else | |
456 | rc = sysfs_create_group(&ipl_kset->kobj, | |
457 | &ipl_ccw_attr_group_lpar); | |
99ca4e58 MH |
458 | break; |
459 | case IPL_TYPE_FCP: | |
460 | case IPL_TYPE_FCP_DUMP: | |
22d557ab | 461 | rc = sysfs_create_group(&ipl_kset->kobj, &ipl_fcp_attr_group); |
99ca4e58 | 462 | break; |
99ca4e58 MH |
463 | default: |
464 | rc = sysfs_create_group(&ipl_kset->kobj, | |
465 | &ipl_unknown_attr_group); | |
466 | break; | |
467 | } | |
468 | out: | |
469 | if (rc) | |
470 | panic("ipl_init failed: rc = %i\n", rc); | |
471 | ||
472 | return 0; | |
473 | } | |
474 | ||
2bc89b5e HC |
475 | static struct shutdown_action __refdata ipl_action = { |
476 | .name = SHUTDOWN_ACTION_IPL_STR, | |
477 | .fn = ipl_run, | |
478 | .init = ipl_init, | |
479 | }; | |
99ca4e58 | 480 | |
ff6b8ea6 | 481 | /* |
99ca4e58 | 482 | * reipl shutdown action: Reboot Linux on shutdown. |
ff6b8ea6 MH |
483 | */ |
484 | ||
a0443fbb HB |
485 | /* VM IPL PARM attributes */ |
486 | static ssize_t reipl_generic_vmparm_show(struct ipl_parameter_block *ipb, | |
487 | char *page) | |
488 | { | |
489 | char vmparm[DIAG308_VMPARM_SIZE + 1] = {}; | |
490 | ||
49698745 | 491 | ipl_block_get_ascii_vmparm(vmparm, sizeof(vmparm), ipb); |
a0443fbb HB |
492 | return sprintf(page, "%s\n", vmparm); |
493 | } | |
494 | ||
495 | static ssize_t reipl_generic_vmparm_store(struct ipl_parameter_block *ipb, | |
496 | size_t vmparm_max, | |
497 | const char *buf, size_t len) | |
498 | { | |
499 | int i, ip_len; | |
500 | ||
501 | /* ignore trailing newline */ | |
502 | ip_len = len; | |
503 | if ((len > 0) && (buf[len - 1] == '\n')) | |
504 | ip_len--; | |
505 | ||
506 | if (ip_len > vmparm_max) | |
507 | return -EINVAL; | |
508 | ||
509 | /* parm is used to store kernel options, check for common chars */ | |
510 | for (i = 0; i < ip_len; i++) | |
511 | if (!(isalnum(buf[i]) || isascii(buf[i]) || isprint(buf[i]))) | |
512 | return -EINVAL; | |
513 | ||
86c74d86 MS |
514 | memset(ipb->ccw.vm_parm, 0, DIAG308_VMPARM_SIZE); |
515 | ipb->ccw.vm_parm_len = ip_len; | |
a0443fbb | 516 | if (ip_len > 0) { |
5f1207fb | 517 | ipb->ccw.vm_flags |= IPL_PB0_CCW_VM_FLAG_VP; |
86c74d86 MS |
518 | memcpy(ipb->ccw.vm_parm, buf, ip_len); |
519 | ASCEBC(ipb->ccw.vm_parm, ip_len); | |
a0443fbb | 520 | } else { |
5f1207fb | 521 | ipb->ccw.vm_flags &= ~IPL_PB0_CCW_VM_FLAG_VP; |
a0443fbb HB |
522 | } |
523 | ||
524 | return len; | |
525 | } | |
526 | ||
527 | /* NSS wrapper */ | |
528 | static ssize_t reipl_nss_vmparm_show(struct kobject *kobj, | |
529 | struct kobj_attribute *attr, char *page) | |
530 | { | |
531 | return reipl_generic_vmparm_show(reipl_block_nss, page); | |
532 | } | |
533 | ||
534 | static ssize_t reipl_nss_vmparm_store(struct kobject *kobj, | |
535 | struct kobj_attribute *attr, | |
536 | const char *buf, size_t len) | |
537 | { | |
538 | return reipl_generic_vmparm_store(reipl_block_nss, 56, buf, len); | |
539 | } | |
540 | ||
541 | /* CCW wrapper */ | |
542 | static ssize_t reipl_ccw_vmparm_show(struct kobject *kobj, | |
543 | struct kobj_attribute *attr, char *page) | |
544 | { | |
545 | return reipl_generic_vmparm_show(reipl_block_ccw, page); | |
546 | } | |
547 | ||
548 | static ssize_t reipl_ccw_vmparm_store(struct kobject *kobj, | |
549 | struct kobj_attribute *attr, | |
550 | const char *buf, size_t len) | |
551 | { | |
552 | return reipl_generic_vmparm_store(reipl_block_ccw, 64, buf, len); | |
553 | } | |
554 | ||
555 | static struct kobj_attribute sys_reipl_nss_vmparm_attr = | |
556 | __ATTR(parm, S_IRUGO | S_IWUSR, reipl_nss_vmparm_show, | |
557 | reipl_nss_vmparm_store); | |
558 | static struct kobj_attribute sys_reipl_ccw_vmparm_attr = | |
559 | __ATTR(parm, S_IRUGO | S_IWUSR, reipl_ccw_vmparm_show, | |
560 | reipl_ccw_vmparm_store); | |
561 | ||
ff6b8ea6 MH |
562 | /* FCP reipl device attributes */ |
563 | ||
2c3c8bea | 564 | static ssize_t reipl_fcp_scpdata_read(struct file *filp, struct kobject *kobj, |
684d2fd4 HB |
565 | struct bin_attribute *attr, |
566 | char *buf, loff_t off, size_t count) | |
567 | { | |
86c74d86 MS |
568 | size_t size = reipl_block_fcp->fcp.scp_data_len; |
569 | void *scp_data = reipl_block_fcp->fcp.scp_data; | |
684d2fd4 HB |
570 | |
571 | return memory_read_from_buffer(buf, count, &off, scp_data, size); | |
572 | } | |
573 | ||
2c3c8bea | 574 | static ssize_t reipl_fcp_scpdata_write(struct file *filp, struct kobject *kobj, |
684d2fd4 HB |
575 | struct bin_attribute *attr, |
576 | char *buf, loff_t off, size_t count) | |
577 | { | |
e0bedada | 578 | size_t scpdata_len = count; |
684d2fd4 | 579 | size_t padding; |
684d2fd4 | 580 | |
684d2fd4 | 581 | |
e0bedada SO |
582 | if (off) |
583 | return -EINVAL; | |
684d2fd4 | 584 | |
86c74d86 | 585 | memcpy(reipl_block_fcp->fcp.scp_data, buf, count); |
684d2fd4 HB |
586 | if (scpdata_len % 8) { |
587 | padding = 8 - (scpdata_len % 8); | |
86c74d86 | 588 | memset(reipl_block_fcp->fcp.scp_data + scpdata_len, |
684d2fd4 HB |
589 | 0, padding); |
590 | scpdata_len += padding; | |
591 | } | |
592 | ||
5f1207fb MS |
593 | reipl_block_fcp->hdr.len = IPL_BP_FCP_LEN + scpdata_len; |
594 | reipl_block_fcp->fcp.len = IPL_BP0_FCP_LEN + scpdata_len; | |
86c74d86 | 595 | reipl_block_fcp->fcp.scp_data_len = scpdata_len; |
684d2fd4 HB |
596 | |
597 | return count; | |
598 | } | |
22d557ab SO |
599 | static struct bin_attribute sys_reipl_fcp_scp_data_attr = |
600 | __BIN_ATTR(scp_data, (S_IRUGO | S_IWUSR), reipl_fcp_scpdata_read, | |
e0bedada | 601 | reipl_fcp_scpdata_write, DIAG308_SCPDATA_SIZE); |
684d2fd4 | 602 | |
22d557ab SO |
603 | static struct bin_attribute *reipl_fcp_bin_attrs[] = { |
604 | &sys_reipl_fcp_scp_data_attr, | |
605 | NULL, | |
684d2fd4 HB |
606 | }; |
607 | ||
eda4ddf7 | 608 | DEFINE_IPL_ATTR_RW(reipl_fcp, wwpn, "0x%016llx\n", "%llx\n", |
86c74d86 | 609 | reipl_block_fcp->fcp.wwpn); |
eda4ddf7 | 610 | DEFINE_IPL_ATTR_RW(reipl_fcp, lun, "0x%016llx\n", "%llx\n", |
86c74d86 | 611 | reipl_block_fcp->fcp.lun); |
ff6b8ea6 | 612 | DEFINE_IPL_ATTR_RW(reipl_fcp, bootprog, "%lld\n", "%lld\n", |
86c74d86 | 613 | reipl_block_fcp->fcp.bootprog); |
ff6b8ea6 | 614 | DEFINE_IPL_ATTR_RW(reipl_fcp, br_lba, "%lld\n", "%lld\n", |
86c74d86 | 615 | reipl_block_fcp->fcp.br_lba); |
ff6b8ea6 | 616 | DEFINE_IPL_ATTR_RW(reipl_fcp, device, "0.0.%04llx\n", "0.0.%llx\n", |
86c74d86 | 617 | reipl_block_fcp->fcp.devno); |
ff6b8ea6 | 618 | |
a0443fbb HB |
619 | static void reipl_get_ascii_loadparm(char *loadparm, |
620 | struct ipl_parameter_block *ibp) | |
03a4d208 | 621 | { |
5f1207fb | 622 | memcpy(loadparm, ibp->common.loadparm, LOADPARM_LEN); |
03a4d208 MH |
623 | EBCASC(loadparm, LOADPARM_LEN); |
624 | loadparm[LOADPARM_LEN] = 0; | |
1d802e24 | 625 | strim(loadparm); |
03a4d208 MH |
626 | } |
627 | ||
a0443fbb HB |
628 | static ssize_t reipl_generic_loadparm_show(struct ipl_parameter_block *ipb, |
629 | char *page) | |
03a4d208 MH |
630 | { |
631 | char buf[LOADPARM_LEN + 1]; | |
632 | ||
a0443fbb | 633 | reipl_get_ascii_loadparm(buf, ipb); |
03a4d208 MH |
634 | return sprintf(page, "%s\n", buf); |
635 | } | |
636 | ||
a0443fbb HB |
637 | static ssize_t reipl_generic_loadparm_store(struct ipl_parameter_block *ipb, |
638 | const char *buf, size_t len) | |
03a4d208 MH |
639 | { |
640 | int i, lp_len; | |
641 | ||
642 | /* ignore trailing newline */ | |
643 | lp_len = len; | |
644 | if ((len > 0) && (buf[len - 1] == '\n')) | |
645 | lp_len--; | |
646 | /* loadparm can have max 8 characters and must not start with a blank */ | |
647 | if ((lp_len > LOADPARM_LEN) || ((lp_len > 0) && (buf[0] == ' '))) | |
648 | return -EINVAL; | |
649 | /* loadparm can only contain "a-z,A-Z,0-9,SP,." */ | |
650 | for (i = 0; i < lp_len; i++) { | |
651 | if (isalpha(buf[i]) || isdigit(buf[i]) || (buf[i] == ' ') || | |
652 | (buf[i] == '.')) | |
653 | continue; | |
654 | return -EINVAL; | |
655 | } | |
656 | /* initialize loadparm with blanks */ | |
5f1207fb | 657 | memset(ipb->common.loadparm, ' ', LOADPARM_LEN); |
03a4d208 | 658 | /* copy and convert to ebcdic */ |
5f1207fb MS |
659 | memcpy(ipb->common.loadparm, buf, lp_len); |
660 | ASCEBC(ipb->common.loadparm, LOADPARM_LEN); | |
661 | ipb->common.flags |= IPL_PB0_FLAG_LOADPARM; | |
03a4d208 MH |
662 | return len; |
663 | } | |
664 | ||
69928601 MH |
665 | /* FCP wrapper */ |
666 | static ssize_t reipl_fcp_loadparm_show(struct kobject *kobj, | |
667 | struct kobj_attribute *attr, char *page) | |
668 | { | |
669 | return reipl_generic_loadparm_show(reipl_block_fcp, page); | |
670 | } | |
671 | ||
672 | static ssize_t reipl_fcp_loadparm_store(struct kobject *kobj, | |
673 | struct kobj_attribute *attr, | |
674 | const char *buf, size_t len) | |
675 | { | |
676 | return reipl_generic_loadparm_store(reipl_block_fcp, buf, len); | |
677 | } | |
678 | ||
679 | static struct kobj_attribute sys_reipl_fcp_loadparm_attr = | |
680 | __ATTR(loadparm, S_IRUGO | S_IWUSR, reipl_fcp_loadparm_show, | |
681 | reipl_fcp_loadparm_store); | |
682 | ||
683 | static struct attribute *reipl_fcp_attrs[] = { | |
684 | &sys_reipl_fcp_device_attr.attr, | |
685 | &sys_reipl_fcp_wwpn_attr.attr, | |
686 | &sys_reipl_fcp_lun_attr.attr, | |
687 | &sys_reipl_fcp_bootprog_attr.attr, | |
688 | &sys_reipl_fcp_br_lba_attr.attr, | |
689 | &sys_reipl_fcp_loadparm_attr.attr, | |
690 | NULL, | |
691 | }; | |
692 | ||
693 | static struct attribute_group reipl_fcp_attr_group = { | |
694 | .attrs = reipl_fcp_attrs, | |
22d557ab | 695 | .bin_attrs = reipl_fcp_bin_attrs, |
69928601 MH |
696 | }; |
697 | ||
698 | /* CCW reipl device attributes */ | |
86c74d86 | 699 | DEFINE_IPL_CCW_ATTR_RW(reipl_ccw, device, reipl_block_ccw->ccw); |
69928601 | 700 | |
a0443fbb HB |
701 | /* NSS wrapper */ |
702 | static ssize_t reipl_nss_loadparm_show(struct kobject *kobj, | |
703 | struct kobj_attribute *attr, char *page) | |
704 | { | |
705 | return reipl_generic_loadparm_show(reipl_block_nss, page); | |
706 | } | |
707 | ||
708 | static ssize_t reipl_nss_loadparm_store(struct kobject *kobj, | |
709 | struct kobj_attribute *attr, | |
710 | const char *buf, size_t len) | |
711 | { | |
712 | return reipl_generic_loadparm_store(reipl_block_nss, buf, len); | |
713 | } | |
714 | ||
715 | /* CCW wrapper */ | |
716 | static ssize_t reipl_ccw_loadparm_show(struct kobject *kobj, | |
717 | struct kobj_attribute *attr, char *page) | |
718 | { | |
719 | return reipl_generic_loadparm_show(reipl_block_ccw, page); | |
720 | } | |
721 | ||
722 | static ssize_t reipl_ccw_loadparm_store(struct kobject *kobj, | |
723 | struct kobj_attribute *attr, | |
724 | const char *buf, size_t len) | |
725 | { | |
726 | return reipl_generic_loadparm_store(reipl_block_ccw, buf, len); | |
727 | } | |
728 | ||
9b949165 | 729 | static struct kobj_attribute sys_reipl_ccw_loadparm_attr = |
a0443fbb HB |
730 | __ATTR(loadparm, S_IRUGO | S_IWUSR, reipl_ccw_loadparm_show, |
731 | reipl_ccw_loadparm_store); | |
732 | ||
733 | static struct attribute *reipl_ccw_attrs_vm[] = { | |
734 | &sys_reipl_ccw_device_attr.attr, | |
735 | &sys_reipl_ccw_loadparm_attr.attr, | |
736 | &sys_reipl_ccw_vmparm_attr.attr, | |
737 | NULL, | |
738 | }; | |
03a4d208 | 739 | |
a0443fbb | 740 | static struct attribute *reipl_ccw_attrs_lpar[] = { |
ff6b8ea6 | 741 | &sys_reipl_ccw_device_attr.attr, |
03a4d208 | 742 | &sys_reipl_ccw_loadparm_attr.attr, |
ff6b8ea6 MH |
743 | NULL, |
744 | }; | |
745 | ||
a0443fbb | 746 | static struct attribute_group reipl_ccw_attr_group_vm = { |
ff6b8ea6 | 747 | .name = IPL_CCW_STR, |
a0443fbb HB |
748 | .attrs = reipl_ccw_attrs_vm, |
749 | }; | |
750 | ||
751 | static struct attribute_group reipl_ccw_attr_group_lpar = { | |
752 | .name = IPL_CCW_STR, | |
753 | .attrs = reipl_ccw_attrs_lpar, | |
ff6b8ea6 MH |
754 | }; |
755 | ||
fe355b7f HY |
756 | |
757 | /* NSS reipl device attributes */ | |
a0443fbb HB |
758 | static void reipl_get_ascii_nss_name(char *dst, |
759 | struct ipl_parameter_block *ipb) | |
760 | { | |
86c74d86 | 761 | memcpy(dst, ipb->ccw.nss_name, NSS_NAME_SIZE); |
a0443fbb HB |
762 | EBCASC(dst, NSS_NAME_SIZE); |
763 | dst[NSS_NAME_SIZE] = 0; | |
764 | } | |
765 | ||
766 | static ssize_t reipl_nss_name_show(struct kobject *kobj, | |
767 | struct kobj_attribute *attr, char *page) | |
768 | { | |
769 | char nss_name[NSS_NAME_SIZE + 1] = {}; | |
770 | ||
771 | reipl_get_ascii_nss_name(nss_name, reipl_block_nss); | |
772 | return sprintf(page, "%s\n", nss_name); | |
773 | } | |
774 | ||
775 | static ssize_t reipl_nss_name_store(struct kobject *kobj, | |
776 | struct kobj_attribute *attr, | |
777 | const char *buf, size_t len) | |
778 | { | |
779 | int nss_len; | |
780 | ||
781 | /* ignore trailing newline */ | |
782 | nss_len = len; | |
783 | if ((len > 0) && (buf[len - 1] == '\n')) | |
784 | nss_len--; | |
fe355b7f | 785 | |
a0443fbb HB |
786 | if (nss_len > NSS_NAME_SIZE) |
787 | return -EINVAL; | |
788 | ||
86c74d86 | 789 | memset(reipl_block_nss->ccw.nss_name, 0x40, NSS_NAME_SIZE); |
a0443fbb | 790 | if (nss_len > 0) { |
5f1207fb | 791 | reipl_block_nss->ccw.vm_flags |= IPL_PB0_CCW_VM_FLAG_NSS; |
86c74d86 MS |
792 | memcpy(reipl_block_nss->ccw.nss_name, buf, nss_len); |
793 | ASCEBC(reipl_block_nss->ccw.nss_name, nss_len); | |
794 | EBC_TOUPPER(reipl_block_nss->ccw.nss_name, nss_len); | |
a0443fbb | 795 | } else { |
5f1207fb | 796 | reipl_block_nss->ccw.vm_flags &= ~IPL_PB0_CCW_VM_FLAG_NSS; |
a0443fbb HB |
797 | } |
798 | ||
799 | return len; | |
800 | } | |
801 | ||
802 | static struct kobj_attribute sys_reipl_nss_name_attr = | |
803 | __ATTR(name, S_IRUGO | S_IWUSR, reipl_nss_name_show, | |
804 | reipl_nss_name_store); | |
805 | ||
806 | static struct kobj_attribute sys_reipl_nss_loadparm_attr = | |
807 | __ATTR(loadparm, S_IRUGO | S_IWUSR, reipl_nss_loadparm_show, | |
808 | reipl_nss_loadparm_store); | |
fe355b7f HY |
809 | |
810 | static struct attribute *reipl_nss_attrs[] = { | |
811 | &sys_reipl_nss_name_attr.attr, | |
a0443fbb HB |
812 | &sys_reipl_nss_loadparm_attr.attr, |
813 | &sys_reipl_nss_vmparm_attr.attr, | |
fe355b7f HY |
814 | NULL, |
815 | }; | |
816 | ||
817 | static struct attribute_group reipl_nss_attr_group = { | |
818 | .name = IPL_NSS_STR, | |
819 | .attrs = reipl_nss_attrs, | |
820 | }; | |
821 | ||
3b967847 | 822 | void set_os_info_reipl_block(void) |
4857d4bb | 823 | { |
4857d4bb | 824 | os_info_entry_add(OS_INFO_REIPL_BLOCK, reipl_block_actual, |
3b967847 | 825 | reipl_block_actual->hdr.len); |
4857d4bb MH |
826 | } |
827 | ||
ff6b8ea6 MH |
828 | /* reipl type */ |
829 | ||
830 | static int reipl_set_type(enum ipl_type type) | |
831 | { | |
832 | if (!(reipl_capabilities & type)) | |
833 | return -EINVAL; | |
834 | ||
835 | switch(type) { | |
836 | case IPL_TYPE_CCW: | |
3b967847 | 837 | reipl_block_actual = reipl_block_ccw; |
ff6b8ea6 MH |
838 | break; |
839 | case IPL_TYPE_FCP: | |
3b967847 | 840 | reipl_block_actual = reipl_block_fcp; |
411ed322 | 841 | break; |
fe355b7f | 842 | case IPL_TYPE_NSS: |
3b967847 | 843 | reipl_block_actual = reipl_block_nss; |
411ed322 | 844 | break; |
ff6b8ea6 | 845 | default: |
96c0cdbc | 846 | break; |
ff6b8ea6 MH |
847 | } |
848 | reipl_type = type; | |
849 | return 0; | |
850 | } | |
851 | ||
9b949165 GKH |
852 | static ssize_t reipl_type_show(struct kobject *kobj, |
853 | struct kobj_attribute *attr, char *page) | |
ff6b8ea6 MH |
854 | { |
855 | return sprintf(page, "%s\n", ipl_type_str(reipl_type)); | |
856 | } | |
857 | ||
9b949165 GKH |
858 | static ssize_t reipl_type_store(struct kobject *kobj, |
859 | struct kobj_attribute *attr, | |
860 | const char *buf, size_t len) | |
ff6b8ea6 MH |
861 | { |
862 | int rc = -EINVAL; | |
863 | ||
864 | if (strncmp(buf, IPL_CCW_STR, strlen(IPL_CCW_STR)) == 0) | |
865 | rc = reipl_set_type(IPL_TYPE_CCW); | |
866 | else if (strncmp(buf, IPL_FCP_STR, strlen(IPL_FCP_STR)) == 0) | |
867 | rc = reipl_set_type(IPL_TYPE_FCP); | |
fe355b7f HY |
868 | else if (strncmp(buf, IPL_NSS_STR, strlen(IPL_NSS_STR)) == 0) |
869 | rc = reipl_set_type(IPL_TYPE_NSS); | |
ff6b8ea6 MH |
870 | return (rc != 0) ? rc : len; |
871 | } | |
872 | ||
9b949165 | 873 | static struct kobj_attribute reipl_type_attr = |
99ca4e58 | 874 | __ATTR(reipl_type, 0644, reipl_type_show, reipl_type_store); |
ff6b8ea6 | 875 | |
d91885be | 876 | static struct kset *reipl_kset; |
684d2fd4 | 877 | static struct kset *reipl_fcp_kset; |
ff6b8ea6 | 878 | |
2c2df118 | 879 | static void __reipl_run(void *unused) |
ff6b8ea6 | 880 | { |
96c0cdbc VG |
881 | switch (reipl_type) { |
882 | case IPL_TYPE_CCW: | |
db9492ce | 883 | uv_set_shared(__pa(reipl_block_ccw)); |
ff6b8ea6 | 884 | diag308(DIAG308_SET, reipl_block_ccw); |
db9492ce | 885 | uv_remove_shared(__pa(reipl_block_ccw)); |
4130b28f | 886 | diag308(DIAG308_LOAD_CLEAR, NULL); |
ff6b8ea6 | 887 | break; |
96c0cdbc | 888 | case IPL_TYPE_FCP: |
db9492ce | 889 | uv_set_shared(__pa(reipl_block_fcp)); |
ff6b8ea6 | 890 | diag308(DIAG308_SET, reipl_block_fcp); |
db9492ce | 891 | uv_remove_shared(__pa(reipl_block_fcp)); |
0599eead | 892 | diag308(DIAG308_LOAD_CLEAR, NULL); |
ff6b8ea6 | 893 | break; |
96c0cdbc | 894 | case IPL_TYPE_NSS: |
db9492ce | 895 | uv_set_shared(__pa(reipl_block_nss)); |
a0443fbb | 896 | diag308(DIAG308_SET, reipl_block_nss); |
db9492ce | 897 | uv_remove_shared(__pa(reipl_block_nss)); |
0599eead | 898 | diag308(DIAG308_LOAD_CLEAR, NULL); |
a0443fbb | 899 | break; |
96c0cdbc | 900 | case IPL_TYPE_UNKNOWN: |
0599eead | 901 | diag308(DIAG308_LOAD_CLEAR, NULL); |
ff6b8ea6 | 902 | break; |
96c0cdbc | 903 | case IPL_TYPE_FCP_DUMP: |
411ed322 | 904 | break; |
ff6b8ea6 | 905 | } |
208e5591 | 906 | disabled_wait((unsigned long) __builtin_return_address(0)); |
ff6b8ea6 MH |
907 | } |
908 | ||
2c2df118 HC |
909 | static void reipl_run(struct shutdown_trigger *trigger) |
910 | { | |
8b646bd7 | 911 | smp_call_ipl_cpu(__reipl_run, NULL); |
2c2df118 HC |
912 | } |
913 | ||
a0443fbb | 914 | static void reipl_block_ccw_init(struct ipl_parameter_block *ipb) |
ff6b8ea6 | 915 | { |
5f1207fb | 916 | ipb->hdr.len = IPL_BP_CCW_LEN; |
a0443fbb | 917 | ipb->hdr.version = IPL_PARM_BLOCK_VERSION; |
5f1207fb MS |
918 | ipb->pb0_hdr.len = IPL_BP0_CCW_LEN; |
919 | ipb->pb0_hdr.pbt = IPL_PBT_CCW; | |
a0443fbb | 920 | } |
ff6b8ea6 | 921 | |
a0443fbb HB |
922 | static void reipl_block_ccw_fill_parms(struct ipl_parameter_block *ipb) |
923 | { | |
924 | /* LOADPARM */ | |
925 | /* check if read scp info worked and set loadparm */ | |
926 | if (sclp_ipl_info.is_valid) | |
5f1207fb | 927 | memcpy(ipb->ccw.loadparm, &sclp_ipl_info.loadparm, LOADPARM_LEN); |
a0443fbb HB |
928 | else |
929 | /* read scp info failed: set empty loadparm (EBCDIC blanks) */ | |
5f1207fb MS |
930 | memset(ipb->ccw.loadparm, 0x40, LOADPARM_LEN); |
931 | ipb->ccw.flags = IPL_PB0_FLAG_LOADPARM; | |
a0443fbb HB |
932 | |
933 | /* VM PARM */ | |
a0832b3a | 934 | if (MACHINE_IS_VM && ipl_block_valid && |
5f1207fb | 935 | (ipl_block.ccw.vm_flags & IPL_PB0_CCW_VM_FLAG_VP)) { |
a0443fbb | 936 | |
5f1207fb | 937 | ipb->ccw.vm_flags |= IPL_PB0_CCW_VM_FLAG_VP; |
86c74d86 MS |
938 | ipb->ccw.vm_parm_len = ipl_block.ccw.vm_parm_len; |
939 | memcpy(ipb->ccw.vm_parm, | |
940 | ipl_block.ccw.vm_parm, DIAG308_VMPARM_SIZE); | |
a0443fbb | 941 | } |
ff6b8ea6 MH |
942 | } |
943 | ||
99ca4e58 | 944 | static int __init reipl_nss_init(void) |
ff6b8ea6 MH |
945 | { |
946 | int rc; | |
947 | ||
99ca4e58 MH |
948 | if (!MACHINE_IS_VM) |
949 | return 0; | |
a0443fbb HB |
950 | |
951 | reipl_block_nss = (void *) get_zeroed_page(GFP_KERNEL); | |
952 | if (!reipl_block_nss) | |
953 | return -ENOMEM; | |
954 | ||
99ca4e58 | 955 | rc = sysfs_create_group(&reipl_kset->kobj, &reipl_nss_attr_group); |
fe355b7f HY |
956 | if (rc) |
957 | return rc; | |
a0443fbb HB |
958 | |
959 | reipl_block_ccw_init(reipl_block_nss); | |
fe355b7f HY |
960 | reipl_capabilities |= IPL_TYPE_NSS; |
961 | return 0; | |
962 | } | |
963 | ||
ff6b8ea6 MH |
964 | static int __init reipl_ccw_init(void) |
965 | { | |
966 | int rc; | |
967 | ||
968 | reipl_block_ccw = (void *) get_zeroed_page(GFP_KERNEL); | |
969 | if (!reipl_block_ccw) | |
970 | return -ENOMEM; | |
a0443fbb | 971 | |
d485235b VG |
972 | rc = sysfs_create_group(&reipl_kset->kobj, |
973 | MACHINE_IS_VM ? &reipl_ccw_attr_group_vm | |
974 | : &reipl_ccw_attr_group_lpar); | |
a0443fbb HB |
975 | if (rc) |
976 | return rc; | |
977 | ||
978 | reipl_block_ccw_init(reipl_block_ccw); | |
979 | if (ipl_info.type == IPL_TYPE_CCW) { | |
86c74d86 MS |
980 | reipl_block_ccw->ccw.ssid = ipl_block.ccw.ssid; |
981 | reipl_block_ccw->ccw.devno = ipl_block.ccw.devno; | |
a0443fbb HB |
982 | reipl_block_ccw_fill_parms(reipl_block_ccw); |
983 | } | |
984 | ||
ff6b8ea6 MH |
985 | reipl_capabilities |= IPL_TYPE_CCW; |
986 | return 0; | |
987 | } | |
988 | ||
989 | static int __init reipl_fcp_init(void) | |
990 | { | |
991 | int rc; | |
992 | ||
ff6b8ea6 MH |
993 | reipl_block_fcp = (void *) get_zeroed_page(GFP_KERNEL); |
994 | if (!reipl_block_fcp) | |
995 | return -ENOMEM; | |
684d2fd4 HB |
996 | |
997 | /* sysfs: create fcp kset for mixing attr group and bin attrs */ | |
998 | reipl_fcp_kset = kset_create_and_add(IPL_FCP_STR, NULL, | |
999 | &reipl_kset->kobj); | |
798620fb | 1000 | if (!reipl_fcp_kset) { |
684d2fd4 HB |
1001 | free_page((unsigned long) reipl_block_fcp); |
1002 | return -ENOMEM; | |
1003 | } | |
1004 | ||
1005 | rc = sysfs_create_group(&reipl_fcp_kset->kobj, &reipl_fcp_attr_group); | |
ff6b8ea6 | 1006 | if (rc) { |
684d2fd4 HB |
1007 | kset_unregister(reipl_fcp_kset); |
1008 | free_page((unsigned long) reipl_block_fcp); | |
ff6b8ea6 MH |
1009 | return rc; |
1010 | } | |
684d2fd4 | 1011 | |
69928601 | 1012 | if (ipl_info.type == IPL_TYPE_FCP) { |
bdbfe185 | 1013 | memcpy(reipl_block_fcp, &ipl_block, sizeof(ipl_block)); |
69928601 MH |
1014 | /* |
1015 | * Fix loadparm: There are systems where the (SCSI) LOADPARM | |
1016 | * is invalid in the SCSI IPL parameter block, so take it | |
1017 | * always from sclp_ipl_info. | |
1018 | */ | |
5f1207fb | 1019 | memcpy(reipl_block_fcp->fcp.loadparm, sclp_ipl_info.loadparm, |
69928601 MH |
1020 | LOADPARM_LEN); |
1021 | } else { | |
5f1207fb | 1022 | reipl_block_fcp->hdr.len = IPL_BP_FCP_LEN; |
ff6b8ea6 | 1023 | reipl_block_fcp->hdr.version = IPL_PARM_BLOCK_VERSION; |
5f1207fb MS |
1024 | reipl_block_fcp->fcp.len = IPL_BP0_FCP_LEN; |
1025 | reipl_block_fcp->fcp.pbt = IPL_PBT_FCP; | |
1026 | reipl_block_fcp->fcp.opt = IPL_PB0_FCP_OPT_IPL; | |
ff6b8ea6 MH |
1027 | } |
1028 | reipl_capabilities |= IPL_TYPE_FCP; | |
1029 | return 0; | |
1030 | } | |
1031 | ||
4857d4bb MH |
1032 | static int __init reipl_type_init(void) |
1033 | { | |
1034 | enum ipl_type reipl_type = ipl_info.type; | |
1035 | struct ipl_parameter_block *reipl_block; | |
1036 | unsigned long size; | |
1037 | ||
1038 | reipl_block = os_info_old_entry(OS_INFO_REIPL_BLOCK, &size); | |
1039 | if (!reipl_block) | |
1040 | goto out; | |
1041 | /* | |
1042 | * If we have an OS info reipl block, this will be used | |
1043 | */ | |
5f1207fb | 1044 | if (reipl_block->pb0_hdr.pbt == IPL_PBT_FCP) { |
4857d4bb MH |
1045 | memcpy(reipl_block_fcp, reipl_block, size); |
1046 | reipl_type = IPL_TYPE_FCP; | |
5f1207fb | 1047 | } else if (reipl_block->pb0_hdr.pbt == IPL_PBT_CCW) { |
4857d4bb MH |
1048 | memcpy(reipl_block_ccw, reipl_block, size); |
1049 | reipl_type = IPL_TYPE_CCW; | |
1050 | } | |
1051 | out: | |
1052 | return reipl_set_type(reipl_type); | |
1053 | } | |
1054 | ||
2bc89b5e | 1055 | static int __init reipl_init(void) |
ff6b8ea6 MH |
1056 | { |
1057 | int rc; | |
1058 | ||
f62ed9e3 | 1059 | reipl_kset = kset_create_and_add("reipl", NULL, firmware_kobj); |
d91885be GKH |
1060 | if (!reipl_kset) |
1061 | return -ENOMEM; | |
1062 | rc = sysfs_create_file(&reipl_kset->kobj, &reipl_type_attr.attr); | |
ff6b8ea6 | 1063 | if (rc) { |
d91885be | 1064 | kset_unregister(reipl_kset); |
ff6b8ea6 MH |
1065 | return rc; |
1066 | } | |
1067 | rc = reipl_ccw_init(); | |
1068 | if (rc) | |
1069 | return rc; | |
1070 | rc = reipl_fcp_init(); | |
fe355b7f HY |
1071 | if (rc) |
1072 | return rc; | |
1073 | rc = reipl_nss_init(); | |
ff6b8ea6 MH |
1074 | if (rc) |
1075 | return rc; | |
4857d4bb | 1076 | return reipl_type_init(); |
ff6b8ea6 MH |
1077 | } |
1078 | ||
2bc89b5e HC |
1079 | static struct shutdown_action __refdata reipl_action = { |
1080 | .name = SHUTDOWN_ACTION_REIPL_STR, | |
1081 | .fn = reipl_run, | |
1082 | .init = reipl_init, | |
1083 | }; | |
99ca4e58 MH |
1084 | |
1085 | /* | |
1086 | * dump shutdown action: Dump Linux on shutdown. | |
1087 | */ | |
1088 | ||
1089 | /* FCP dump device attributes */ | |
1090 | ||
eda4ddf7 | 1091 | DEFINE_IPL_ATTR_RW(dump_fcp, wwpn, "0x%016llx\n", "%llx\n", |
86c74d86 | 1092 | dump_block_fcp->fcp.wwpn); |
eda4ddf7 | 1093 | DEFINE_IPL_ATTR_RW(dump_fcp, lun, "0x%016llx\n", "%llx\n", |
86c74d86 | 1094 | dump_block_fcp->fcp.lun); |
99ca4e58 | 1095 | DEFINE_IPL_ATTR_RW(dump_fcp, bootprog, "%lld\n", "%lld\n", |
86c74d86 | 1096 | dump_block_fcp->fcp.bootprog); |
99ca4e58 | 1097 | DEFINE_IPL_ATTR_RW(dump_fcp, br_lba, "%lld\n", "%lld\n", |
86c74d86 | 1098 | dump_block_fcp->fcp.br_lba); |
99ca4e58 | 1099 | DEFINE_IPL_ATTR_RW(dump_fcp, device, "0.0.%04llx\n", "0.0.%llx\n", |
86c74d86 | 1100 | dump_block_fcp->fcp.devno); |
99ca4e58 MH |
1101 | |
1102 | static struct attribute *dump_fcp_attrs[] = { | |
1103 | &sys_dump_fcp_device_attr.attr, | |
1104 | &sys_dump_fcp_wwpn_attr.attr, | |
1105 | &sys_dump_fcp_lun_attr.attr, | |
1106 | &sys_dump_fcp_bootprog_attr.attr, | |
1107 | &sys_dump_fcp_br_lba_attr.attr, | |
1108 | NULL, | |
1109 | }; | |
1110 | ||
1111 | static struct attribute_group dump_fcp_attr_group = { | |
1112 | .name = IPL_FCP_STR, | |
1113 | .attrs = dump_fcp_attrs, | |
1114 | }; | |
1115 | ||
1116 | /* CCW dump device attributes */ | |
86c74d86 | 1117 | DEFINE_IPL_CCW_ATTR_RW(dump_ccw, device, dump_block_ccw->ccw); |
99ca4e58 MH |
1118 | |
1119 | static struct attribute *dump_ccw_attrs[] = { | |
1120 | &sys_dump_ccw_device_attr.attr, | |
1121 | NULL, | |
1122 | }; | |
1123 | ||
1124 | static struct attribute_group dump_ccw_attr_group = { | |
1125 | .name = IPL_CCW_STR, | |
1126 | .attrs = dump_ccw_attrs, | |
1127 | }; | |
1128 | ||
1129 | /* dump type */ | |
1130 | ||
1131 | static int dump_set_type(enum dump_type type) | |
1132 | { | |
1133 | if (!(dump_capabilities & type)) | |
1134 | return -EINVAL; | |
99ca4e58 MH |
1135 | dump_type = type; |
1136 | return 0; | |
1137 | } | |
1138 | ||
1139 | static ssize_t dump_type_show(struct kobject *kobj, | |
1140 | struct kobj_attribute *attr, char *page) | |
1141 | { | |
1142 | return sprintf(page, "%s\n", dump_type_str(dump_type)); | |
1143 | } | |
1144 | ||
1145 | static ssize_t dump_type_store(struct kobject *kobj, | |
1146 | struct kobj_attribute *attr, | |
1147 | const char *buf, size_t len) | |
1148 | { | |
1149 | int rc = -EINVAL; | |
1150 | ||
1151 | if (strncmp(buf, DUMP_NONE_STR, strlen(DUMP_NONE_STR)) == 0) | |
1152 | rc = dump_set_type(DUMP_TYPE_NONE); | |
1153 | else if (strncmp(buf, DUMP_CCW_STR, strlen(DUMP_CCW_STR)) == 0) | |
1154 | rc = dump_set_type(DUMP_TYPE_CCW); | |
1155 | else if (strncmp(buf, DUMP_FCP_STR, strlen(DUMP_FCP_STR)) == 0) | |
1156 | rc = dump_set_type(DUMP_TYPE_FCP); | |
1157 | return (rc != 0) ? rc : len; | |
1158 | } | |
1159 | ||
1160 | static struct kobj_attribute dump_type_attr = | |
1161 | __ATTR(dump_type, 0644, dump_type_show, dump_type_store); | |
1162 | ||
1163 | static struct kset *dump_kset; | |
1164 | ||
0894b3ae MH |
1165 | static void diag308_dump(void *dump_block) |
1166 | { | |
db9492ce | 1167 | uv_set_shared(__pa(dump_block)); |
0894b3ae | 1168 | diag308(DIAG308_SET, dump_block); |
db9492ce | 1169 | uv_remove_shared(__pa(dump_block)); |
0894b3ae | 1170 | while (1) { |
0599eead | 1171 | if (diag308(DIAG308_LOAD_NORMAL_DUMP, NULL) != 0x302) |
0894b3ae MH |
1172 | break; |
1173 | udelay_simple(USEC_PER_SEC); | |
1174 | } | |
1175 | } | |
1176 | ||
2c2df118 | 1177 | static void __dump_run(void *unused) |
99ca4e58 | 1178 | { |
96c0cdbc VG |
1179 | switch (dump_type) { |
1180 | case DUMP_TYPE_CCW: | |
0894b3ae | 1181 | diag308_dump(dump_block_ccw); |
99ca4e58 | 1182 | break; |
96c0cdbc | 1183 | case DUMP_TYPE_FCP: |
0894b3ae | 1184 | diag308_dump(dump_block_fcp); |
99ca4e58 | 1185 | break; |
2c2df118 HC |
1186 | default: |
1187 | break; | |
99ca4e58 | 1188 | } |
2c2df118 HC |
1189 | } |
1190 | ||
1191 | static void dump_run(struct shutdown_trigger *trigger) | |
1192 | { | |
96c0cdbc | 1193 | if (dump_type == DUMP_TYPE_NONE) |
2c2df118 HC |
1194 | return; |
1195 | smp_send_stop(); | |
8b646bd7 | 1196 | smp_call_ipl_cpu(__dump_run, NULL); |
99ca4e58 MH |
1197 | } |
1198 | ||
ff6b8ea6 MH |
1199 | static int __init dump_ccw_init(void) |
1200 | { | |
1201 | int rc; | |
1202 | ||
1203 | dump_block_ccw = (void *) get_zeroed_page(GFP_KERNEL); | |
1204 | if (!dump_block_ccw) | |
1205 | return -ENOMEM; | |
d91885be | 1206 | rc = sysfs_create_group(&dump_kset->kobj, &dump_ccw_attr_group); |
ff6b8ea6 MH |
1207 | if (rc) { |
1208 | free_page((unsigned long)dump_block_ccw); | |
1209 | return rc; | |
1210 | } | |
5f1207fb | 1211 | dump_block_ccw->hdr.len = IPL_BP_CCW_LEN; |
ff6b8ea6 | 1212 | dump_block_ccw->hdr.version = IPL_PARM_BLOCK_VERSION; |
5f1207fb MS |
1213 | dump_block_ccw->ccw.len = IPL_BP0_CCW_LEN; |
1214 | dump_block_ccw->ccw.pbt = IPL_PBT_CCW; | |
411ed322 | 1215 | dump_capabilities |= DUMP_TYPE_CCW; |
ff6b8ea6 MH |
1216 | return 0; |
1217 | } | |
1218 | ||
ff6b8ea6 MH |
1219 | static int __init dump_fcp_init(void) |
1220 | { | |
1221 | int rc; | |
1222 | ||
05dd2530 | 1223 | if (!sclp_ipl_info.has_dump) |
ff6b8ea6 | 1224 | return 0; /* LDIPL DUMP is not installed */ |
ff6b8ea6 MH |
1225 | dump_block_fcp = (void *) get_zeroed_page(GFP_KERNEL); |
1226 | if (!dump_block_fcp) | |
1227 | return -ENOMEM; | |
d91885be | 1228 | rc = sysfs_create_group(&dump_kset->kobj, &dump_fcp_attr_group); |
ff6b8ea6 MH |
1229 | if (rc) { |
1230 | free_page((unsigned long)dump_block_fcp); | |
1231 | return rc; | |
1232 | } | |
5f1207fb | 1233 | dump_block_fcp->hdr.len = IPL_BP_FCP_LEN; |
ff6b8ea6 | 1234 | dump_block_fcp->hdr.version = IPL_PARM_BLOCK_VERSION; |
5f1207fb MS |
1235 | dump_block_fcp->fcp.len = IPL_BP0_FCP_LEN; |
1236 | dump_block_fcp->fcp.pbt = IPL_PBT_FCP; | |
1237 | dump_block_fcp->fcp.opt = IPL_PB0_FCP_OPT_DUMP; | |
411ed322 | 1238 | dump_capabilities |= DUMP_TYPE_FCP; |
ff6b8ea6 MH |
1239 | return 0; |
1240 | } | |
1241 | ||
2bc89b5e | 1242 | static int __init dump_init(void) |
ff6b8ea6 MH |
1243 | { |
1244 | int rc; | |
1245 | ||
f62ed9e3 | 1246 | dump_kset = kset_create_and_add("dump", NULL, firmware_kobj); |
d91885be GKH |
1247 | if (!dump_kset) |
1248 | return -ENOMEM; | |
99ca4e58 | 1249 | rc = sysfs_create_file(&dump_kset->kobj, &dump_type_attr.attr); |
ff6b8ea6 | 1250 | if (rc) { |
d91885be | 1251 | kset_unregister(dump_kset); |
ff6b8ea6 MH |
1252 | return rc; |
1253 | } | |
1254 | rc = dump_ccw_init(); | |
1255 | if (rc) | |
1256 | return rc; | |
1257 | rc = dump_fcp_init(); | |
1258 | if (rc) | |
1259 | return rc; | |
411ed322 | 1260 | dump_set_type(DUMP_TYPE_NONE); |
ff6b8ea6 MH |
1261 | return 0; |
1262 | } | |
1263 | ||
2bc89b5e HC |
1264 | static struct shutdown_action __refdata dump_action = { |
1265 | .name = SHUTDOWN_ACTION_DUMP_STR, | |
1266 | .fn = dump_run, | |
1267 | .init = dump_init, | |
1268 | }; | |
99ca4e58 | 1269 | |
099b7651 FM |
1270 | static void dump_reipl_run(struct shutdown_trigger *trigger) |
1271 | { | |
a7df7a94 | 1272 | unsigned long ipib = (unsigned long) reipl_block_actual; |
fbe76568 HC |
1273 | unsigned int csum; |
1274 | ||
90b3baa2 HC |
1275 | csum = (__force unsigned int) |
1276 | csum_partial(reipl_block_actual, reipl_block_actual->hdr.len, 0); | |
fbe76568 HC |
1277 | mem_assign_absolute(S390_lowcore.ipib, ipib); |
1278 | mem_assign_absolute(S390_lowcore.ipib_checksum, csum); | |
099b7651 FM |
1279 | dump_run(trigger); |
1280 | } | |
1281 | ||
099b7651 FM |
1282 | static struct shutdown_action __refdata dump_reipl_action = { |
1283 | .name = SHUTDOWN_ACTION_DUMP_REIPL_STR, | |
1284 | .fn = dump_reipl_run, | |
099b7651 FM |
1285 | }; |
1286 | ||
99ca4e58 MH |
1287 | /* |
1288 | * vmcmd shutdown action: Trigger vm command on shutdown. | |
1289 | */ | |
1290 | ||
1291 | static char vmcmd_on_reboot[128]; | |
1292 | static char vmcmd_on_panic[128]; | |
1293 | static char vmcmd_on_halt[128]; | |
1294 | static char vmcmd_on_poff[128]; | |
7dd6b334 | 1295 | static char vmcmd_on_restart[128]; |
99ca4e58 MH |
1296 | |
1297 | DEFINE_IPL_ATTR_STR_RW(vmcmd, on_reboot, "%s\n", "%s\n", vmcmd_on_reboot); | |
1298 | DEFINE_IPL_ATTR_STR_RW(vmcmd, on_panic, "%s\n", "%s\n", vmcmd_on_panic); | |
1299 | DEFINE_IPL_ATTR_STR_RW(vmcmd, on_halt, "%s\n", "%s\n", vmcmd_on_halt); | |
1300 | DEFINE_IPL_ATTR_STR_RW(vmcmd, on_poff, "%s\n", "%s\n", vmcmd_on_poff); | |
7dd6b334 | 1301 | DEFINE_IPL_ATTR_STR_RW(vmcmd, on_restart, "%s\n", "%s\n", vmcmd_on_restart); |
99ca4e58 MH |
1302 | |
1303 | static struct attribute *vmcmd_attrs[] = { | |
1304 | &sys_vmcmd_on_reboot_attr.attr, | |
1305 | &sys_vmcmd_on_panic_attr.attr, | |
1306 | &sys_vmcmd_on_halt_attr.attr, | |
1307 | &sys_vmcmd_on_poff_attr.attr, | |
7dd6b334 | 1308 | &sys_vmcmd_on_restart_attr.attr, |
99ca4e58 MH |
1309 | NULL, |
1310 | }; | |
1311 | ||
1312 | static struct attribute_group vmcmd_attr_group = { | |
1313 | .attrs = vmcmd_attrs, | |
1314 | }; | |
1315 | ||
1316 | static struct kset *vmcmd_kset; | |
1317 | ||
1318 | static void vmcmd_run(struct shutdown_trigger *trigger) | |
ff6b8ea6 | 1319 | { |
8143adaf | 1320 | char *cmd; |
99ca4e58 MH |
1321 | |
1322 | if (strcmp(trigger->name, ON_REIPL_STR) == 0) | |
1323 | cmd = vmcmd_on_reboot; | |
1324 | else if (strcmp(trigger->name, ON_PANIC_STR) == 0) | |
1325 | cmd = vmcmd_on_panic; | |
1326 | else if (strcmp(trigger->name, ON_HALT_STR) == 0) | |
1327 | cmd = vmcmd_on_halt; | |
1328 | else if (strcmp(trigger->name, ON_POFF_STR) == 0) | |
1329 | cmd = vmcmd_on_poff; | |
7dd6b334 MH |
1330 | else if (strcmp(trigger->name, ON_RESTART_STR) == 0) |
1331 | cmd = vmcmd_on_restart; | |
99ca4e58 MH |
1332 | else |
1333 | return; | |
1334 | ||
1335 | if (strlen(cmd) == 0) | |
1336 | return; | |
8143adaf | 1337 | __cpcmd(cmd, NULL, 0, NULL); |
99ca4e58 MH |
1338 | } |
1339 | ||
1340 | static int vmcmd_init(void) | |
1341 | { | |
1342 | if (!MACHINE_IS_VM) | |
b8e660b8 | 1343 | return -EOPNOTSUPP; |
99ca4e58 MH |
1344 | vmcmd_kset = kset_create_and_add("vmcmd", NULL, firmware_kobj); |
1345 | if (!vmcmd_kset) | |
1346 | return -ENOMEM; | |
1347 | return sysfs_create_group(&vmcmd_kset->kobj, &vmcmd_attr_group); | |
1348 | } | |
1349 | ||
1350 | static struct shutdown_action vmcmd_action = {SHUTDOWN_ACTION_VMCMD_STR, | |
1351 | vmcmd_run, vmcmd_init}; | |
1352 | ||
1353 | /* | |
1354 | * stop shutdown action: Stop Linux on shutdown. | |
1355 | */ | |
1356 | ||
1357 | static void stop_run(struct shutdown_trigger *trigger) | |
1358 | { | |
e1202eda MH |
1359 | if (strcmp(trigger->name, ON_PANIC_STR) == 0 || |
1360 | strcmp(trigger->name, ON_RESTART_STR) == 0) | |
c6547497 | 1361 | disabled_wait((unsigned long) __builtin_return_address(0)); |
8b646bd7 | 1362 | smp_stop_cpu(); |
99ca4e58 MH |
1363 | } |
1364 | ||
1365 | static struct shutdown_action stop_action = {SHUTDOWN_ACTION_STOP_STR, | |
1366 | stop_run, NULL}; | |
1367 | ||
1368 | /* action list */ | |
1369 | ||
1370 | static struct shutdown_action *shutdown_actions_list[] = { | |
099b7651 FM |
1371 | &ipl_action, &reipl_action, &dump_reipl_action, &dump_action, |
1372 | &vmcmd_action, &stop_action}; | |
99ca4e58 MH |
1373 | #define SHUTDOWN_ACTIONS_COUNT (sizeof(shutdown_actions_list) / sizeof(void *)) |
1374 | ||
1375 | /* | |
1376 | * Trigger section | |
1377 | */ | |
1378 | ||
1379 | static struct kset *shutdown_actions_kset; | |
1380 | ||
1381 | static int set_trigger(const char *buf, struct shutdown_trigger *trigger, | |
1382 | size_t len) | |
1383 | { | |
1384 | int i; | |
099b7651 | 1385 | |
99ca4e58 | 1386 | for (i = 0; i < SHUTDOWN_ACTIONS_COUNT; i++) { |
099b7651 | 1387 | if (sysfs_streq(buf, shutdown_actions_list[i]->name)) { |
81088819 FM |
1388 | if (shutdown_actions_list[i]->init_rc) { |
1389 | return shutdown_actions_list[i]->init_rc; | |
1390 | } else { | |
1391 | trigger->action = shutdown_actions_list[i]; | |
1392 | return len; | |
1393 | } | |
99ca4e58 MH |
1394 | } |
1395 | } | |
1396 | return -EINVAL; | |
1397 | } | |
1398 | ||
1399 | /* on reipl */ | |
1400 | ||
1401 | static struct shutdown_trigger on_reboot_trigger = {ON_REIPL_STR, | |
1402 | &reipl_action}; | |
1403 | ||
1404 | static ssize_t on_reboot_show(struct kobject *kobj, | |
1405 | struct kobj_attribute *attr, char *page) | |
1406 | { | |
1407 | return sprintf(page, "%s\n", on_reboot_trigger.action->name); | |
1408 | } | |
1409 | ||
1410 | static ssize_t on_reboot_store(struct kobject *kobj, | |
1411 | struct kobj_attribute *attr, | |
1412 | const char *buf, size_t len) | |
1413 | { | |
1414 | return set_trigger(buf, &on_reboot_trigger, len); | |
1415 | } | |
0f024379 | 1416 | static struct kobj_attribute on_reboot_attr = __ATTR_RW(on_reboot); |
99ca4e58 MH |
1417 | |
1418 | static void do_machine_restart(char *__unused) | |
1419 | { | |
1420 | smp_send_stop(); | |
1421 | on_reboot_trigger.action->fn(&on_reboot_trigger); | |
1422 | reipl_run(NULL); | |
1423 | } | |
1424 | void (*_machine_restart)(char *command) = do_machine_restart; | |
1425 | ||
1426 | /* on panic */ | |
1427 | ||
1428 | static struct shutdown_trigger on_panic_trigger = {ON_PANIC_STR, &stop_action}; | |
1429 | ||
1430 | static ssize_t on_panic_show(struct kobject *kobj, | |
1431 | struct kobj_attribute *attr, char *page) | |
1432 | { | |
1433 | return sprintf(page, "%s\n", on_panic_trigger.action->name); | |
1434 | } | |
1435 | ||
1436 | static ssize_t on_panic_store(struct kobject *kobj, | |
1437 | struct kobj_attribute *attr, | |
1438 | const char *buf, size_t len) | |
1439 | { | |
1440 | return set_trigger(buf, &on_panic_trigger, len); | |
1441 | } | |
0f024379 | 1442 | static struct kobj_attribute on_panic_attr = __ATTR_RW(on_panic); |
99ca4e58 MH |
1443 | |
1444 | static void do_panic(void) | |
1445 | { | |
3ab121ab | 1446 | lgr_info_log(); |
99ca4e58 MH |
1447 | on_panic_trigger.action->fn(&on_panic_trigger); |
1448 | stop_run(&on_panic_trigger); | |
1449 | } | |
1450 | ||
7dd6b334 MH |
1451 | /* on restart */ |
1452 | ||
1453 | static struct shutdown_trigger on_restart_trigger = {ON_RESTART_STR, | |
e1202eda | 1454 | &stop_action}; |
7dd6b334 MH |
1455 | |
1456 | static ssize_t on_restart_show(struct kobject *kobj, | |
1457 | struct kobj_attribute *attr, char *page) | |
1458 | { | |
1459 | return sprintf(page, "%s\n", on_restart_trigger.action->name); | |
1460 | } | |
1461 | ||
1462 | static ssize_t on_restart_store(struct kobject *kobj, | |
1463 | struct kobj_attribute *attr, | |
1464 | const char *buf, size_t len) | |
1465 | { | |
1466 | return set_trigger(buf, &on_restart_trigger, len); | |
1467 | } | |
0f024379 | 1468 | static struct kobj_attribute on_restart_attr = __ATTR_RW(on_restart); |
7dd6b334 | 1469 | |
8b646bd7 | 1470 | static void __do_restart(void *ignore) |
7dd6b334 | 1471 | { |
fa7c0043 | 1472 | __arch_local_irq_stosm(0x04); /* enable DAT */ |
7dd6b334 | 1473 | smp_send_stop(); |
60a0c68d MH |
1474 | #ifdef CONFIG_CRASH_DUMP |
1475 | crash_kexec(NULL); | |
1476 | #endif | |
7dd6b334 MH |
1477 | on_restart_trigger.action->fn(&on_restart_trigger); |
1478 | stop_run(&on_restart_trigger); | |
1479 | } | |
1480 | ||
8b646bd7 MS |
1481 | void do_restart(void) |
1482 | { | |
3ab121ab MH |
1483 | tracing_off(); |
1484 | debug_locks_off(); | |
1485 | lgr_info_log(); | |
8b646bd7 MS |
1486 | smp_call_online_cpu(__do_restart, NULL); |
1487 | } | |
1488 | ||
99ca4e58 MH |
1489 | /* on halt */ |
1490 | ||
1491 | static struct shutdown_trigger on_halt_trigger = {ON_HALT_STR, &stop_action}; | |
1492 | ||
1493 | static ssize_t on_halt_show(struct kobject *kobj, | |
1494 | struct kobj_attribute *attr, char *page) | |
1495 | { | |
1496 | return sprintf(page, "%s\n", on_halt_trigger.action->name); | |
1497 | } | |
1498 | ||
1499 | static ssize_t on_halt_store(struct kobject *kobj, | |
1500 | struct kobj_attribute *attr, | |
1501 | const char *buf, size_t len) | |
1502 | { | |
1503 | return set_trigger(buf, &on_halt_trigger, len); | |
1504 | } | |
0f024379 | 1505 | static struct kobj_attribute on_halt_attr = __ATTR_RW(on_halt); |
99ca4e58 MH |
1506 | |
1507 | static void do_machine_halt(void) | |
1508 | { | |
1509 | smp_send_stop(); | |
1510 | on_halt_trigger.action->fn(&on_halt_trigger); | |
1511 | stop_run(&on_halt_trigger); | |
1512 | } | |
1513 | void (*_machine_halt)(void) = do_machine_halt; | |
1514 | ||
1515 | /* on power off */ | |
1516 | ||
1517 | static struct shutdown_trigger on_poff_trigger = {ON_POFF_STR, &stop_action}; | |
1518 | ||
1519 | static ssize_t on_poff_show(struct kobject *kobj, | |
1520 | struct kobj_attribute *attr, char *page) | |
1521 | { | |
1522 | return sprintf(page, "%s\n", on_poff_trigger.action->name); | |
1523 | } | |
1524 | ||
1525 | static ssize_t on_poff_store(struct kobject *kobj, | |
1526 | struct kobj_attribute *attr, | |
1527 | const char *buf, size_t len) | |
1528 | { | |
1529 | return set_trigger(buf, &on_poff_trigger, len); | |
1530 | } | |
0f024379 | 1531 | static struct kobj_attribute on_poff_attr = __ATTR_RW(on_poff); |
99ca4e58 MH |
1532 | |
1533 | static void do_machine_power_off(void) | |
1534 | { | |
1535 | smp_send_stop(); | |
1536 | on_poff_trigger.action->fn(&on_poff_trigger); | |
1537 | stop_run(&on_poff_trigger); | |
1538 | } | |
1539 | void (*_machine_power_off)(void) = do_machine_power_off; | |
1540 | ||
0f024379 SO |
1541 | static struct attribute *shutdown_action_attrs[] = { |
1542 | &on_restart_attr.attr, | |
1543 | &on_reboot_attr.attr, | |
1544 | &on_panic_attr.attr, | |
1545 | &on_halt_attr.attr, | |
1546 | &on_poff_attr.attr, | |
1547 | NULL, | |
1548 | }; | |
1549 | ||
1550 | static struct attribute_group shutdown_action_attr_group = { | |
1551 | .attrs = shutdown_action_attrs, | |
1552 | }; | |
1553 | ||
99ca4e58 MH |
1554 | static void __init shutdown_triggers_init(void) |
1555 | { | |
d91885be | 1556 | shutdown_actions_kset = kset_create_and_add("shutdown_actions", NULL, |
f62ed9e3 | 1557 | firmware_kobj); |
d91885be | 1558 | if (!shutdown_actions_kset) |
99ca4e58 | 1559 | goto fail; |
0f024379 SO |
1560 | if (sysfs_create_group(&shutdown_actions_kset->kobj, |
1561 | &shutdown_action_attr_group)) | |
7dd6b334 | 1562 | goto fail; |
99ca4e58 MH |
1563 | return; |
1564 | fail: | |
1565 | panic("shutdown_triggers_init failed\n"); | |
1566 | } | |
1567 | ||
1568 | static void __init shutdown_actions_init(void) | |
1569 | { | |
1570 | int i; | |
1571 | ||
1572 | for (i = 0; i < SHUTDOWN_ACTIONS_COUNT; i++) { | |
1573 | if (!shutdown_actions_list[i]->init) | |
1574 | continue; | |
81088819 FM |
1575 | shutdown_actions_list[i]->init_rc = |
1576 | shutdown_actions_list[i]->init(); | |
ff6b8ea6 | 1577 | } |
ff6b8ea6 MH |
1578 | } |
1579 | ||
1580 | static int __init s390_ipl_init(void) | |
1581 | { | |
69928601 MH |
1582 | char str[8] = {0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40}; |
1583 | ||
d5ab7a34 | 1584 | sclp_early_get_ipl_info(&sclp_ipl_info); |
69928601 MH |
1585 | /* |
1586 | * Fix loadparm: There are systems where the (SCSI) LOADPARM | |
1587 | * returned by read SCP info is invalid (contains EBCDIC blanks) | |
1588 | * when the system has been booted via diag308. In that case we use | |
1589 | * the value from diag308, if available. | |
1590 | * | |
1591 | * There are also systems where diag308 store does not work in | |
1592 | * case the system is booted from HMC. Fortunately in this case | |
1593 | * READ SCP info provides the correct value. | |
1594 | */ | |
a0832b3a | 1595 | if (memcmp(sclp_ipl_info.loadparm, str, sizeof(str)) == 0 && ipl_block_valid) |
5f1207fb | 1596 | memcpy(sclp_ipl_info.loadparm, ipl_block.ccw.loadparm, LOADPARM_LEN); |
99ca4e58 MH |
1597 | shutdown_actions_init(); |
1598 | shutdown_triggers_init(); | |
ff6b8ea6 MH |
1599 | return 0; |
1600 | } | |
1601 | ||
1602 | __initcall(s390_ipl_init); | |
15e9b586 | 1603 | |
99ca4e58 MH |
1604 | static void __init strncpy_skip_quote(char *dst, char *src, int n) |
1605 | { | |
1606 | int sx, dx; | |
1607 | ||
1608 | dx = 0; | |
1609 | for (sx = 0; src[sx] != 0; sx++) { | |
1610 | if (src[sx] == '"') | |
1611 | continue; | |
1612 | dst[dx++] = src[sx]; | |
1613 | if (dx >= n) | |
1614 | break; | |
1615 | } | |
1616 | } | |
1617 | ||
1618 | static int __init vmcmd_on_reboot_setup(char *str) | |
1619 | { | |
1620 | if (!MACHINE_IS_VM) | |
1621 | return 1; | |
1622 | strncpy_skip_quote(vmcmd_on_reboot, str, 127); | |
1623 | vmcmd_on_reboot[127] = 0; | |
1624 | on_reboot_trigger.action = &vmcmd_action; | |
1625 | return 1; | |
1626 | } | |
1627 | __setup("vmreboot=", vmcmd_on_reboot_setup); | |
1628 | ||
1629 | static int __init vmcmd_on_panic_setup(char *str) | |
1630 | { | |
1631 | if (!MACHINE_IS_VM) | |
1632 | return 1; | |
1633 | strncpy_skip_quote(vmcmd_on_panic, str, 127); | |
1634 | vmcmd_on_panic[127] = 0; | |
1635 | on_panic_trigger.action = &vmcmd_action; | |
1636 | return 1; | |
1637 | } | |
1638 | __setup("vmpanic=", vmcmd_on_panic_setup); | |
1639 | ||
1640 | static int __init vmcmd_on_halt_setup(char *str) | |
1641 | { | |
1642 | if (!MACHINE_IS_VM) | |
1643 | return 1; | |
1644 | strncpy_skip_quote(vmcmd_on_halt, str, 127); | |
1645 | vmcmd_on_halt[127] = 0; | |
1646 | on_halt_trigger.action = &vmcmd_action; | |
1647 | return 1; | |
1648 | } | |
1649 | __setup("vmhalt=", vmcmd_on_halt_setup); | |
1650 | ||
1651 | static int __init vmcmd_on_poff_setup(char *str) | |
1652 | { | |
1653 | if (!MACHINE_IS_VM) | |
1654 | return 1; | |
1655 | strncpy_skip_quote(vmcmd_on_poff, str, 127); | |
1656 | vmcmd_on_poff[127] = 0; | |
1657 | on_poff_trigger.action = &vmcmd_action; | |
1658 | return 1; | |
1659 | } | |
1660 | __setup("vmpoff=", vmcmd_on_poff_setup); | |
1661 | ||
1662 | static int on_panic_notify(struct notifier_block *self, | |
1663 | unsigned long event, void *data) | |
1664 | { | |
1665 | do_panic(); | |
1666 | return NOTIFY_OK; | |
1667 | } | |
1668 | ||
1669 | static struct notifier_block on_panic_nb = { | |
1670 | .notifier_call = on_panic_notify, | |
7e9b580e | 1671 | .priority = INT_MIN, |
99ca4e58 MH |
1672 | }; |
1673 | ||
1674 | void __init setup_ipl(void) | |
1675 | { | |
bdbfe185 VG |
1676 | BUILD_BUG_ON(sizeof(struct ipl_parameter_block) != PAGE_SIZE); |
1677 | ||
99ca4e58 MH |
1678 | ipl_info.type = get_ipl_type(); |
1679 | switch (ipl_info.type) { | |
1680 | case IPL_TYPE_CCW: | |
86c74d86 MS |
1681 | ipl_info.data.ccw.dev_id.ssid = ipl_block.ccw.ssid; |
1682 | ipl_info.data.ccw.dev_id.devno = ipl_block.ccw.devno; | |
99ca4e58 MH |
1683 | break; |
1684 | case IPL_TYPE_FCP: | |
1685 | case IPL_TYPE_FCP_DUMP: | |
18e22a17 | 1686 | ipl_info.data.fcp.dev_id.ssid = 0; |
86c74d86 MS |
1687 | ipl_info.data.fcp.dev_id.devno = ipl_block.fcp.devno; |
1688 | ipl_info.data.fcp.wwpn = ipl_block.fcp.wwpn; | |
1689 | ipl_info.data.fcp.lun = ipl_block.fcp.lun; | |
99ca4e58 MH |
1690 | break; |
1691 | case IPL_TYPE_NSS: | |
99ca4e58 | 1692 | case IPL_TYPE_UNKNOWN: |
99ca4e58 MH |
1693 | /* We have no info to copy */ |
1694 | break; | |
1695 | } | |
1696 | atomic_notifier_chain_register(&panic_notifier_list, &on_panic_nb); | |
1697 | } | |
1698 | ||
1a36a39e | 1699 | void s390_reset_system(void) |
15e9b586 | 1700 | { |
15e9b586 HC |
1701 | /* Disable prefixing */ |
1702 | set_prefix(0); | |
1703 | ||
1704 | /* Disable lowcore protection */ | |
d485235b VG |
1705 | __ctl_clear_bit(0, 28); |
1706 | diag308_reset(); | |
15e9b586 | 1707 | } |
937347ac MS |
1708 | |
1709 | #ifdef CONFIG_KEXEC_FILE | |
1710 | ||
1711 | int ipl_report_add_component(struct ipl_report *report, struct kexec_buf *kbuf, | |
1712 | unsigned char flags, unsigned short cert) | |
1713 | { | |
1714 | struct ipl_report_component *comp; | |
1715 | ||
1716 | comp = vzalloc(sizeof(*comp)); | |
1717 | if (!comp) | |
1718 | return -ENOMEM; | |
1719 | list_add_tail(&comp->list, &report->components); | |
1720 | ||
1721 | comp->entry.addr = kbuf->mem; | |
1722 | comp->entry.len = kbuf->memsz; | |
1723 | comp->entry.flags = flags; | |
1724 | comp->entry.certificate_index = cert; | |
1725 | ||
1726 | report->size += sizeof(comp->entry); | |
1727 | ||
1728 | return 0; | |
1729 | } | |
1730 | ||
1731 | int ipl_report_add_certificate(struct ipl_report *report, void *key, | |
1732 | unsigned long addr, unsigned long len) | |
1733 | { | |
1734 | struct ipl_report_certificate *cert; | |
1735 | ||
1736 | cert = vzalloc(sizeof(*cert)); | |
1737 | if (!cert) | |
1738 | return -ENOMEM; | |
1739 | list_add_tail(&cert->list, &report->certificates); | |
1740 | ||
1741 | cert->entry.addr = addr; | |
1742 | cert->entry.len = len; | |
1743 | cert->key = key; | |
1744 | ||
1745 | report->size += sizeof(cert->entry); | |
1746 | report->size += cert->entry.len; | |
1747 | ||
1748 | return 0; | |
1749 | } | |
1750 | ||
1751 | struct ipl_report *ipl_report_init(struct ipl_parameter_block *ipib) | |
1752 | { | |
1753 | struct ipl_report *report; | |
1754 | ||
1755 | report = vzalloc(sizeof(*report)); | |
1756 | if (!report) | |
1757 | return ERR_PTR(-ENOMEM); | |
1758 | ||
1759 | report->ipib = ipib; | |
1760 | INIT_LIST_HEAD(&report->components); | |
1761 | INIT_LIST_HEAD(&report->certificates); | |
1762 | ||
1763 | report->size = ALIGN(ipib->hdr.len, 8); | |
1764 | report->size += sizeof(struct ipl_rl_hdr); | |
1765 | report->size += sizeof(struct ipl_rb_components); | |
1766 | report->size += sizeof(struct ipl_rb_certificates); | |
1767 | ||
1768 | return report; | |
1769 | } | |
1770 | ||
1771 | void *ipl_report_finish(struct ipl_report *report) | |
1772 | { | |
1773 | struct ipl_report_certificate *cert; | |
1774 | struct ipl_report_component *comp; | |
1775 | struct ipl_rb_certificates *certs; | |
1776 | struct ipl_parameter_block *ipib; | |
1777 | struct ipl_rb_components *comps; | |
1778 | struct ipl_rl_hdr *rl_hdr; | |
1779 | void *buf, *ptr; | |
1780 | ||
1781 | buf = vzalloc(report->size); | |
1782 | if (!buf) | |
1783 | return ERR_PTR(-ENOMEM); | |
1784 | ptr = buf; | |
1785 | ||
1786 | memcpy(ptr, report->ipib, report->ipib->hdr.len); | |
1787 | ipib = ptr; | |
1788 | if (ipl_secure_flag) | |
1789 | ipib->hdr.flags |= IPL_PL_FLAG_SIPL; | |
1790 | ipib->hdr.flags |= IPL_PL_FLAG_IPLSR; | |
1791 | ptr += report->ipib->hdr.len; | |
1792 | ptr = PTR_ALIGN(ptr, 8); | |
1793 | ||
1794 | rl_hdr = ptr; | |
1795 | ptr += sizeof(*rl_hdr); | |
1796 | ||
1797 | comps = ptr; | |
1798 | comps->rbt = IPL_RBT_COMPONENTS; | |
1799 | ptr += sizeof(*comps); | |
1800 | list_for_each_entry(comp, &report->components, list) { | |
1801 | memcpy(ptr, &comp->entry, sizeof(comp->entry)); | |
1802 | ptr += sizeof(comp->entry); | |
1803 | } | |
1804 | comps->len = ptr - (void *)comps; | |
1805 | ||
1806 | certs = ptr; | |
1807 | certs->rbt = IPL_RBT_CERTIFICATES; | |
1808 | ptr += sizeof(*certs); | |
1809 | list_for_each_entry(cert, &report->certificates, list) { | |
1810 | memcpy(ptr, &cert->entry, sizeof(cert->entry)); | |
1811 | ptr += sizeof(cert->entry); | |
1812 | } | |
1813 | certs->len = ptr - (void *)certs; | |
1814 | rl_hdr->len = ptr - (void *)rl_hdr; | |
1815 | ||
1816 | list_for_each_entry(cert, &report->certificates, list) { | |
1817 | memcpy(ptr, cert->key, cert->entry.len); | |
1818 | ptr += cert->entry.len; | |
1819 | } | |
1820 | ||
1821 | BUG_ON(ptr > buf + report->size); | |
1822 | return buf; | |
1823 | } | |
1824 | ||
1825 | int ipl_report_free(struct ipl_report *report) | |
1826 | { | |
1827 | struct ipl_report_component *comp, *ncomp; | |
1828 | struct ipl_report_certificate *cert, *ncert; | |
1829 | ||
1830 | list_for_each_entry_safe(comp, ncomp, &report->components, list) | |
1831 | vfree(comp); | |
1832 | ||
1833 | list_for_each_entry_safe(cert, ncert, &report->certificates, list) | |
1834 | vfree(cert); | |
1835 | ||
1836 | vfree(report); | |
1837 | ||
1838 | return 0; | |
1839 | } | |
1840 | ||
1841 | #endif |