]>
Commit | Line | Data |
---|---|---|
3e4415a7 TH |
1 | /* |
2 | * S390 virtio-ccw network boot loading program | |
3 | * | |
4 | * Copyright 2017 Thomas Huth, Red Hat Inc. | |
5 | * | |
6 | * Based on the S390 virtio-ccw loading program (main.c) | |
7 | * Copyright (c) 2013 Alexander Graf <[email protected]> | |
8 | * | |
29d12216 TH |
9 | * And based on the network loading code from SLOF (netload.c) |
10 | * Copyright (c) 2004, 2008 IBM Corporation | |
11 | * | |
3e4415a7 TH |
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. | |
16 | */ | |
17 | ||
18 | #include <stdint.h> | |
19 | #include <stdbool.h> | |
20 | #include <stdio.h> | |
21 | #include <stdlib.h> | |
22 | #include <string.h> | |
23 | #include <unistd.h> | |
29d12216 TH |
24 | |
25 | #include <tftp.h> | |
26 | #include <ethernet.h> | |
27 | #include <dhcp.h> | |
28 | #include <dhcpv6.h> | |
29 | #include <ipv4.h> | |
30 | #include <ipv6.h> | |
31 | #include <dns.h> | |
3e4415a7 TH |
32 | #include <time.h> |
33 | ||
34 | #include "s390-ccw.h" | |
35 | #include "virtio.h" | |
36 | ||
29d12216 TH |
37 | #define DEFAULT_BOOT_RETRIES 10 |
38 | #define DEFAULT_TFTP_RETRIES 20 | |
39 | ||
3e4415a7 TH |
40 | extern char _start[]; |
41 | ||
42 | char stack[PAGE_SIZE * 8] __attribute__((aligned(PAGE_SIZE))); | |
43 | IplParameterBlock iplb __attribute__((aligned(PAGE_SIZE))); | |
44 | ||
45 | static SubChannelId net_schid = { .one = 1 }; | |
29d12216 | 46 | static int ip_version = 4; |
3e4415a7 TH |
47 | static uint64_t dest_timer; |
48 | ||
49 | static uint64_t get_timer_ms(void) | |
50 | { | |
51 | uint64_t clk; | |
52 | ||
53 | asm volatile(" stck %0 " : : "Q"(clk) : "memory"); | |
54 | ||
55 | /* Bit 51 is incremented each microsecond */ | |
56 | return (clk >> (63 - 51)) / 1000; | |
57 | } | |
58 | ||
59 | void set_timer(int val) | |
60 | { | |
61 | dest_timer = get_timer_ms() + val; | |
62 | } | |
63 | ||
64 | int get_timer(void) | |
65 | { | |
66 | return dest_timer - get_timer_ms(); | |
67 | } | |
68 | ||
69 | int get_sec_ticks(void) | |
70 | { | |
71 | return 1000; /* number of ticks in 1 second */ | |
72 | } | |
73 | ||
29d12216 TH |
74 | /** |
75 | * Obtain IP and configuration info from DHCP server (either IPv4 or IPv6). | |
76 | * @param fn_ip contains the following configuration information: | |
77 | * client MAC, client IP, TFTP-server MAC, TFTP-server IP, | |
78 | * boot file name | |
79 | * @param retries Number of DHCP attempts | |
80 | * @return 0 : IP and configuration info obtained; | |
81 | * non-0 : error condition occurred. | |
82 | */ | |
83 | static int dhcp(struct filename_ip *fn_ip, int retries) | |
84 | { | |
85 | int i = retries + 1; | |
86 | int rc = -1; | |
87 | ||
88 | printf(" Requesting information via DHCP: "); | |
89 | ||
90 | dhcpv4_generate_transaction_id(); | |
91 | dhcpv6_generate_transaction_id(); | |
92 | ||
93 | do { | |
94 | printf("\b\b\b%03d", i - 1); | |
95 | if (!--i) { | |
96 | printf("\nGiving up after %d DHCP requests\n", retries); | |
97 | return -1; | |
98 | } | |
99 | ip_version = 4; | |
100 | rc = dhcpv4(NULL, fn_ip); | |
101 | if (rc == -1) { | |
102 | ip_version = 6; | |
103 | set_ipv6_address(fn_ip->fd, 0); | |
104 | rc = dhcpv6(NULL, fn_ip); | |
105 | if (rc == 0) { | |
106 | memcpy(&fn_ip->own_ip6, get_ipv6_address(), 16); | |
107 | break; | |
108 | } | |
109 | } | |
110 | if (rc != -1) { /* either success or non-dhcp failure */ | |
111 | break; | |
112 | } | |
113 | } while (1); | |
114 | printf("\b\b\b\bdone\n"); | |
115 | ||
116 | return rc; | |
117 | } | |
118 | ||
119 | /** | |
120 | * Seed the random number generator with our mac and current timestamp | |
121 | */ | |
122 | static void seed_rng(uint8_t mac[]) | |
123 | { | |
124 | uint64_t seed; | |
125 | ||
126 | asm volatile(" stck %0 " : : "Q"(seed) : "memory"); | |
127 | seed ^= (mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5]; | |
128 | srand(seed); | |
129 | } | |
130 | ||
131 | static int tftp_load(filename_ip_t *fnip, void *buffer, int len, | |
132 | unsigned int retries, int ip_vers) | |
133 | { | |
134 | tftp_err_t tftp_err; | |
135 | int rc; | |
136 | ||
137 | rc = tftp(fnip, buffer, len, retries, &tftp_err, 1, 1428, ip_vers); | |
138 | ||
139 | if (rc > 0) { | |
140 | printf(" TFTP: Received %s (%d KBytes)\n", fnip->filename, | |
141 | rc / 1024); | |
142 | } else if (rc == -1) { | |
143 | puts("unknown TFTP error"); | |
144 | } else if (rc == -2) { | |
145 | printf("TFTP buffer of %d bytes is too small for %s\n", | |
146 | len, fnip->filename); | |
147 | } else if (rc == -3) { | |
148 | printf("file not found: %s\n", fnip->filename); | |
149 | } else if (rc == -4) { | |
150 | puts("TFTP access violation"); | |
151 | } else if (rc == -5) { | |
152 | puts("illegal TFTP operation"); | |
153 | } else if (rc == -6) { | |
154 | puts("unknown TFTP transfer ID"); | |
155 | } else if (rc == -7) { | |
156 | puts("no such TFTP user"); | |
157 | } else if (rc == -8) { | |
158 | puts("TFTP blocksize negotiation failed"); | |
159 | } else if (rc == -9) { | |
160 | puts("file exceeds maximum TFTP transfer size"); | |
161 | } else if (rc <= -10 && rc >= -15) { | |
162 | const char *icmp_err_str; | |
163 | switch (rc) { | |
164 | case -ICMP_NET_UNREACHABLE - 10: | |
165 | icmp_err_str = "net unreachable"; | |
166 | break; | |
167 | case -ICMP_HOST_UNREACHABLE - 10: | |
168 | icmp_err_str = "host unreachable"; | |
169 | break; | |
170 | case -ICMP_PROTOCOL_UNREACHABLE - 10: | |
171 | icmp_err_str = "protocol unreachable"; | |
172 | break; | |
173 | case -ICMP_PORT_UNREACHABLE - 10: | |
174 | icmp_err_str = "port unreachable"; | |
175 | break; | |
176 | case -ICMP_FRAGMENTATION_NEEDED - 10: | |
177 | icmp_err_str = "fragmentation needed and DF set"; | |
178 | break; | |
179 | case -ICMP_SOURCE_ROUTE_FAILED - 10: | |
180 | icmp_err_str = "source route failed"; | |
181 | break; | |
182 | default: | |
183 | icmp_err_str = " UNKNOWN"; | |
184 | break; | |
185 | } | |
186 | printf("ICMP ERROR \"%s\"\n", icmp_err_str); | |
187 | } else if (rc == -40) { | |
188 | printf("TFTP error occurred after %d bad packets received", | |
189 | tftp_err.bad_tftp_packets); | |
190 | } else if (rc == -41) { | |
191 | printf("TFTP error occurred after missing %d responses", | |
192 | tftp_err.no_packets); | |
193 | } else if (rc == -42) { | |
194 | printf("TFTP error missing block %d, expected block was %d", | |
195 | tftp_err.blocks_missed, | |
196 | tftp_err.blocks_received); | |
197 | } | |
198 | ||
199 | return rc; | |
200 | } | |
201 | ||
202 | static int net_load(char *buffer, int len) | |
203 | { | |
204 | filename_ip_t fn_ip; | |
205 | uint8_t mac[6]; | |
206 | int rc; | |
207 | ||
208 | memset(&fn_ip, 0, sizeof(filename_ip_t)); | |
209 | ||
210 | rc = virtio_net_init(mac); | |
211 | if (rc < 0) { | |
212 | puts("Could not initialize network device"); | |
213 | return -101; | |
214 | } | |
215 | fn_ip.fd = rc; | |
216 | ||
217 | printf(" Using MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", | |
218 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | |
219 | ||
220 | set_mac_address(mac); /* init ethernet layer */ | |
221 | seed_rng(mac); | |
222 | ||
223 | rc = dhcp(&fn_ip, DEFAULT_BOOT_RETRIES); | |
224 | if (rc >= 0) { | |
225 | if (ip_version == 4) { | |
226 | set_ipv4_address(fn_ip.own_ip); | |
227 | } | |
228 | } else { | |
229 | puts("Could not get IP address"); | |
230 | return -101; | |
231 | } | |
232 | ||
233 | if (ip_version == 4) { | |
234 | printf(" Using IPv4 address: %d.%d.%d.%d\n", | |
235 | (fn_ip.own_ip >> 24) & 0xFF, (fn_ip.own_ip >> 16) & 0xFF, | |
236 | (fn_ip.own_ip >> 8) & 0xFF, fn_ip.own_ip & 0xFF); | |
237 | } else if (ip_version == 6) { | |
238 | char ip6_str[40]; | |
239 | ipv6_to_str(fn_ip.own_ip6.addr, ip6_str); | |
240 | printf(" Using IPv6 address: %s\n", ip6_str); | |
241 | } | |
242 | ||
243 | if (rc == -2) { | |
244 | printf("ARP request to TFTP server (%d.%d.%d.%d) failed\n", | |
245 | (fn_ip.server_ip >> 24) & 0xFF, (fn_ip.server_ip >> 16) & 0xFF, | |
246 | (fn_ip.server_ip >> 8) & 0xFF, fn_ip.server_ip & 0xFF); | |
247 | return -102; | |
248 | } | |
249 | if (rc == -4 || rc == -3) { | |
250 | puts("Can't obtain TFTP server IP address"); | |
251 | return -107; | |
252 | } | |
253 | ||
254 | if (ip_version == 4) { | |
255 | printf(" Requesting file \"%s\" via TFTP from %d.%d.%d.%d\n", | |
256 | fn_ip.filename, | |
257 | (fn_ip.server_ip >> 24) & 0xFF, (fn_ip.server_ip >> 16) & 0xFF, | |
258 | (fn_ip.server_ip >> 8) & 0xFF, fn_ip.server_ip & 0xFF); | |
259 | } else if (ip_version == 6) { | |
260 | char ip6_str[40]; | |
261 | printf(" Requesting file \"%s\" via TFTP from ", fn_ip.filename); | |
262 | ipv6_to_str(fn_ip.server_ip6.addr, ip6_str); | |
263 | printf("%s\n", ip6_str); | |
264 | } | |
265 | ||
266 | /* Do the TFTP load and print error message if necessary */ | |
267 | rc = tftp_load(&fn_ip, buffer, len, DEFAULT_TFTP_RETRIES, ip_version); | |
268 | ||
269 | if (ip_version == 4) { | |
270 | dhcp_send_release(fn_ip.fd); | |
271 | } | |
272 | ||
273 | return rc; | |
274 | } | |
275 | ||
3e4415a7 TH |
276 | void panic(const char *string) |
277 | { | |
278 | sclp_print(string); | |
279 | for (;;) { | |
280 | disabled_wait(); | |
281 | } | |
282 | } | |
283 | ||
284 | static bool find_net_dev(Schib *schib, int dev_no) | |
285 | { | |
286 | int i, r; | |
287 | ||
288 | for (i = 0; i < 0x10000; i++) { | |
289 | net_schid.sch_no = i; | |
290 | r = stsch_err(net_schid, schib); | |
291 | if (r == 3 || r == -EIO) { | |
292 | break; | |
293 | } | |
294 | if (!schib->pmcw.dnv) { | |
295 | continue; | |
296 | } | |
297 | if (!virtio_is_supported(net_schid)) { | |
298 | continue; | |
299 | } | |
300 | if (virtio_get_device_type() != VIRTIO_ID_NET) { | |
301 | continue; | |
302 | } | |
303 | if (dev_no < 0 || schib->pmcw.dev == dev_no) { | |
304 | return true; | |
305 | } | |
306 | } | |
307 | ||
308 | return false; | |
309 | } | |
310 | ||
311 | static void virtio_setup(void) | |
312 | { | |
313 | Schib schib; | |
314 | int ssid; | |
315 | bool found = false; | |
316 | uint16_t dev_no; | |
317 | ||
318 | /* | |
319 | * We unconditionally enable mss support. In every sane configuration, | |
320 | * this will succeed; and even if it doesn't, stsch_err() can deal | |
321 | * with the consequences. | |
322 | */ | |
323 | enable_mss_facility(); | |
324 | ||
325 | if (store_iplb(&iplb)) { | |
326 | IPL_assert(iplb.pbt == S390_IPL_TYPE_CCW, "IPL_TYPE_CCW expected"); | |
327 | dev_no = iplb.ccw.devno; | |
328 | debug_print_int("device no. ", dev_no); | |
329 | net_schid.ssid = iplb.ccw.ssid & 0x3; | |
330 | debug_print_int("ssid ", net_schid.ssid); | |
331 | found = find_net_dev(&schib, dev_no); | |
332 | } else { | |
333 | for (ssid = 0; ssid < 0x3; ssid++) { | |
334 | net_schid.ssid = ssid; | |
335 | found = find_net_dev(&schib, -1); | |
336 | if (found) { | |
337 | break; | |
338 | } | |
339 | } | |
340 | } | |
341 | ||
342 | IPL_assert(found, "No virtio net device found"); | |
343 | } | |
344 | ||
345 | void main(void) | |
346 | { | |
29d12216 TH |
347 | int rc; |
348 | ||
3e4415a7 TH |
349 | sclp_setup(); |
350 | sclp_print("Network boot starting...\n"); | |
351 | ||
352 | virtio_setup(); | |
353 | ||
29d12216 TH |
354 | rc = net_load(NULL, (long)_start); |
355 | if (rc > 0) { | |
356 | sclp_print("Network loading done, starting kernel...\n"); | |
357 | asm volatile (" lpsw 0(%0) " : : "r"(0) : "memory"); | |
358 | } | |
359 | ||
3e4415a7 TH |
360 | panic("Failed to load OS from network\n"); |
361 | } |