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 #define DEFAULT_BOOT_RETRIES 10
39 #define DEFAULT_TFTP_RETRIES 20
43 #define KERNEL_ADDR ((void *)0L)
44 #define KERNEL_MAX_SIZE ((long)_start)
45 #define ARCH_COMMAND_LINE_SIZE 896 /* Taken from Linux kernel */
47 /* STSI 3.2.2 offset of first vmdb + offset of uuid inside vmdb */
48 #define STSI322_VMDB_UUID_OFFSET ((8 + 12) * 4)
50 char stack
[PAGE_SIZE
* 8] __attribute__((aligned(PAGE_SIZE
)));
51 IplParameterBlock iplb
__attribute__((aligned(PAGE_SIZE
)));
52 static char cfgbuf
[2048];
54 static SubChannelId net_schid
= { .one
= 1 };
55 static uint8_t mac
[6];
56 static uint64_t dest_timer
;
58 static uint64_t get_timer_ms(void)
62 asm volatile(" stck %0 " : : "Q"(clk
) : "memory");
64 /* Bit 51 is incremented each microsecond */
65 return (clk
>> (63 - 51)) / 1000;
68 void set_timer(int val
)
70 dest_timer
= get_timer_ms() + val
;
75 return dest_timer
- get_timer_ms();
78 int get_sec_ticks(void)
80 return 1000; /* number of ticks in 1 second */
84 * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6).
85 * @param fn_ip contains the following configuration information:
86 * client MAC, client IP, TFTP-server MAC, TFTP-server IP,
88 * @param retries Number of DHCP attempts
89 * @return 0 : IP and configuration info obtained;
90 * non-0 : error condition occurred.
92 static int dhcp(struct filename_ip
*fn_ip
, int retries
)
97 printf(" Requesting information via DHCP: ");
99 dhcpv4_generate_transaction_id();
100 dhcpv6_generate_transaction_id();
103 printf("\b\b\b%03d", i
- 1);
105 printf("\nGiving up after %d DHCP requests\n", retries
);
108 fn_ip
->ip_version
= 4;
109 rc
= dhcpv4(NULL
, fn_ip
);
111 fn_ip
->ip_version
= 6;
112 set_ipv6_address(fn_ip
->fd
, 0);
113 rc
= dhcpv6(NULL
, fn_ip
);
115 memcpy(&fn_ip
->own_ip6
, get_ipv6_address(), 16);
119 if (rc
!= -1) { /* either success or non-dhcp failure */
123 printf("\b\b\b\bdone\n");
129 * Seed the random number generator with our mac and current timestamp
131 static void seed_rng(uint8_t mac
[])
135 asm volatile(" stck %0 " : : "Q"(seed
) : "memory");
136 seed
^= (mac
[2] << 24) | (mac
[3] << 16) | (mac
[4] << 8) | mac
[5];
140 static int tftp_load(filename_ip_t
*fnip
, void *buffer
, int len
)
145 rc
= tftp(fnip
, buffer
, len
, DEFAULT_TFTP_RETRIES
, &tftp_err
);
148 /* Make sure that error messages are put into a new line */
153 printf(" TFTP: Received %s (%d KBytes)\n", fnip
->filename
, rc
/ 1024);
155 printf(" TFTP: Received %s (%d Bytes)\n", fnip
->filename
, rc
);
157 const char *errstr
= NULL
;
159 tftp_get_error_info(fnip
, &tftp_err
, rc
, &errstr
, &ecode
);
160 printf("TFTP error: %s\n", errstr
? errstr
: "unknown error");
166 static int net_init(filename_ip_t
*fn_ip
)
170 memset(fn_ip
, 0, sizeof(filename_ip_t
));
172 rc
= virtio_net_init(mac
);
174 puts("Could not initialize network device");
179 printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
180 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5]);
182 set_mac_address(mac
); /* init ethernet layer */
185 rc
= dhcp(fn_ip
, DEFAULT_BOOT_RETRIES
);
187 if (fn_ip
->ip_version
== 4) {
188 set_ipv4_address(fn_ip
->own_ip
);
191 puts("Could not get IP address");
195 if (fn_ip
->ip_version
== 4) {
196 printf(" Using IPv4 address: %d.%d.%d.%d\n",
197 (fn_ip
->own_ip
>> 24) & 0xFF, (fn_ip
->own_ip
>> 16) & 0xFF,
198 (fn_ip
->own_ip
>> 8) & 0xFF, fn_ip
->own_ip
& 0xFF);
199 } else if (fn_ip
->ip_version
== 6) {
201 ipv6_to_str(fn_ip
->own_ip6
.addr
, ip6_str
);
202 printf(" Using IPv6 address: %s\n", ip6_str
);
206 printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n",
207 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
208 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
211 if (rc
== -4 || rc
== -3) {
212 puts("Can't obtain TFTP server IP address");
216 printf(" Using TFTP server: ");
217 if (fn_ip
->ip_version
== 4) {
218 printf("%d.%d.%d.%d\n",
219 (fn_ip
->server_ip
>> 24) & 0xFF, (fn_ip
->server_ip
>> 16) & 0xFF,
220 (fn_ip
->server_ip
>> 8) & 0xFF, fn_ip
->server_ip
& 0xFF);
221 } else if (fn_ip
->ip_version
== 6) {
223 ipv6_to_str(fn_ip
->server_ip6
.addr
, ip6_str
);
224 printf("%s\n", ip6_str
);
227 if (strlen(fn_ip
->filename
) > 0) {
228 printf(" Bootfile name: '%s'\n", fn_ip
->filename
);
234 static void net_release(filename_ip_t
*fn_ip
)
236 if (fn_ip
->ip_version
== 4) {
237 dhcp_send_release(fn_ip
->fd
);
242 * Retrieve the Universally Unique Identifier of the VM.
243 * @return UUID string, or NULL in case of errors
245 static const char *get_uuid(void)
247 register int r0
asm("0");
248 register int r1
asm("1");
249 uint8_t *mem
, *buf
, uuid
[16];
251 static char uuid_str
[37];
253 mem
= malloc(2 * PAGE_SIZE
);
255 puts("Out of memory ... can not get UUID.");
258 buf
= (uint8_t *)(((uint64_t)mem
+ PAGE_SIZE
- 1) & ~(PAGE_SIZE
- 1));
259 memset(buf
, 0, PAGE_SIZE
);
261 /* Get SYSIB 3.2.2 */
264 asm volatile(" stsi 0(%[addr])\n"
268 : "d" (r0
), "d" (r1
), [addr
] "a" (buf
)
274 for (i
= 0; i
< 16; i
++) {
275 uuid
[i
] = buf
[STSI322_VMDB_UUID_OFFSET
+ i
];
283 sprintf(uuid_str
, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
284 "%02x%02x%02x%02x%02x%02x", uuid
[0], uuid
[1], uuid
[2], uuid
[3],
285 uuid
[4], uuid
[5], uuid
[6], uuid
[7], uuid
[8], uuid
[9], uuid
[10],
286 uuid
[11], uuid
[12], uuid
[13], uuid
[14], uuid
[15]);
292 * Load a kernel with initrd (i.e. with the information that we've got from
293 * a pxelinux.cfg config file)
295 static int load_kernel_with_initrd(filename_ip_t
*fn_ip
,
296 struct pl_cfg_entry
*entry
)
300 printf("Loading pxelinux.cfg entry '%s'\n", entry
->label
);
302 if (!entry
->kernel
) {
303 printf("Kernel entry is missing!\n");
307 strncpy(fn_ip
->filename
, entry
->kernel
, sizeof(fn_ip
->filename
));
308 rc
= tftp_load(fn_ip
, KERNEL_ADDR
, KERNEL_MAX_SIZE
);
314 uint64_t iaddr
= (rc
+ 0xfff) & ~0xfffUL
;
316 strncpy(fn_ip
->filename
, entry
->initrd
, sizeof(fn_ip
->filename
));
317 rc
= tftp_load(fn_ip
, (void *)iaddr
, KERNEL_MAX_SIZE
- iaddr
);
321 /* Patch location and size: */
322 *(uint64_t *)0x10408 = iaddr
;
323 *(uint64_t *)0x10410 = rc
;
328 strncpy((char *)0x10480, entry
->append
, ARCH_COMMAND_LINE_SIZE
);
334 #define MAX_PXELINUX_ENTRIES 16
336 static int net_try_pxelinux_cfg(filename_ip_t
*fn_ip
)
338 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
339 int num_ent
, def_ent
= 0;
341 num_ent
= pxelinux_load_parse_cfg(fn_ip
, mac
, get_uuid(),
342 DEFAULT_TFTP_RETRIES
,
343 cfgbuf
, sizeof(cfgbuf
),
344 entries
, MAX_PXELINUX_ENTRIES
, &def_ent
);
346 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
353 * Load via information from a .INS file (which can be found on CD-ROMs
356 static int handle_ins_cfg(filename_ip_t
*fn_ip
, char *cfg
, int cfgsize
)
363 ptr
= strchr(insbuf
, '\n');
365 puts("Does not seem to be a valid .INS file");
370 printf("\nParsing .INS file:\n %s\n", &insbuf
[2]);
373 while (*insbuf
&& insbuf
< cfg
+ cfgsize
) {
374 ptr
= strchr(insbuf
, '\n');
378 llen
= strlen(insbuf
);
383 ptr
= strchr(insbuf
, ' ');
385 puts("Missing space separator in .INS file");
389 strncpy(fn_ip
->filename
, insbuf
, sizeof(fn_ip
->filename
));
390 destaddr
= (char *)atol(ptr
+ 1);
391 rc
= tftp_load(fn_ip
, destaddr
, (long)_start
- (long)destaddr
);
401 static int net_try_direct_tftp_load(filename_ip_t
*fn_ip
)
404 void *loadaddr
= (void *)0x2000; /* Load right after the low-core */
406 rc
= tftp_load(fn_ip
, loadaddr
, KERNEL_MAX_SIZE
- (long)loadaddr
);
410 printf("'%s' is too small (%i bytes only).\n", fn_ip
->filename
, rc
);
414 /* Check whether it is a configuration file instead of a kernel */
415 if (rc
< sizeof(cfgbuf
) - 1) {
416 memcpy(cfgbuf
, loadaddr
, rc
);
417 cfgbuf
[rc
] = 0; /* Make sure that it is NUL-terminated */
418 if (!strncmp("* ", cfgbuf
, 2)) {
419 return handle_ins_cfg(fn_ip
, cfgbuf
, rc
);
422 * pxelinux.cfg support via bootfile name is just here for developers'
423 * convenience (it eases testing with the built-in DHCP server of QEMU
424 * that does not support RFC 5071). The official way to configure a
425 * pxelinux.cfg file name is to use DHCP options 209 and 210 instead.
426 * So only use the pxelinux.cfg parser here for files that start with
427 * a magic comment string.
429 if (!strncasecmp("# pxelinux", cfgbuf
, 10)) {
430 struct pl_cfg_entry entries
[MAX_PXELINUX_ENTRIES
];
431 int num_ent
, def_ent
= 0;
433 num_ent
= pxelinux_parse_cfg(cfgbuf
, sizeof(cfgbuf
), entries
,
434 MAX_PXELINUX_ENTRIES
, &def_ent
);
438 return load_kernel_with_initrd(fn_ip
, &entries
[def_ent
]);
442 /* Move kernel to right location */
443 memmove(KERNEL_ADDR
, loadaddr
, rc
);
448 void panic(const char *string
)
456 void write_subsystem_identification(void)
458 SubChannelId
*schid
= (SubChannelId
*) 184;
459 uint32_t *zeroes
= (uint32_t *) 188;
465 static bool find_net_dev(Schib
*schib
, int dev_no
)
469 for (i
= 0; i
< 0x10000; i
++) {
470 net_schid
.sch_no
= i
;
471 r
= stsch_err(net_schid
, schib
);
472 if (r
== 3 || r
== -EIO
) {
475 if (!schib
->pmcw
.dnv
) {
478 if (!virtio_is_supported(net_schid
)) {
481 if (virtio_get_device_type() != VIRTIO_ID_NET
) {
484 if (dev_no
< 0 || schib
->pmcw
.dev
== dev_no
) {
492 static void virtio_setup(void)
500 * We unconditionally enable mss support. In every sane configuration,
501 * this will succeed; and even if it doesn't, stsch_err() can deal
502 * with the consequences.
504 enable_mss_facility();
506 if (store_iplb(&iplb
)) {
507 IPL_assert(iplb
.pbt
== S390_IPL_TYPE_CCW
, "IPL_TYPE_CCW expected");
508 dev_no
= iplb
.ccw
.devno
;
509 debug_print_int("device no. ", dev_no
);
510 net_schid
.ssid
= iplb
.ccw
.ssid
& 0x3;
511 debug_print_int("ssid ", net_schid
.ssid
);
512 found
= find_net_dev(&schib
, dev_no
);
514 for (ssid
= 0; ssid
< 0x3; ssid
++) {
515 net_schid
.ssid
= ssid
;
516 found
= find_net_dev(&schib
, -1);
523 IPL_assert(found
, "No virtio net device found");
532 sclp_print("Network boot starting...\n");
536 rc
= net_init(&fn_ip
);
538 panic("Network initialization failed. Halting.\n");
541 fnlen
= strlen(fn_ip
.filename
);
542 if (fnlen
> 0 && fn_ip
.filename
[fnlen
- 1] != '/') {
543 rc
= net_try_direct_tftp_load(&fn_ip
);
546 rc
= net_try_pxelinux_cfg(&fn_ip
);
552 sclp_print("Network loading done, starting kernel...\n");
553 jump_to_low_kernel();
556 panic("Failed to load OS from network\n");