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