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
44 #define KERNEL_ADDR ((void *)0L)
45 #define KERNEL_MAX_SIZE ((long)_start)
46 #define ARCH_COMMAND_LINE_SIZE 896 /* Taken from Linux kernel */
48 /* STSI 3.2.2 offset of first vmdb + offset of uuid inside vmdb */
49 #define STSI322_VMDB_UUID_OFFSET ((8 + 12) * 4)
51 char stack
[PAGE_SIZE
* 8] __attribute__((aligned(PAGE_SIZE
)));
52 IplParameterBlock iplb
__attribute__((aligned(PAGE_SIZE
)));
53 static char cfgbuf
[2048];
55 static SubChannelId net_schid
= { .one
= 1 };
56 static uint8_t mac
[6];
57 static uint64_t dest_timer
;
59 static uint64_t get_timer_ms(void)
63 asm volatile(" stck %0 " : : "Q"(clk
) : "memory");
65 /* Bit 51 is incremented each microsecond */
66 return (clk
>> (63 - 51)) / 1000;
69 void set_timer(int val
)
71 dest_timer
= get_timer_ms() + val
;
76 return dest_timer
- get_timer_ms();
79 int get_sec_ticks(void)
81 return 1000; /* number of ticks in 1 second */
85 * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
86 * @param fn_ip contains the following configuration information:
87 * client MAC, client IP, TFTP-server MAC, TFTP-server IP,
89 * @param retries Number of DHCP attempts
90 * @return 0 : IP and configuration info obtained;
91 * non-0 : error condition occurred.
93 static int dhcp(struct filename_ip
*fn_ip
, int retries
)
98 printf(" Requesting information via DHCP: ");
100 dhcpv4_generate_transaction_id();
101 dhcpv6_generate_transaction_id();
104 printf("\b\b\b%03d", i
- 1);
106 printf("\nGiving up after %d DHCP requests\n", retries
);
109 fn_ip
->ip_version
= 4;
110 rc
= dhcpv4(NULL
, fn_ip
);
112 fn_ip
->ip_version
= 6;
113 set_ipv6_address(fn_ip
->fd
, 0);
114 rc
= dhcpv6(NULL
, fn_ip
);
116 memcpy(&fn_ip
->own_ip6
, get_ipv6_address(), 16);
120 if (rc
!= -1) { /* either success or non-dhcp failure */
124 printf("\b\b\b\bdone\n");
130 * Seed the random number generator with our mac and current timestamp
132 static void seed_rng(uint8_t mac
[])
136 asm volatile(" stck %0 " : : "Q"(seed
) : "memory");
137 seed
^= (mac
[2] << 24) | (mac
[3] << 16) | (mac
[4] << 8) | mac
[5];
141 static int tftp_load(filename_ip_t
*fnip
, void *buffer
, int len
)
146 rc
= tftp(fnip
, buffer
, len
, DEFAULT_TFTP_RETRIES
, &tftp_err
);
149 /* Make sure that error messages are put into a new line */
154 printf(" TFTP: Received %s (%d KBytes)\n", fnip
->filename
, rc
/ 1024);
156 printf(" TFTP: Received %s (%d Bytes)\n", fnip
->filename
, rc
);
158 const char *errstr
= NULL
;
160 tftp_get_error_info(fnip
, &tftp_err
, rc
, &errstr
, &ecode
);
161 printf("TFTP error: %s\n", errstr
? errstr
: "unknown error");
167 static int net_init(filename_ip_t
*fn_ip
)
171 memset(fn_ip
, 0, sizeof(filename_ip_t
));
173 rc
= virtio_net_init(mac
);
175 puts("Could not initialize network device");
180 printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
181 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
183 set_mac_address(mac
); /* init ethernet layer */
186 rc
= dhcp(fn_ip
, DEFAULT_BOOT_RETRIES
);
188 if (fn_ip
->ip_version
== 4) {
189 set_ipv4_address(fn_ip
->own_ip
);
192 puts("Could not get IP address");
196 if (fn_ip
->ip_version
== 4) {
197 printf(" Using IPv4 address: %d.%d.%d.%d\n",
198 (fn_ip
->own_ip
>> 24) & 0xFF, (fn_ip
->own_ip
>> 16) & 0xFF,
199 (fn_ip
->own_ip
>> 8) & 0xFF, fn_ip
->own_ip
& 0xFF);
200 } else if (fn_ip
->ip_version
== 6) {
202 ipv6_to_str(fn_ip
->own_ip6
.addr
, ip6_str
);
203 printf(" Using IPv6 address: %s\n", ip6_str
);
207 printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
208 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
209 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
212 if (rc
== -4 || rc
== -3) {
213 puts("Can't obtain TFTP server IP address");
217 printf(" Using TFTP server: ");
218 if (fn_ip
->ip_version
== 4) {
219 printf("%d.%d.%d.%d\n",
220 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
221 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
222 } else if (fn_ip
->ip_version
== 6) {
224 ipv6_to_str(fn_ip
->server_ip6
.addr
, ip6_str
);
225 printf("%s\n", ip6_str
);
228 if (strlen(fn_ip
->filename
) > 0) {
229 printf(" Bootfile name: '%s'\n", fn_ip
->filename
);
235 static void net_release(filename_ip_t
*fn_ip
)
237 if (fn_ip
->ip_version
== 4) {
238 dhcp_send_release(fn_ip
->fd
);
243 * Retrieve the Universally Unique Identifier of the VM.
244 * @return UUID string, or NULL in case of errors
246 static const char *get_uuid(void)
248 register int r0
asm("0");
249 register int r1
asm("1");
250 uint8_t *mem
, *buf
, uuid
[16];
252 static char uuid_str
[37];
254 mem
= malloc(2 * PAGE_SIZE
);
256 puts("Out of memory ... can not get UUID.");
259 buf
= (uint8_t *)(((uint64_t)mem
+ PAGE_SIZE
- 1) & ~(PAGE_SIZE
- 1));
260 memset(buf
, 0, PAGE_SIZE
);
262 /* Get SYSIB 3.2.2 */
265 asm volatile(" stsi 0(%[addr])\n"
269 : "d" (r0
), "d" (r1
), [addr
] "a" (buf
)
275 for (i
= 0; i
< 16; i
++) {
276 uuid
[i
] = buf
[STSI322_VMDB_UUID_OFFSET
+ i
];
284 sprintf(uuid_str
, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
285 "%02x%02x%02x%02x%02x%02x", uuid
[0], uuid
[1], uuid
[2], uuid
[3],
286 uuid
[4], uuid
[5], uuid
[6], uuid
[7], uuid
[8], uuid
[9], uuid
[10],
287 uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15]);
293 * Load a kernel with initrd (i.e. with the information that we've got from
294 * a pxelinux.cfg config file)
296 static int load_kernel_with_initrd(filename_ip_t
*fn_ip
,
297 struct pl_cfg_entry
*entry
)
301 printf("Loading pxelinux.cfg entry '%s'\n", entry
->label
);
303 if (!entry
->kernel
) {
304 printf("Kernel entry is missing!\n");
308 strncpy(fn_ip
->filename
, entry
->kernel
, sizeof(fn_ip
->filename
));
309 rc
= tftp_load(fn_ip
, KERNEL_ADDR
, KERNEL_MAX_SIZE
);
315 uint64_t iaddr
= (rc
+ 0xfff) & ~0xfffUL
;
317 strncpy(fn_ip
->filename
, entry
->initrd
, sizeof(fn_ip
->filename
));
318 rc
= tftp_load(fn_ip
, (void *)iaddr
, KERNEL_MAX_SIZE
- iaddr
);
322 /* Patch location and size: */
323 *(uint64_t *)0x10408 = iaddr
;
324 *(uint64_t *)0x10410 = rc
;
329 strncpy((char *)0x10480, entry
->append
, ARCH_COMMAND_LINE_SIZE
);
335 #define MAX_PXELINUX_ENTRIES 16
337 static int net_try_pxelinux_cfg(filename_ip_t
*fn_ip
)
339 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
340 int num_ent
, def_ent
= 0;
342 num_ent
= pxelinux_load_parse_cfg(fn_ip
, mac
, get_uuid(),
343 DEFAULT_TFTP_RETRIES
,
344 cfgbuf
, sizeof(cfgbuf
),
345 entries
, MAX_PXELINUX_ENTRIES
, &def_ent
);
347 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
354 * Load via information from a .INS file (which can be found on CD-ROMs
357 static int handle_ins_cfg(filename_ip_t
*fn_ip
, char *cfg
, int cfgsize
)
364 ptr
= strchr(insbuf
, '\n');
366 puts("Does not seem to be a valid .INS file");
371 printf("\nParsing .INS file:\n %s\n", &insbuf
[2]);
374 while (*insbuf
&& insbuf
< cfg
+ cfgsize
) {
375 ptr
= strchr(insbuf
, '\n');
379 llen
= strlen(insbuf
);
384 ptr
= strchr(insbuf
, ' ');
386 puts("Missing space separator in .INS file");
390 strncpy(fn_ip
->filename
, insbuf
, sizeof(fn_ip
->filename
));
391 destaddr
= (char *)atol(ptr
+ 1);
392 rc
= tftp_load(fn_ip
, destaddr
, (long)_start
- (long)destaddr
);
402 static int net_try_direct_tftp_load(filename_ip_t
*fn_ip
)
405 void *loadaddr
= (void *)0x2000; /* Load right after the low-core */
407 rc
= tftp_load(fn_ip
, loadaddr
, KERNEL_MAX_SIZE
- (long)loadaddr
);
411 printf("'%s' is too small (%i bytes only).\n", fn_ip
->filename
, rc
);
415 /* Check whether it is a configuration file instead of a kernel */
416 if (rc
< sizeof(cfgbuf
) - 1) {
417 memcpy(cfgbuf
, loadaddr
, rc
);
418 cfgbuf
[rc
] = 0; /* Make sure that it is NUL-terminated */
419 if (!strncmp("* ", cfgbuf
, 2)) {
420 return handle_ins_cfg(fn_ip
, cfgbuf
, rc
);
423 * pxelinux.cfg support via bootfile name is just here for developers'
424 * convenience (it eases testing with the built-in DHCP server of QEMU
425 * that does not support RFC 5071). The official way to configure a
426 * pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
427 * So only use the pxelinux.cfg parser here for files that start with
428 * a magic comment string.
430 if (!strncasecmp("# pxelinux", cfgbuf
, 10)) {
431 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
432 int num_ent
, def_ent
= 0;
434 num_ent
= pxelinux_parse_cfg(cfgbuf
, sizeof(cfgbuf
), entries
,
435 MAX_PXELINUX_ENTRIES
, &def_ent
);
439 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
443 /* Move kernel to right location */
444 memmove(KERNEL_ADDR
, loadaddr
, rc
);
449 void panic(const char *string
)
457 void write_subsystem_identification(void)
459 SubChannelId
*schid
= (SubChannelId
*) 184;
460 uint32_t *zeroes
= (uint32_t *) 188;
466 static bool find_net_dev(Schib
*schib
, int dev_no
)
470 for (i
= 0; i
< 0x10000; i
++) {
471 net_schid
.sch_no
= i
;
472 r
= stsch_err(net_schid
, schib
);
473 if (r
== 3 || r
== -EIO
) {
476 if (!schib
->pmcw
.dnv
) {
479 enable_subchannel(net_schid
);
480 if (!virtio_is_supported(net_schid
)) {
483 if (virtio_get_device_type() != VIRTIO_ID_NET
) {
486 if (dev_no
< 0 || schib
->pmcw
.dev
== dev_no
) {
494 static void virtio_setup(void)
502 * We unconditionally enable mss support. In every sane configuration,
503 * this will succeed; and even if it doesn't, stsch_err() can deal
504 * with the consequences.
506 enable_mss_facility();
508 if (store_iplb(&iplb
)) {
509 IPL_assert(iplb
.pbt
== S390_IPL_TYPE_CCW
, "IPL_TYPE_CCW expected");
510 dev_no
= iplb
.ccw
.devno
;
511 debug_print_int("device no. ", dev_no
);
512 net_schid
.ssid
= iplb
.ccw
.ssid
& 0x3;
513 debug_print_int("ssid ", net_schid
.ssid
);
514 found
= find_net_dev(&schib
, dev_no
);
516 for (ssid
= 0; ssid
< 0x3; ssid
++) {
517 net_schid
.ssid
= ssid
;
518 found
= find_net_dev(&schib
, -1);
525 IPL_assert(found
, "No virtio net device found");
534 sclp_print("Network boot starting...\n");
538 rc
= net_init(&fn_ip
);
540 panic("Network initialization failed. Halting.\n");
543 fnlen
= strlen(fn_ip
.filename
);
544 if (fnlen
> 0 && fn_ip
.filename
[fnlen
- 1] != '/') {
545 rc
= net_try_direct_tftp_load(&fn_ip
);
548 rc
= net_try_pxelinux_cfg(&fn_ip
);
554 sclp_print("Network loading done, starting kernel...\n");
555 jump_to_low_kernel();
558 panic("Failed to load OS from network\n");