]>
Commit | Line | Data |
---|---|---|
cfbae482 YCLP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * WGET/HTTP support driver based on U-BOOT's nfs.c | |
4 | * Copyright Duncan Hare <dh@synoia.com> 2017 | |
5 | */ | |
6 | ||
7 | #include <command.h> | |
8 | #include <common.h> | |
fe1489bc | 9 | #include <display_options.h> |
cfbae482 YCLP |
10 | #include <env.h> |
11 | #include <image.h> | |
12 | #include <mapmem.h> | |
13 | #include <net.h> | |
14 | #include <net/tcp.h> | |
15 | #include <net/wget.h> | |
16 | ||
4caacb2f MV |
17 | /* The default, change with environment variable 'httpdstp' */ |
18 | #define SERVER_PORT 80 | |
19 | ||
cfbae482 YCLP |
20 | static const char bootfile1[] = "GET "; |
21 | static const char bootfile3[] = " HTTP/1.0\r\n\r\n"; | |
22 | static const char http_eom[] = "\r\n\r\n"; | |
23 | static const char http_ok[] = "200"; | |
24 | static const char content_len[] = "Content-Length"; | |
25 | static const char linefeed[] = "\r\n"; | |
26 | static struct in_addr web_server_ip; | |
27 | static int our_port; | |
28 | static int wget_timeout_count; | |
29 | ||
30 | struct pkt_qd { | |
31 | uchar *pkt; | |
32 | unsigned int tcp_seq_num; | |
33 | unsigned int len; | |
34 | }; | |
35 | ||
36 | /* | |
37 | * This is a control structure for out of order packets received. | |
38 | * The actual packet bufers are in the kernel space, and are | |
39 | * expected to be overwritten by the downloaded image. | |
40 | */ | |
a8bd5ec0 RW |
41 | #define PKTQ_SZ (PKTBUFSRX / 4) |
42 | static struct pkt_qd pkt_q[PKTQ_SZ]; | |
cfbae482 YCLP |
43 | static int pkt_q_idx; |
44 | static unsigned long content_length; | |
45 | static unsigned int packets; | |
46 | ||
47 | static unsigned int initial_data_seq_num; | |
48 | ||
49 | static enum wget_state current_wget_state; | |
50 | ||
51 | static char *image_url; | |
52 | static unsigned int wget_timeout = WGET_TIMEOUT; | |
53 | ||
54 | static enum net_loop_state wget_loop_state; | |
55 | ||
56 | /* Timeout retry parameters */ | |
57 | static u8 retry_action; /* actions for TCP retry */ | |
58 | static unsigned int retry_tcp_ack_num; /* TCP retry acknowledge number*/ | |
59 | static unsigned int retry_tcp_seq_num; /* TCP retry sequence number */ | |
60 | static int retry_len; /* TCP retry length */ | |
61 | ||
62 | /** | |
63 | * store_block() - store block in memory | |
64 | * @src: source of data | |
65 | * @offset: offset | |
66 | * @len: length | |
67 | */ | |
68 | static inline int store_block(uchar *src, unsigned int offset, unsigned int len) | |
69 | { | |
70 | ulong newsize = offset + len; | |
71 | uchar *ptr; | |
72 | ||
73 | ptr = map_sysmem(image_load_addr + offset, len); | |
74 | memcpy(ptr, src, len); | |
75 | unmap_sysmem(ptr); | |
76 | ||
77 | if (net_boot_file_size < (offset + len)) | |
78 | net_boot_file_size = newsize; | |
79 | ||
80 | return 0; | |
81 | } | |
82 | ||
83 | /** | |
84 | * wget_send_stored() - wget response dispatcher | |
85 | * | |
86 | * WARNING, This, and only this, is the place in wget.c where | |
87 | * SEQUENCE NUMBERS are swapped between incoming (RX) | |
88 | * and outgoing (TX). | |
89 | * Procedure wget_handler() is correct for RX traffic. | |
90 | */ | |
91 | static void wget_send_stored(void) | |
92 | { | |
93 | u8 action = retry_action; | |
94 | int len = retry_len; | |
08fb8da3 DM |
95 | unsigned int tcp_ack_num = retry_tcp_seq_num + (len == 0 ? 1 : len); |
96 | unsigned int tcp_seq_num = retry_tcp_ack_num; | |
4caacb2f | 97 | unsigned int server_port; |
cfbae482 YCLP |
98 | uchar *ptr, *offset; |
99 | ||
4caacb2f MV |
100 | server_port = env_get_ulong("httpdstp", 10, SERVER_PORT) & 0xffff; |
101 | ||
cfbae482 YCLP |
102 | switch (current_wget_state) { |
103 | case WGET_CLOSED: | |
104 | debug_cond(DEBUG_WGET, "wget: send SYN\n"); | |
105 | current_wget_state = WGET_CONNECTING; | |
4caacb2f | 106 | net_send_tcp_packet(0, server_port, our_port, action, |
cfbae482 YCLP |
107 | tcp_seq_num, tcp_ack_num); |
108 | packets = 0; | |
109 | break; | |
110 | case WGET_CONNECTING: | |
111 | pkt_q_idx = 0; | |
4caacb2f | 112 | net_send_tcp_packet(0, server_port, our_port, action, |
cfbae482 YCLP |
113 | tcp_seq_num, tcp_ack_num); |
114 | ||
115 | ptr = net_tx_packet + net_eth_hdr_size() + | |
116 | IP_TCP_HDR_SIZE + TCP_TSOPT_SIZE + 2; | |
117 | offset = ptr; | |
118 | ||
119 | memcpy(offset, &bootfile1, strlen(bootfile1)); | |
120 | offset += strlen(bootfile1); | |
121 | ||
122 | memcpy(offset, image_url, strlen(image_url)); | |
123 | offset += strlen(image_url); | |
124 | ||
125 | memcpy(offset, &bootfile3, strlen(bootfile3)); | |
126 | offset += strlen(bootfile3); | |
4caacb2f | 127 | net_send_tcp_packet((offset - ptr), server_port, our_port, |
cfbae482 YCLP |
128 | TCP_PUSH, tcp_seq_num, tcp_ack_num); |
129 | current_wget_state = WGET_CONNECTED; | |
130 | break; | |
131 | case WGET_CONNECTED: | |
132 | case WGET_TRANSFERRING: | |
133 | case WGET_TRANSFERRED: | |
4caacb2f | 134 | net_send_tcp_packet(0, server_port, our_port, action, |
cfbae482 YCLP |
135 | tcp_seq_num, tcp_ack_num); |
136 | break; | |
137 | } | |
138 | } | |
139 | ||
08fb8da3 DM |
140 | static void wget_send(u8 action, unsigned int tcp_seq_num, |
141 | unsigned int tcp_ack_num, int len) | |
cfbae482 YCLP |
142 | { |
143 | retry_action = action; | |
144 | retry_tcp_ack_num = tcp_ack_num; | |
145 | retry_tcp_seq_num = tcp_seq_num; | |
146 | retry_len = len; | |
147 | ||
148 | wget_send_stored(); | |
149 | } | |
150 | ||
151 | void wget_fail(char *error_message, unsigned int tcp_seq_num, | |
152 | unsigned int tcp_ack_num, u8 action) | |
153 | { | |
154 | printf("wget: Transfer Fail - %s\n", error_message); | |
155 | net_set_timeout_handler(0, NULL); | |
156 | wget_send(action, tcp_seq_num, tcp_ack_num, 0); | |
157 | } | |
158 | ||
159 | void wget_success(u8 action, unsigned int tcp_seq_num, | |
160 | unsigned int tcp_ack_num, int len, int packets) | |
161 | { | |
162 | printf("Packets received %d, Transfer Successful\n", packets); | |
163 | wget_send(action, tcp_seq_num, tcp_ack_num, len); | |
164 | } | |
165 | ||
166 | /* | |
167 | * Interfaces of U-BOOT | |
168 | */ | |
169 | static void wget_timeout_handler(void) | |
170 | { | |
171 | if (++wget_timeout_count > WGET_RETRY_COUNT) { | |
172 | puts("\nRetry count exceeded; starting again\n"); | |
173 | wget_send(TCP_RST, 0, 0, 0); | |
174 | net_start_again(); | |
175 | } else { | |
176 | puts("T "); | |
177 | net_set_timeout_handler(wget_timeout + | |
178 | WGET_TIMEOUT * wget_timeout_count, | |
179 | wget_timeout_handler); | |
180 | wget_send_stored(); | |
181 | } | |
182 | } | |
183 | ||
184 | #define PKT_QUEUE_OFFSET 0x20000 | |
185 | #define PKT_QUEUE_PACKET_SIZE 0x800 | |
186 | ||
187 | static void wget_connected(uchar *pkt, unsigned int tcp_seq_num, | |
08fb8da3 | 188 | u8 action, unsigned int tcp_ack_num, unsigned int len) |
cfbae482 | 189 | { |
cfbae482 YCLP |
190 | uchar *pkt_in_q; |
191 | char *pos; | |
192 | int hlen, i; | |
193 | uchar *ptr1; | |
194 | ||
195 | pkt[len] = '\0'; | |
196 | pos = strstr((char *)pkt, http_eom); | |
197 | ||
198 | if (!pos) { | |
199 | debug_cond(DEBUG_WGET, | |
200 | "wget: Connected, data before Header %p\n", pkt); | |
201 | pkt_in_q = (void *)image_load_addr + PKT_QUEUE_OFFSET + | |
202 | (pkt_q_idx * PKT_QUEUE_PACKET_SIZE); | |
203 | ||
204 | ptr1 = map_sysmem((phys_addr_t)pkt_in_q, len); | |
205 | memcpy(ptr1, pkt, len); | |
206 | unmap_sysmem(ptr1); | |
207 | ||
208 | pkt_q[pkt_q_idx].pkt = pkt_in_q; | |
209 | pkt_q[pkt_q_idx].tcp_seq_num = tcp_seq_num; | |
210 | pkt_q[pkt_q_idx].len = len; | |
211 | pkt_q_idx++; | |
a8bd5ec0 RW |
212 | |
213 | if (pkt_q_idx >= PKTQ_SZ) { | |
214 | printf("wget: Fatal error, queue overrun!\n"); | |
215 | net_set_state(NETLOOP_FAIL); | |
216 | ||
217 | return; | |
218 | } | |
cfbae482 YCLP |
219 | } else { |
220 | debug_cond(DEBUG_WGET, "wget: Connected HTTP Header %p\n", pkt); | |
221 | /* sizeof(http_eom) - 1 is the string length of (http_eom) */ | |
222 | hlen = pos - (char *)pkt + sizeof(http_eom) - 1; | |
223 | pos = strstr((char *)pkt, linefeed); | |
224 | if (pos > 0) | |
225 | i = pos - (char *)pkt; | |
226 | else | |
227 | i = hlen; | |
228 | printf("%.*s", i, pkt); | |
229 | ||
230 | current_wget_state = WGET_TRANSFERRING; | |
231 | ||
232 | if (strstr((char *)pkt, http_ok) == 0) { | |
233 | debug_cond(DEBUG_WGET, | |
234 | "wget: Connected Bad Xfer\n"); | |
235 | initial_data_seq_num = tcp_seq_num + hlen; | |
236 | wget_loop_state = NETLOOP_FAIL; | |
237 | wget_send(action, tcp_seq_num, tcp_ack_num, len); | |
238 | } else { | |
239 | debug_cond(DEBUG_WGET, | |
240 | "wget: Connctd pkt %p hlen %x\n", | |
241 | pkt, hlen); | |
242 | initial_data_seq_num = tcp_seq_num + hlen; | |
243 | ||
244 | pos = strstr((char *)pkt, content_len); | |
245 | if (!pos) { | |
246 | content_length = -1; | |
247 | } else { | |
248 | pos += sizeof(content_len) + 2; | |
249 | strict_strtoul(pos, 10, &content_length); | |
250 | debug_cond(DEBUG_WGET, | |
251 | "wget: Connected Len %lu\n", | |
252 | content_length); | |
253 | } | |
254 | ||
255 | net_boot_file_size = 0; | |
256 | ||
257 | if (len > hlen) | |
258 | store_block(pkt + hlen, 0, len - hlen); | |
259 | ||
260 | debug_cond(DEBUG_WGET, | |
261 | "wget: Connected Pkt %p hlen %x\n", | |
262 | pkt, hlen); | |
263 | ||
264 | for (i = 0; i < pkt_q_idx; i++) { | |
265 | ptr1 = map_sysmem( | |
266 | (phys_addr_t)(pkt_q[i].pkt), | |
267 | pkt_q[i].len); | |
268 | store_block(ptr1, | |
269 | pkt_q[i].tcp_seq_num - | |
270 | initial_data_seq_num, | |
271 | pkt_q[i].len); | |
272 | unmap_sysmem(ptr1); | |
273 | debug_cond(DEBUG_WGET, | |
274 | "wget: Connctd pkt Q %p len %x\n", | |
275 | pkt_q[i].pkt, pkt_q[i].len); | |
276 | } | |
277 | } | |
278 | } | |
279 | wget_send(action, tcp_seq_num, tcp_ack_num, len); | |
280 | } | |
281 | ||
282 | /** | |
08fb8da3 DM |
283 | * wget_handler() - TCP handler of wget |
284 | * @pkt: pointer to the application packet | |
285 | * @dport: destination TCP port | |
286 | * @sip: source IP address | |
287 | * @sport: source TCP port | |
288 | * @tcp_seq_num: TCP sequential number | |
289 | * @tcp_ack_num: TCP acknowledgment number | |
290 | * @action: TCP action (SYN, ACK, FIN, etc) | |
291 | * @len: packet length | |
cfbae482 YCLP |
292 | * |
293 | * In the "application push" invocation, the TCP header with all | |
294 | * its information is pointed to by the packet pointer. | |
295 | */ | |
08fb8da3 DM |
296 | static void wget_handler(uchar *pkt, u16 dport, |
297 | struct in_addr sip, u16 sport, | |
298 | u32 tcp_seq_num, u32 tcp_ack_num, | |
299 | u8 action, unsigned int len) | |
cfbae482 YCLP |
300 | { |
301 | enum tcp_state wget_tcp_state = tcp_get_tcp_state(); | |
cfbae482 YCLP |
302 | |
303 | net_set_timeout_handler(wget_timeout, wget_timeout_handler); | |
304 | packets++; | |
305 | ||
306 | switch (current_wget_state) { | |
307 | case WGET_CLOSED: | |
308 | debug_cond(DEBUG_WGET, "wget: Handler: Error!, State wrong\n"); | |
309 | break; | |
310 | case WGET_CONNECTING: | |
311 | debug_cond(DEBUG_WGET, | |
08fb8da3 | 312 | "wget: Connecting In len=%x, Seq=%u, Ack=%u\n", |
cfbae482 YCLP |
313 | len, tcp_seq_num, tcp_ack_num); |
314 | if (!len) { | |
315 | if (wget_tcp_state == TCP_ESTABLISHED) { | |
316 | debug_cond(DEBUG_WGET, | |
317 | "wget: Cting, send, len=%x\n", len); | |
318 | wget_send(action, tcp_seq_num, tcp_ack_num, | |
319 | len); | |
320 | } else { | |
321 | printf("%.*s", len, pkt); | |
322 | wget_fail("wget: Handler Connected Fail\n", | |
323 | tcp_seq_num, tcp_ack_num, action); | |
324 | } | |
325 | } | |
326 | break; | |
327 | case WGET_CONNECTED: | |
08fb8da3 | 328 | debug_cond(DEBUG_WGET, "wget: Connected seq=%u, len=%x\n", |
cfbae482 YCLP |
329 | tcp_seq_num, len); |
330 | if (!len) { | |
331 | wget_fail("Image not found, no data returned\n", | |
332 | tcp_seq_num, tcp_ack_num, action); | |
333 | } else { | |
08fb8da3 | 334 | wget_connected(pkt, tcp_seq_num, action, tcp_ack_num, len); |
cfbae482 YCLP |
335 | } |
336 | break; | |
337 | case WGET_TRANSFERRING: | |
338 | debug_cond(DEBUG_WGET, | |
339 | "wget: Transferring, seq=%x, ack=%x,len=%x\n", | |
340 | tcp_seq_num, tcp_ack_num, len); | |
341 | ||
342 | if (tcp_seq_num >= initial_data_seq_num && | |
343 | store_block(pkt, tcp_seq_num - initial_data_seq_num, | |
344 | len) != 0) { | |
345 | wget_fail("wget: store error\n", | |
346 | tcp_seq_num, tcp_ack_num, action); | |
347 | return; | |
348 | } | |
349 | ||
350 | switch (wget_tcp_state) { | |
351 | case TCP_FIN_WAIT_2: | |
352 | wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, len); | |
353 | fallthrough; | |
354 | case TCP_SYN_SENT: | |
08fb8da3 | 355 | case TCP_SYN_RECEIVED: |
cfbae482 YCLP |
356 | case TCP_CLOSING: |
357 | case TCP_FIN_WAIT_1: | |
358 | case TCP_CLOSED: | |
359 | net_set_state(NETLOOP_FAIL); | |
360 | break; | |
361 | case TCP_ESTABLISHED: | |
362 | wget_send(TCP_ACK, tcp_seq_num, tcp_ack_num, | |
363 | len); | |
364 | wget_loop_state = NETLOOP_SUCCESS; | |
365 | break; | |
366 | case TCP_CLOSE_WAIT: /* End of transfer */ | |
367 | current_wget_state = WGET_TRANSFERRED; | |
368 | wget_send(action | TCP_ACK | TCP_FIN, | |
369 | tcp_seq_num, tcp_ack_num, len); | |
370 | break; | |
371 | } | |
372 | break; | |
373 | case WGET_TRANSFERRED: | |
374 | printf("Packets received %d, Transfer Successful\n", packets); | |
375 | net_set_state(wget_loop_state); | |
376 | break; | |
377 | } | |
378 | } | |
379 | ||
380 | #define RANDOM_PORT_START 1024 | |
381 | #define RANDOM_PORT_RANGE 0x4000 | |
382 | ||
383 | /** | |
384 | * random_port() - make port a little random (1024-17407) | |
385 | * | |
386 | * Return: random port number from 1024 to 17407 | |
387 | * | |
388 | * This keeps the math somewhat trivial to compute, and seems to work with | |
389 | * all supported protocols/clients/servers | |
390 | */ | |
391 | static unsigned int random_port(void) | |
392 | { | |
393 | return RANDOM_PORT_START + (get_timer(0) % RANDOM_PORT_RANGE); | |
394 | } | |
395 | ||
396 | #define BLOCKSIZE 512 | |
397 | ||
398 | void wget_start(void) | |
399 | { | |
400 | image_url = strchr(net_boot_file_name, ':'); | |
401 | if (image_url > 0) { | |
402 | web_server_ip = string_to_ip(net_boot_file_name); | |
403 | ++image_url; | |
404 | net_server_ip = web_server_ip; | |
405 | } else { | |
406 | web_server_ip = net_server_ip; | |
407 | image_url = net_boot_file_name; | |
408 | } | |
409 | ||
410 | debug_cond(DEBUG_WGET, | |
411 | "wget: Transfer HTTP Server %pI4; our IP %pI4\n", | |
412 | &web_server_ip, &net_ip); | |
413 | ||
414 | /* Check if we need to send across this subnet */ | |
415 | if (net_gateway.s_addr && net_netmask.s_addr) { | |
416 | struct in_addr our_net; | |
417 | struct in_addr server_net; | |
418 | ||
419 | our_net.s_addr = net_ip.s_addr & net_netmask.s_addr; | |
420 | server_net.s_addr = net_server_ip.s_addr & net_netmask.s_addr; | |
421 | if (our_net.s_addr != server_net.s_addr) | |
422 | debug_cond(DEBUG_WGET, | |
423 | "wget: sending through gateway %pI4", | |
424 | &net_gateway); | |
425 | } | |
426 | debug_cond(DEBUG_WGET, "URL '%s'\n", image_url); | |
427 | ||
428 | if (net_boot_file_expected_size_in_blocks) { | |
429 | debug_cond(DEBUG_WGET, "wget: Size is 0x%x Bytes = ", | |
430 | net_boot_file_expected_size_in_blocks * BLOCKSIZE); | |
431 | print_size(net_boot_file_expected_size_in_blocks * BLOCKSIZE, | |
432 | ""); | |
433 | } | |
434 | debug_cond(DEBUG_WGET, | |
435 | "\nwget:Load address: 0x%lx\nLoading: *\b", image_load_addr); | |
436 | ||
437 | net_set_timeout_handler(wget_timeout, wget_timeout_handler); | |
438 | tcp_set_tcp_handler(wget_handler); | |
439 | ||
440 | wget_timeout_count = 0; | |
441 | current_wget_state = WGET_CLOSED; | |
442 | ||
443 | our_port = random_port(); | |
444 | ||
445 | /* | |
446 | * Zero out server ether to force arp resolution in case | |
447 | * the server ip for the previous u-boot command, for example dns | |
448 | * is not the same as the web server ip. | |
449 | */ | |
450 | ||
451 | memset(net_server_ethaddr, 0, 6); | |
452 | ||
453 | wget_send(TCP_SYN, 0, 0, 0); | |
454 | } |