2 * S390 virtio-ccw network boot loading program
4 * Copyright 2017 Thomas Huth, Red Hat Inc.
6 * Based on the S390 virtio-ccw loading program (main.c)
7 * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
9 * And based on the network loading code from SLOF (netload.c)
10 * Copyright (c) 2004, 2008 IBM Corporation
12 * This code is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU General Public License as published by the
14 * Free Software Foundation; either version 2 of the License, or (at your
15 * option) any later version.
38 #include "s390-time.h"
40 #define DEFAULT_BOOT_RETRIES 10
41 #define DEFAULT_TFTP_RETRIES 20
44 void write_iplb_location(void) {}
46 #define KERNEL_ADDR ((void *)0L)
47 #define KERNEL_MAX_SIZE ((long)_start)
48 #define ARCH_COMMAND_LINE_SIZE 896 /* Taken from Linux kernel */
50 /* STSI 3.2.2 offset of first vmdb + offset of uuid inside vmdb */
51 #define STSI322_VMDB_UUID_OFFSET ((8 + 12) * 4)
53 IplParameterBlock iplb
__attribute__((aligned(PAGE_SIZE
)));
54 static char cfgbuf
[2048];
56 static SubChannelId net_schid
= { .one
= 1 };
57 static uint8_t mac
[6];
58 static uint64_t dest_timer
;
60 void set_timer(int val
)
62 dest_timer
= get_time_ms() + val
;
67 return dest_timer
- get_time_ms();
70 int get_sec_ticks(void)
72 return 1000; /* number of ticks in 1 second */
76 * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
77 * @param fn_ip contains the following configuration information:
78 * client MAC, client IP, TFTP-server MAC, TFTP-server IP,
80 * @param retries Number of DHCP attempts
81 * @return 0 : IP and configuration info obtained;
82 * non-0 : error condition occurred.
84 static int dhcp(struct filename_ip
*fn_ip
, int retries
)
89 printf(" Requesting information via DHCP: ");
91 dhcpv4_generate_transaction_id();
92 dhcpv6_generate_transaction_id();
95 printf("\b\b\b%03d", i
- 1);
97 printf("\nGiving up after %d DHCP requests\n", retries
);
100 fn_ip
->ip_version
= 4;
101 rc
= dhcpv4(NULL
, fn_ip
);
103 fn_ip
->ip_version
= 6;
104 set_ipv6_address(fn_ip
->fd
, 0);
105 rc
= dhcpv6(NULL
, fn_ip
);
107 memcpy(&fn_ip
->own_ip6
, get_ipv6_address(), 16);
111 if (rc
!= -1) { /* either success or non-dhcp failure */
115 printf("\b\b\b\bdone\n");
121 * Seed the random number generator with our mac and current timestamp
123 static void seed_rng(uint8_t mac
[])
127 asm volatile(" stck %0 " : : "Q"(seed
) : "memory");
128 seed
^= (mac
[2] << 24) | (mac
[3] << 16) | (mac
[4] << 8) | mac
[5];
132 static int tftp_load(filename_ip_t
*fnip
, void *buffer
, int len
)
137 rc
= tftp(fnip
, buffer
, len
, DEFAULT_TFTP_RETRIES
, &tftp_err
);
140 /* Make sure that error messages are put into a new line */
145 printf(" TFTP: Received %s (%d KBytes)\n", fnip
->filename
, rc
/ 1024);
147 printf(" TFTP: Received %s (%d Bytes)\n", fnip
->filename
, rc
);
149 const char *errstr
= NULL
;
151 tftp_get_error_info(fnip
, &tftp_err
, rc
, &errstr
, &ecode
);
152 printf("TFTP error: %s\n", errstr
? errstr
: "unknown error");
158 static int net_init(filename_ip_t
*fn_ip
)
162 memset(fn_ip
, 0, sizeof(filename_ip_t
));
164 rc
= virtio_net_init(mac
);
166 puts("Could not initialize network device");
171 printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
172 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
174 set_mac_address(mac
); /* init ethernet layer */
177 rc
= dhcp(fn_ip
, DEFAULT_BOOT_RETRIES
);
179 if (fn_ip
->ip_version
== 4) {
180 set_ipv4_address(fn_ip
->own_ip
);
183 puts("Could not get IP address");
187 if (fn_ip
->ip_version
== 4) {
188 printf(" Using IPv4 address: %d.%d.%d.%d\n",
189 (fn_ip
->own_ip
>> 24) & 0xFF, (fn_ip
->own_ip
>> 16) & 0xFF,
190 (fn_ip
->own_ip
>> 8) & 0xFF, fn_ip
->own_ip
& 0xFF);
191 } else if (fn_ip
->ip_version
== 6) {
193 ipv6_to_str(fn_ip
->own_ip6
.addr
, ip6_str
);
194 printf(" Using IPv6 address: %s\n", ip6_str
);
198 printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
199 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
200 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
203 if (rc
== -4 || rc
== -3) {
204 puts("Can't obtain TFTP server IP address");
208 printf(" Using TFTP server: ");
209 if (fn_ip
->ip_version
== 4) {
210 printf("%d.%d.%d.%d\n",
211 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
212 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
213 } else if (fn_ip
->ip_version
== 6) {
215 ipv6_to_str(fn_ip
->server_ip6
.addr
, ip6_str
);
216 printf("%s\n", ip6_str
);
219 if (strlen(fn_ip
->filename
) > 0) {
220 printf(" Bootfile name: '%s'\n", fn_ip
->filename
);
226 static void net_release(filename_ip_t
*fn_ip
)
228 if (fn_ip
->ip_version
== 4) {
229 dhcp_send_release(fn_ip
->fd
);
234 * Retrieve the Universally Unique Identifier of the VM.
235 * @return UUID string, or NULL in case of errors
237 static const char *get_uuid(void)
239 register int r0
asm("0");
240 register int r1
asm("1");
241 uint8_t *mem
, *buf
, uuid
[16];
243 static char uuid_str
[37];
245 mem
= malloc(2 * PAGE_SIZE
);
247 puts("Out of memory ... can not get UUID.");
250 buf
= (uint8_t *)(((uint64_t)mem
+ PAGE_SIZE
- 1) & ~(PAGE_SIZE
- 1));
251 memset(buf
, 0, PAGE_SIZE
);
253 /* Get SYSIB 3.2.2 */
256 asm volatile(" stsi 0(%[addr])\n"
260 : "d" (r0
), "d" (r1
), [addr
] "a" (buf
)
267 for (i
= 0; i
< 16; i
++) {
268 uuid
[i
] = buf
[STSI322_VMDB_UUID_OFFSET
+ i
];
276 sprintf(uuid_str
, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
277 "%02x%02x%02x%02x%02x%02x", uuid
[0], uuid
[1], uuid
[2], uuid
[3],
278 uuid
[4], uuid
[5], uuid
[6], uuid
[7], uuid
[8], uuid
[9], uuid
[10],
279 uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15]);
285 * Load a kernel with initrd (i.e. with the information that we've got from
286 * a pxelinux.cfg config file)
288 static int load_kernel_with_initrd(filename_ip_t
*fn_ip
,
289 struct pl_cfg_entry
*entry
)
293 printf("Loading pxelinux.cfg entry '%s'\n", entry
->label
);
295 if (!entry
->kernel
) {
296 printf("Kernel entry is missing!\n");
300 strncpy(fn_ip
->filename
, entry
->kernel
, sizeof(fn_ip
->filename
));
301 rc
= tftp_load(fn_ip
, KERNEL_ADDR
, KERNEL_MAX_SIZE
);
307 uint64_t iaddr
= (rc
+ 0xfff) & ~0xfffUL
;
309 strncpy(fn_ip
->filename
, entry
->initrd
, sizeof(fn_ip
->filename
));
310 rc
= tftp_load(fn_ip
, (void *)iaddr
, KERNEL_MAX_SIZE
- iaddr
);
314 /* Patch location and size: */
315 *(uint64_t *)0x10408 = iaddr
;
316 *(uint64_t *)0x10410 = rc
;
321 strncpy((char *)0x10480, entry
->append
, ARCH_COMMAND_LINE_SIZE
);
327 #define MAX_PXELINUX_ENTRIES 16
329 static int net_try_pxelinux_cfg(filename_ip_t
*fn_ip
)
331 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
332 int num_ent
, def_ent
= 0;
334 num_ent
= pxelinux_load_parse_cfg(fn_ip
, mac
, get_uuid(),
335 DEFAULT_TFTP_RETRIES
,
336 cfgbuf
, sizeof(cfgbuf
),
337 entries
, MAX_PXELINUX_ENTRIES
, &def_ent
);
339 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
346 * Load via information from a .INS file (which can be found on CD-ROMs
349 static int handle_ins_cfg(filename_ip_t
*fn_ip
, char *cfg
, int cfgsize
)
356 ptr
= strchr(insbuf
, '\n');
358 puts("Does not seem to be a valid .INS file");
363 printf("\nParsing .INS file:\n %s\n", &insbuf
[2]);
366 while (*insbuf
&& insbuf
< cfg
+ cfgsize
) {
367 ptr
= strchr(insbuf
, '\n');
371 llen
= strlen(insbuf
);
376 ptr
= strchr(insbuf
, ' ');
378 puts("Missing space separator in .INS file");
382 strncpy(fn_ip
->filename
, insbuf
, sizeof(fn_ip
->filename
));
383 destaddr
= (char *)atol(ptr
+ 1);
384 rc
= tftp_load(fn_ip
, destaddr
, (long)_start
- (long)destaddr
);
394 static int net_try_direct_tftp_load(filename_ip_t
*fn_ip
)
397 void *loadaddr
= (void *)0x2000; /* Load right after the low-core */
399 rc
= tftp_load(fn_ip
, loadaddr
, KERNEL_MAX_SIZE
- (long)loadaddr
);
403 printf("'%s' is too small (%i bytes only).\n", fn_ip
->filename
, rc
);
407 /* Check whether it is a configuration file instead of a kernel */
408 if (rc
< sizeof(cfgbuf
) - 1) {
409 memcpy(cfgbuf
, loadaddr
, rc
);
410 cfgbuf
[rc
] = 0; /* Make sure that it is NUL-terminated */
411 if (!strncmp("* ", cfgbuf
, 2)) {
412 return handle_ins_cfg(fn_ip
, cfgbuf
, rc
);
415 * pxelinux.cfg support via bootfile name is just here for developers'
416 * convenience (it eases testing with the built-in DHCP server of QEMU
417 * that does not support RFC 5071). The official way to configure a
418 * pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
419 * So only use the pxelinux.cfg parser here for files that start with
420 * a magic comment string.
422 if (!strncasecmp("# pxelinux", cfgbuf
, 10)) {
423 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
424 int num_ent
, def_ent
= 0;
426 num_ent
= pxelinux_parse_cfg(cfgbuf
, sizeof(cfgbuf
), entries
,
427 MAX_PXELINUX_ENTRIES
, &def_ent
);
431 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
435 /* Move kernel to right location */
436 memmove(KERNEL_ADDR
, loadaddr
, rc
);
441 void write_subsystem_identification(void)
443 SubChannelId
*schid
= (SubChannelId
*) 184;
444 uint32_t *zeroes
= (uint32_t *) 188;
450 static bool find_net_dev(Schib
*schib
, int dev_no
)
454 for (i
= 0; i
< 0x10000; i
++) {
455 net_schid
.sch_no
= i
;
456 r
= stsch_err(net_schid
, schib
);
457 if (r
== 3 || r
== -EIO
) {
460 if (!schib
->pmcw
.dnv
) {
463 enable_subchannel(net_schid
);
464 if (!virtio_is_supported(net_schid
)) {
467 if (virtio_get_device_type() != VIRTIO_ID_NET
) {
470 if (dev_no
< 0 || schib
->pmcw
.dev
== dev_no
) {
478 static void virtio_setup(void)
486 * We unconditionally enable mss support. In every sane configuration,
487 * this will succeed; and even if it doesn't, stsch_err() can deal
488 * with the consequences.
490 enable_mss_facility();
492 if (store_iplb(&iplb
)) {
493 IPL_assert(iplb
.pbt
== S390_IPL_TYPE_CCW
, "IPL_TYPE_CCW expected");
494 dev_no
= iplb
.ccw
.devno
;
495 debug_print_int("device no. ", dev_no
);
496 net_schid
.ssid
= iplb
.ccw
.ssid
& 0x3;
497 debug_print_int("ssid ", net_schid
.ssid
);
498 found
= find_net_dev(&schib
, dev_no
);
500 for (ssid
= 0; ssid
< 0x3; ssid
++) {
501 net_schid
.ssid
= ssid
;
502 found
= find_net_dev(&schib
, -1);
509 IPL_assert(found
, "No virtio net device found");
518 sclp_print("Network boot starting...\n");
522 rc
= net_init(&fn_ip
);
524 panic("Network initialization failed. Halting.\n");
527 fnlen
= strlen(fn_ip
.filename
);
528 if (fnlen
> 0 && fn_ip
.filename
[fnlen
- 1] != '/') {
529 rc
= net_try_direct_tftp_load(&fn_ip
);
532 rc
= net_try_pxelinux_cfg(&fn_ip
);
538 sclp_print("Network loading done, starting kernel...\n");
539 jump_to_low_kernel();
542 panic("Failed to load OS from network\n");