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