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 char stack
[PAGE_SIZE
* 8] __attribute__((aligned(PAGE_SIZE
)));
54 IplParameterBlock iplb
__attribute__((aligned(PAGE_SIZE
)));
55 static char cfgbuf
[2048];
57 static SubChannelId net_schid
= { .one
= 1 };
58 static uint8_t mac
[6];
59 static uint64_t dest_timer
;
61 void set_timer(int val
)
63 dest_timer
= get_time_ms() + val
;
68 return dest_timer
- get_time_ms();
71 int get_sec_ticks(void)
73 return 1000; /* number of ticks in 1 second */
77 * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
78 * @param fn_ip contains the following configuration information:
79 * client MAC, client IP, TFTP-server MAC, TFTP-server IP,
81 * @param retries Number of DHCP attempts
82 * @return 0 : IP and configuration info obtained;
83 * non-0 : error condition occurred.
85 static int dhcp(struct filename_ip
*fn_ip
, int retries
)
90 printf(" Requesting information via DHCP: ");
92 dhcpv4_generate_transaction_id();
93 dhcpv6_generate_transaction_id();
96 printf("\b\b\b%03d", i
- 1);
98 printf("\nGiving up after %d DHCP requests\n", retries
);
101 fn_ip
->ip_version
= 4;
102 rc
= dhcpv4(NULL
, fn_ip
);
104 fn_ip
->ip_version
= 6;
105 set_ipv6_address(fn_ip
->fd
, 0);
106 rc
= dhcpv6(NULL
, fn_ip
);
108 memcpy(&fn_ip
->own_ip6
, get_ipv6_address(), 16);
112 if (rc
!= -1) { /* either success or non-dhcp failure */
116 printf("\b\b\b\bdone\n");
122 * Seed the random number generator with our mac and current timestamp
124 static void seed_rng(uint8_t mac
[])
128 asm volatile(" stck %0 " : : "Q"(seed
) : "memory");
129 seed
^= (mac
[2] << 24) | (mac
[3] << 16) | (mac
[4] << 8) | mac
[5];
133 static int tftp_load(filename_ip_t
*fnip
, void *buffer
, int len
)
138 rc
= tftp(fnip
, buffer
, len
, DEFAULT_TFTP_RETRIES
, &tftp_err
);
141 /* Make sure that error messages are put into a new line */
146 printf(" TFTP: Received %s (%d KBytes)\n", fnip
->filename
, rc
/ 1024);
148 printf(" TFTP: Received %s (%d Bytes)\n", fnip
->filename
, rc
);
150 const char *errstr
= NULL
;
152 tftp_get_error_info(fnip
, &tftp_err
, rc
, &errstr
, &ecode
);
153 printf("TFTP error: %s\n", errstr
? errstr
: "unknown error");
159 static int net_init(filename_ip_t
*fn_ip
)
163 memset(fn_ip
, 0, sizeof(filename_ip_t
));
165 rc
= virtio_net_init(mac
);
167 puts("Could not initialize network device");
172 printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
173 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
175 set_mac_address(mac
); /* init ethernet layer */
178 rc
= dhcp(fn_ip
, DEFAULT_BOOT_RETRIES
);
180 if (fn_ip
->ip_version
== 4) {
181 set_ipv4_address(fn_ip
->own_ip
);
184 puts("Could not get IP address");
188 if (fn_ip
->ip_version
== 4) {
189 printf(" Using IPv4 address: %d.%d.%d.%d\n",
190 (fn_ip
->own_ip
>> 24) & 0xFF, (fn_ip
->own_ip
>> 16) & 0xFF,
191 (fn_ip
->own_ip
>> 8) & 0xFF, fn_ip
->own_ip
& 0xFF);
192 } else if (fn_ip
->ip_version
== 6) {
194 ipv6_to_str(fn_ip
->own_ip6
.addr
, ip6_str
);
195 printf(" Using IPv6 address: %s\n", ip6_str
);
199 printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
200 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
201 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
204 if (rc
== -4 || rc
== -3) {
205 puts("Can't obtain TFTP server IP address");
209 printf(" Using TFTP server: ");
210 if (fn_ip
->ip_version
== 4) {
211 printf("%d.%d.%d.%d\n",
212 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
213 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
214 } else if (fn_ip
->ip_version
== 6) {
216 ipv6_to_str(fn_ip
->server_ip6
.addr
, ip6_str
);
217 printf("%s\n", ip6_str
);
220 if (strlen(fn_ip
->filename
) > 0) {
221 printf(" Bootfile name: '%s'\n", fn_ip
->filename
);
227 static void net_release(filename_ip_t
*fn_ip
)
229 if (fn_ip
->ip_version
== 4) {
230 dhcp_send_release(fn_ip
->fd
);
235 * Retrieve the Universally Unique Identifier of the VM.
236 * @return UUID string, or NULL in case of errors
238 static const char *get_uuid(void)
240 register int r0
asm("0");
241 register int r1
asm("1");
242 uint8_t *mem
, *buf
, uuid
[16];
244 static char uuid_str
[37];
246 mem
= malloc(2 * PAGE_SIZE
);
248 puts("Out of memory ... can not get UUID.");
251 buf
= (uint8_t *)(((uint64_t)mem
+ PAGE_SIZE
- 1) & ~(PAGE_SIZE
- 1));
252 memset(buf
, 0, PAGE_SIZE
);
254 /* Get SYSIB 3.2.2 */
257 asm volatile(" stsi 0(%[addr])\n"
261 : "d" (r0
), "d" (r1
), [addr
] "a" (buf
)
268 for (i
= 0; i
< 16; i
++) {
269 uuid
[i
] = buf
[STSI322_VMDB_UUID_OFFSET
+ i
];
277 sprintf(uuid_str
, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
278 "%02x%02x%02x%02x%02x%02x", uuid
[0], uuid
[1], uuid
[2], uuid
[3],
279 uuid
[4], uuid
[5], uuid
[6], uuid
[7], uuid
[8], uuid
[9], uuid
[10],
280 uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15]);
286 * Load a kernel with initrd (i.e. with the information that we've got from
287 * a pxelinux.cfg config file)
289 static int load_kernel_with_initrd(filename_ip_t
*fn_ip
,
290 struct pl_cfg_entry
*entry
)
294 printf("Loading pxelinux.cfg entry '%s'\n", entry
->label
);
296 if (!entry
->kernel
) {
297 printf("Kernel entry is missing!\n");
301 strncpy(fn_ip
->filename
, entry
->kernel
, sizeof(fn_ip
->filename
));
302 rc
= tftp_load(fn_ip
, KERNEL_ADDR
, KERNEL_MAX_SIZE
);
308 uint64_t iaddr
= (rc
+ 0xfff) & ~0xfffUL
;
310 strncpy(fn_ip
->filename
, entry
->initrd
, sizeof(fn_ip
->filename
));
311 rc
= tftp_load(fn_ip
, (void *)iaddr
, KERNEL_MAX_SIZE
- iaddr
);
315 /* Patch location and size: */
316 *(uint64_t *)0x10408 = iaddr
;
317 *(uint64_t *)0x10410 = rc
;
322 strncpy((char *)0x10480, entry
->append
, ARCH_COMMAND_LINE_SIZE
);
328 #define MAX_PXELINUX_ENTRIES 16
330 static int net_try_pxelinux_cfg(filename_ip_t
*fn_ip
)
332 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
333 int num_ent
, def_ent
= 0;
335 num_ent
= pxelinux_load_parse_cfg(fn_ip
, mac
, get_uuid(),
336 DEFAULT_TFTP_RETRIES
,
337 cfgbuf
, sizeof(cfgbuf
),
338 entries
, MAX_PXELINUX_ENTRIES
, &def_ent
);
340 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
347 * Load via information from a .INS file (which can be found on CD-ROMs
350 static int handle_ins_cfg(filename_ip_t
*fn_ip
, char *cfg
, int cfgsize
)
357 ptr
= strchr(insbuf
, '\n');
359 puts("Does not seem to be a valid .INS file");
364 printf("\nParsing .INS file:\n %s\n", &insbuf
[2]);
367 while (*insbuf
&& insbuf
< cfg
+ cfgsize
) {
368 ptr
= strchr(insbuf
, '\n');
372 llen
= strlen(insbuf
);
377 ptr
= strchr(insbuf
, ' ');
379 puts("Missing space separator in .INS file");
383 strncpy(fn_ip
->filename
, insbuf
, sizeof(fn_ip
->filename
));
384 destaddr
= (char *)atol(ptr
+ 1);
385 rc
= tftp_load(fn_ip
, destaddr
, (long)_start
- (long)destaddr
);
395 static int net_try_direct_tftp_load(filename_ip_t
*fn_ip
)
398 void *loadaddr
= (void *)0x2000; /* Load right after the low-core */
400 rc
= tftp_load(fn_ip
, loadaddr
, KERNEL_MAX_SIZE
- (long)loadaddr
);
404 printf("'%s' is too small (%i bytes only).\n", fn_ip
->filename
, rc
);
408 /* Check whether it is a configuration file instead of a kernel */
409 if (rc
< sizeof(cfgbuf
) - 1) {
410 memcpy(cfgbuf
, loadaddr
, rc
);
411 cfgbuf
[rc
] = 0; /* Make sure that it is NUL-terminated */
412 if (!strncmp("* ", cfgbuf
, 2)) {
413 return handle_ins_cfg(fn_ip
, cfgbuf
, rc
);
416 * pxelinux.cfg support via bootfile name is just here for developers'
417 * convenience (it eases testing with the built-in DHCP server of QEMU
418 * that does not support RFC 5071). The official way to configure a
419 * pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
420 * So only use the pxelinux.cfg parser here for files that start with
421 * a magic comment string.
423 if (!strncasecmp("# pxelinux", cfgbuf
, 10)) {
424 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
425 int num_ent
, def_ent
= 0;
427 num_ent
= pxelinux_parse_cfg(cfgbuf
, sizeof(cfgbuf
), entries
,
428 MAX_PXELINUX_ENTRIES
, &def_ent
);
432 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
436 /* Move kernel to right location */
437 memmove(KERNEL_ADDR
, loadaddr
, rc
);
442 void write_subsystem_identification(void)
444 SubChannelId
*schid
= (SubChannelId
*) 184;
445 uint32_t *zeroes
= (uint32_t *) 188;
451 static bool find_net_dev(Schib
*schib
, int dev_no
)
455 for (i
= 0; i
< 0x10000; i
++) {
456 net_schid
.sch_no
= i
;
457 r
= stsch_err(net_schid
, schib
);
458 if (r
== 3 || r
== -EIO
) {
461 if (!schib
->pmcw
.dnv
) {
464 enable_subchannel(net_schid
);
465 if (!virtio_is_supported(net_schid
)) {
468 if (virtio_get_device_type() != VIRTIO_ID_NET
) {
471 if (dev_no
< 0 || schib
->pmcw
.dev
== dev_no
) {
479 static void virtio_setup(void)
487 * We unconditionally enable mss support. In every sane configuration,
488 * this will succeed; and even if it doesn't, stsch_err() can deal
489 * with the consequences.
491 enable_mss_facility();
493 if (store_iplb(&iplb
)) {
494 IPL_assert(iplb
.pbt
== S390_IPL_TYPE_CCW
, "IPL_TYPE_CCW expected");
495 dev_no
= iplb
.ccw
.devno
;
496 debug_print_int("device no. ", dev_no
);
497 net_schid
.ssid
= iplb
.ccw
.ssid
& 0x3;
498 debug_print_int("ssid ", net_schid
.ssid
);
499 found
= find_net_dev(&schib
, dev_no
);
501 for (ssid
= 0; ssid
< 0x3; ssid
++) {
502 net_schid
.ssid
= ssid
;
503 found
= find_net_dev(&schib
, -1);
510 IPL_assert(found
, "No virtio net device found");
519 sclp_print("Network boot starting...\n");
523 rc
= net_init(&fn_ip
);
525 panic("Network initialization failed. Halting.\n");
528 fnlen
= strlen(fn_ip
.filename
);
529 if (fnlen
> 0 && fn_ip
.filename
[fnlen
- 1] != '/') {
530 rc
= net_try_direct_tftp_load(&fn_ip
);
533 rc
= net_try_pxelinux_cfg(&fn_ip
);
539 sclp_print("Network loading done, starting kernel...\n");
540 jump_to_low_kernel();
543 panic("Failed to load OS from network\n");