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.
39 #define DEFAULT_BOOT_RETRIES 10
40 #define DEFAULT_TFTP_RETRIES 20
43 void write_iplb_location(void) {}
45 #define KERNEL_ADDR ((void *)0L)
46 #define KERNEL_MAX_SIZE ((long)_start)
47 #define ARCH_COMMAND_LINE_SIZE 896 /* Taken from Linux kernel */
49 /* STSI 3.2.2 offset of first vmdb + offset of uuid inside vmdb */
50 #define STSI322_VMDB_UUID_OFFSET ((8 + 12) * 4)
52 char stack
[PAGE_SIZE
* 8] __attribute__((aligned(PAGE_SIZE
)));
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 static uint64_t get_timer_ms(void)
64 asm volatile(" stck %0 " : : "Q"(clk
) : "memory");
66 /* Bit 51 is incremented each microsecond */
67 return (clk
>> (63 - 51)) / 1000;
70 void set_timer(int val
)
72 dest_timer
= get_timer_ms() + val
;
77 return dest_timer
- get_timer_ms();
80 int get_sec_ticks(void)
82 return 1000; /* number of ticks in 1 second */
86 * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
87 * @param fn_ip contains the following configuration information:
88 * client MAC, client IP, TFTP-server MAC, TFTP-server IP,
90 * @param retries Number of DHCP attempts
91 * @return 0 : IP and configuration info obtained;
92 * non-0 : error condition occurred.
94 static int dhcp(struct filename_ip
*fn_ip
, int retries
)
99 printf(" Requesting information via DHCP: ");
101 dhcpv4_generate_transaction_id();
102 dhcpv6_generate_transaction_id();
105 printf("\b\b\b%03d", i
- 1);
107 printf("\nGiving up after %d DHCP requests\n", retries
);
110 fn_ip
->ip_version
= 4;
111 rc
= dhcpv4(NULL
, fn_ip
);
113 fn_ip
->ip_version
= 6;
114 set_ipv6_address(fn_ip
->fd
, 0);
115 rc
= dhcpv6(NULL
, fn_ip
);
117 memcpy(&fn_ip
->own_ip6
, get_ipv6_address(), 16);
121 if (rc
!= -1) { /* either success or non-dhcp failure */
125 printf("\b\b\b\bdone\n");
131 * Seed the random number generator with our mac and current timestamp
133 static void seed_rng(uint8_t mac
[])
137 asm volatile(" stck %0 " : : "Q"(seed
) : "memory");
138 seed
^= (mac
[2] << 24) | (mac
[3] << 16) | (mac
[4] << 8) | mac
[5];
142 static int tftp_load(filename_ip_t
*fnip
, void *buffer
, int len
)
147 rc
= tftp(fnip
, buffer
, len
, DEFAULT_TFTP_RETRIES
, &tftp_err
);
150 /* Make sure that error messages are put into a new line */
155 printf(" TFTP: Received %s (%d KBytes)\n", fnip
->filename
, rc
/ 1024);
157 printf(" TFTP: Received %s (%d Bytes)\n", fnip
->filename
, rc
);
159 const char *errstr
= NULL
;
161 tftp_get_error_info(fnip
, &tftp_err
, rc
, &errstr
, &ecode
);
162 printf("TFTP error: %s\n", errstr
? errstr
: "unknown error");
168 static int net_init(filename_ip_t
*fn_ip
)
172 memset(fn_ip
, 0, sizeof(filename_ip_t
));
174 rc
= virtio_net_init(mac
);
176 puts("Could not initialize network device");
181 printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
182 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
184 set_mac_address(mac
); /* init ethernet layer */
187 rc
= dhcp(fn_ip
, DEFAULT_BOOT_RETRIES
);
189 if (fn_ip
->ip_version
== 4) {
190 set_ipv4_address(fn_ip
->own_ip
);
193 puts("Could not get IP address");
197 if (fn_ip
->ip_version
== 4) {
198 printf(" Using IPv4 address: %d.%d.%d.%d\n",
199 (fn_ip
->own_ip
>> 24) & 0xFF, (fn_ip
->own_ip
>> 16) & 0xFF,
200 (fn_ip
->own_ip
>> 8) & 0xFF, fn_ip
->own_ip
& 0xFF);
201 } else if (fn_ip
->ip_version
== 6) {
203 ipv6_to_str(fn_ip
->own_ip6
.addr
, ip6_str
);
204 printf(" Using IPv6 address: %s\n", ip6_str
);
208 printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
209 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
210 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
213 if (rc
== -4 || rc
== -3) {
214 puts("Can't obtain TFTP server IP address");
218 printf(" Using TFTP server: ");
219 if (fn_ip
->ip_version
== 4) {
220 printf("%d.%d.%d.%d\n",
221 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
222 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
223 } else if (fn_ip
->ip_version
== 6) {
225 ipv6_to_str(fn_ip
->server_ip6
.addr
, ip6_str
);
226 printf("%s\n", ip6_str
);
229 if (strlen(fn_ip
->filename
) > 0) {
230 printf(" Bootfile name: '%s'\n", fn_ip
->filename
);
236 static void net_release(filename_ip_t
*fn_ip
)
238 if (fn_ip
->ip_version
== 4) {
239 dhcp_send_release(fn_ip
->fd
);
244 * Retrieve the Universally Unique Identifier of the VM.
245 * @return UUID string, or NULL in case of errors
247 static const char *get_uuid(void)
249 register int r0
asm("0");
250 register int r1
asm("1");
251 uint8_t *mem
, *buf
, uuid
[16];
253 static char uuid_str
[37];
255 mem
= malloc(2 * PAGE_SIZE
);
257 puts("Out of memory ... can not get UUID.");
260 buf
= (uint8_t *)(((uint64_t)mem
+ PAGE_SIZE
- 1) & ~(PAGE_SIZE
- 1));
261 memset(buf
, 0, PAGE_SIZE
);
263 /* Get SYSIB 3.2.2 */
266 asm volatile(" stsi 0(%[addr])\n"
270 : "d" (r0
), "d" (r1
), [addr
] "a" (buf
)
277 for (i
= 0; i
< 16; i
++) {
278 uuid
[i
] = buf
[STSI322_VMDB_UUID_OFFSET
+ i
];
286 sprintf(uuid_str
, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
287 "%02x%02x%02x%02x%02x%02x", uuid
[0], uuid
[1], uuid
[2], uuid
[3],
288 uuid
[4], uuid
[5], uuid
[6], uuid
[7], uuid
[8], uuid
[9], uuid
[10],
289 uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15]);
295 * Load a kernel with initrd (i.e. with the information that we've got from
296 * a pxelinux.cfg config file)
298 static int load_kernel_with_initrd(filename_ip_t
*fn_ip
,
299 struct pl_cfg_entry
*entry
)
303 printf("Loading pxelinux.cfg entry '%s'\n", entry
->label
);
305 if (!entry
->kernel
) {
306 printf("Kernel entry is missing!\n");
310 strncpy(fn_ip
->filename
, entry
->kernel
, sizeof(fn_ip
->filename
));
311 rc
= tftp_load(fn_ip
, KERNEL_ADDR
, KERNEL_MAX_SIZE
);
317 uint64_t iaddr
= (rc
+ 0xfff) & ~0xfffUL
;
319 strncpy(fn_ip
->filename
, entry
->initrd
, sizeof(fn_ip
->filename
));
320 rc
= tftp_load(fn_ip
, (void *)iaddr
, KERNEL_MAX_SIZE
- iaddr
);
324 /* Patch location and size: */
325 *(uint64_t *)0x10408 = iaddr
;
326 *(uint64_t *)0x10410 = rc
;
331 strncpy((char *)0x10480, entry
->append
, ARCH_COMMAND_LINE_SIZE
);
337 #define MAX_PXELINUX_ENTRIES 16
339 static int net_try_pxelinux_cfg(filename_ip_t
*fn_ip
)
341 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
342 int num_ent
, def_ent
= 0;
344 num_ent
= pxelinux_load_parse_cfg(fn_ip
, mac
, get_uuid(),
345 DEFAULT_TFTP_RETRIES
,
346 cfgbuf
, sizeof(cfgbuf
),
347 entries
, MAX_PXELINUX_ENTRIES
, &def_ent
);
349 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
356 * Load via information from a .INS file (which can be found on CD-ROMs
359 static int handle_ins_cfg(filename_ip_t
*fn_ip
, char *cfg
, int cfgsize
)
366 ptr
= strchr(insbuf
, '\n');
368 puts("Does not seem to be a valid .INS file");
373 printf("\nParsing .INS file:\n %s\n", &insbuf
[2]);
376 while (*insbuf
&& insbuf
< cfg
+ cfgsize
) {
377 ptr
= strchr(insbuf
, '\n');
381 llen
= strlen(insbuf
);
386 ptr
= strchr(insbuf
, ' ');
388 puts("Missing space separator in .INS file");
392 strncpy(fn_ip
->filename
, insbuf
, sizeof(fn_ip
->filename
));
393 destaddr
= (char *)atol(ptr
+ 1);
394 rc
= tftp_load(fn_ip
, destaddr
, (long)_start
- (long)destaddr
);
404 static int net_try_direct_tftp_load(filename_ip_t
*fn_ip
)
407 void *loadaddr
= (void *)0x2000; /* Load right after the low-core */
409 rc
= tftp_load(fn_ip
, loadaddr
, KERNEL_MAX_SIZE
- (long)loadaddr
);
413 printf("'%s' is too small (%i bytes only).\n", fn_ip
->filename
, rc
);
417 /* Check whether it is a configuration file instead of a kernel */
418 if (rc
< sizeof(cfgbuf
) - 1) {
419 memcpy(cfgbuf
, loadaddr
, rc
);
420 cfgbuf
[rc
] = 0; /* Make sure that it is NUL-terminated */
421 if (!strncmp("* ", cfgbuf
, 2)) {
422 return handle_ins_cfg(fn_ip
, cfgbuf
, rc
);
425 * pxelinux.cfg support via bootfile name is just here for developers'
426 * convenience (it eases testing with the built-in DHCP server of QEMU
427 * that does not support RFC 5071). The official way to configure a
428 * pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
429 * So only use the pxelinux.cfg parser here for files that start with
430 * a magic comment string.
432 if (!strncasecmp("# pxelinux", cfgbuf
, 10)) {
433 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
434 int num_ent
, def_ent
= 0;
436 num_ent
= pxelinux_parse_cfg(cfgbuf
, sizeof(cfgbuf
), entries
,
437 MAX_PXELINUX_ENTRIES
, &def_ent
);
441 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
445 /* Move kernel to right location */
446 memmove(KERNEL_ADDR
, loadaddr
, rc
);
451 void panic(const char *string
)
459 void write_subsystem_identification(void)
461 SubChannelId
*schid
= (SubChannelId
*) 184;
462 uint32_t *zeroes
= (uint32_t *) 188;
468 static bool find_net_dev(Schib
*schib
, int dev_no
)
472 for (i
= 0; i
< 0x10000; i
++) {
473 net_schid
.sch_no
= i
;
474 r
= stsch_err(net_schid
, schib
);
475 if (r
== 3 || r
== -EIO
) {
478 if (!schib
->pmcw
.dnv
) {
481 enable_subchannel(net_schid
);
482 if (!virtio_is_supported(net_schid
)) {
485 if (virtio_get_device_type() != VIRTIO_ID_NET
) {
488 if (dev_no
< 0 || schib
->pmcw
.dev
== dev_no
) {
496 static void virtio_setup(void)
504 * We unconditionally enable mss support. In every sane configuration,
505 * this will succeed; and even if it doesn't, stsch_err() can deal
506 * with the consequences.
508 enable_mss_facility();
510 if (store_iplb(&iplb
)) {
511 IPL_assert(iplb
.pbt
== S390_IPL_TYPE_CCW
, "IPL_TYPE_CCW expected");
512 dev_no
= iplb
.ccw
.devno
;
513 debug_print_int("device no. ", dev_no
);
514 net_schid
.ssid
= iplb
.ccw
.ssid
& 0x3;
515 debug_print_int("ssid ", net_schid
.ssid
);
516 found
= find_net_dev(&schib
, dev_no
);
518 for (ssid
= 0; ssid
< 0x3; ssid
++) {
519 net_schid
.ssid
= ssid
;
520 found
= find_net_dev(&schib
, -1);
527 IPL_assert(found
, "No virtio net device found");
536 sclp_print("Network boot starting...\n");
540 rc
= net_init(&fn_ip
);
542 panic("Network initialization failed. Halting.\n");
545 fnlen
= strlen(fn_ip
.filename
);
546 if (fnlen
> 0 && fn_ip
.filename
[fnlen
- 1] != '/') {
547 rc
= net_try_direct_tftp_load(&fn_ip
);
550 rc
= net_try_pxelinux_cfg(&fn_ip
);
556 sclp_print("Network loading done, starting kernel...\n");
557 jump_to_low_kernel();
560 panic("Failed to load OS from network\n");