xref: /rk3399_rockchip-uboot/net/fastboot.c (revision 9ec8daff33ec31bf37d9083ebc4441a35c42aaa5)
1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6 
7 #include <common.h>
8 #include <fastboot.h>
9 #include <fb_mmc.h>
10 #include <net.h>
11 #include <net/fastboot.h>
12 #include <part.h>
13 #include <stdlib.h>
14 #include <version.h>
15 
16 /* Fastboot port # defined in spec */
17 #define WELL_KNOWN_PORT 5554
18 
19 enum {
20 	FASTBOOT_ERROR = 0,
21 	FASTBOOT_QUERY = 1,
22 	FASTBOOT_INIT = 2,
23 	FASTBOOT_FASTBOOT = 3,
24 };
25 
26 struct __attribute__((packed)) fastboot_header {
27 	uchar id;
28 	uchar flags;
29 	unsigned short seq;
30 };
31 
32 #define PACKET_SIZE 1024
33 #define FASTBOOT_HEADER_SIZE sizeof(struct fastboot_header)
34 #define DATA_SIZE (PACKET_SIZE - FASTBOOT_HEADER_SIZE)
35 #define FASTBOOT_VERSION "0.4"
36 
37 /* Sequence number sent for every packet */
38 static unsigned short fb_sequence_number = 1;
39 static const unsigned short fb_packet_size = PACKET_SIZE;
40 static const unsigned short fb_udp_version = 1;
41 
42 /* Keep track of last packet for resubmission */
43 static uchar last_packet[PACKET_SIZE];
44 static unsigned int last_packet_len = 0;
45 
46 /* Parsed from first fastboot command packet */
47 static char *cmd_string = NULL;
48 static char *cmd_parameter = NULL;
49 
50 /* Fastboot download parameters */
51 static unsigned int bytes_received = 0;
52 static unsigned int bytes_expected = 0;
53 static unsigned int image_size = 0;
54 
55 static struct in_addr fastboot_remote_ip;
56 /* The UDP port at their end */
57 static int fastboot_remote_port;
58 /* The UDP port at our end */
59 static int fastboot_our_port;
60 
61 static void fb_getvar(char*);
62 static void fb_download(char*, unsigned int, char*);
63 static void fb_flash(char*);
64 static void fb_erase(char*);
65 static void fb_continue(char*);
66 static void fb_reboot(char*);
67 static void boot_downloaded_image(void);
68 static void cleanup_command_data(void);
69 static void write_fb_response(const char*, const char*, char*);
70 
71 void fastboot_send_info(const char *msg)
72 {
73 	uchar *packet;
74 	uchar *packet_base;
75 	int len = 0;
76 	char response[FASTBOOT_RESPONSE_LEN] = {0};
77 
78 	struct fastboot_header fb_response_header =
79 	{
80 		.id = FASTBOOT_FASTBOOT,
81 		.flags = 0,
82 		.seq = htons(fb_sequence_number)
83 	};
84 	++fb_sequence_number;
85 	packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
86 	packet_base = packet;
87 
88 	/* Write headers */
89 	memcpy(packet, &fb_response_header, sizeof(fb_response_header));
90 	packet += sizeof(fb_response_header);
91 	/* Write response */
92 	write_fb_response("INFO", msg, response);
93 	memcpy(packet, response, strlen(response));
94 	packet += strlen(response);
95 
96 	len = packet-packet_base;
97 
98 	/* Save packet for retransmitting */
99 	last_packet_len = len;
100 	memcpy(last_packet, packet_base, last_packet_len);
101 
102 	net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
103 			    fastboot_remote_port, fastboot_our_port, len);
104 }
105 
106 /**
107  * Constructs and sends a packet in response to received fastboot packet
108  *
109  * @param fb_header            Header for response packet
110  * @param fastboot_data        Pointer to received fastboot data
111  * @param fastboot_data_len    Length of received fastboot data
112  * @param retransmit           Nonzero if sending last sent packet
113  */
114 static void fastboot_send(struct fastboot_header fb_header, char *fastboot_data,
115 		unsigned int fastboot_data_len, uchar retransmit)
116 {
117 	uchar *packet;
118 	uchar *packet_base;
119 	int len = 0;
120 	const char *error_msg = "An error occurred.";
121 	short tmp;
122 	struct fastboot_header fb_response_header = fb_header;
123 	char response[FASTBOOT_RESPONSE_LEN] = {0};
124 	/*
125 	 *	We will always be sending some sort of packet, so
126 	 *	cobble together the packet headers now.
127 	 */
128 	packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
129 	packet_base = packet;
130 
131 	/* Resend last packet */
132 	if (retransmit) {
133 		memcpy(packet, last_packet, last_packet_len);
134 		net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
135 				    fastboot_remote_port, fastboot_our_port, last_packet_len);
136 		return;
137 	}
138 
139 	fb_response_header.seq = htons(fb_response_header.seq);
140 	memcpy(packet, &fb_response_header, sizeof(fb_response_header));
141 	packet += sizeof(fb_response_header);
142 
143 	switch (fb_header.id) {
144 	case FASTBOOT_QUERY:
145 		tmp = htons(fb_sequence_number);
146 		memcpy(packet, &tmp, sizeof(tmp));
147 		packet += sizeof(tmp);
148 		break;
149 	case FASTBOOT_INIT:
150 		tmp = htons(fb_udp_version);
151 		memcpy(packet, &tmp, sizeof(tmp));
152 		packet += sizeof(tmp);
153 		tmp = htons(fb_packet_size);
154 		memcpy(packet, &tmp, sizeof(tmp));
155 		packet += sizeof(tmp);
156 		break;
157 	case FASTBOOT_ERROR:
158 		memcpy(packet, error_msg, strlen(error_msg));
159 		packet += strlen(error_msg);
160 		break;
161 	case FASTBOOT_FASTBOOT:
162 		if (cmd_string == NULL) {
163 			/* Parse command and send ack */
164 			cmd_parameter = fastboot_data;
165 			cmd_string = strsep(&cmd_parameter, ":");
166 			cmd_string = strdup(cmd_string);
167 			if (cmd_parameter) {
168 				cmd_parameter = strdup(cmd_parameter);
169 			}
170 		} else if (!strcmp("getvar", cmd_string)) {
171 			fb_getvar(response);
172 		} else if (!strcmp("download", cmd_string)) {
173 			fb_download(fastboot_data, fastboot_data_len, response);
174 		} else if (!strcmp("flash", cmd_string)) {
175 			fb_flash(response);
176 		} else if (!strcmp("erase", cmd_string)) {
177 			fb_erase(response);
178 		} else if (!strcmp("boot", cmd_string)) {
179 			write_fb_response("OKAY", "", response);
180 		} else if (!strcmp("continue", cmd_string)) {
181 			fb_continue(response);
182 		} else if (!strncmp("reboot", cmd_string, 6)) {
183 			fb_reboot(response);
184 		} else if (!strcmp("set_active", cmd_string)) {
185 			/* A/B not implemented, for now do nothing */
186 			write_fb_response("OKAY", "", response);
187 		} else {
188 			error("command %s not implemented.\n", cmd_string);
189 			write_fb_response("FAIL", "unrecognized command", response);
190 		}
191 		/* Sent some INFO packets, need to update sequence number in header */
192 		if (fb_header.seq != fb_sequence_number) {
193 			fb_response_header.seq = htons(fb_sequence_number);
194 			memcpy(packet_base, &fb_response_header, sizeof(fb_response_header));
195 		}
196 		/* Write response to packet */
197 		memcpy(packet, response, strlen(response));
198 		packet += strlen(response);
199 		break;
200 	default:
201 		error("ID %d not implemented.\n", fb_header.id);
202 		return;
203 	}
204 
205 	len = packet-packet_base;
206 
207 	/* Save packet for retransmitting */
208 	last_packet_len = len;
209 	memcpy(last_packet, packet_base, last_packet_len);
210 
211 	net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
212 			    fastboot_remote_port, fastboot_our_port, len);
213 
214 	/* Continue boot process after sending response */
215 	if (!strncmp("OKAY", response, 4)) {
216 		if (!strcmp("boot", cmd_string)) {
217 			boot_downloaded_image();
218 		} else if (!strcmp("continue", cmd_string)) {
219 			run_command(getenv("bootcmd"), CMD_FLAG_ENV);
220 		} else if (!strncmp("reboot", cmd_string, 6)) {
221 			/* Matches reboot or reboot-bootloader */
222 			do_reset(NULL, 0, 0, NULL);
223 		}
224 	}
225 
226 	/* OKAY and FAIL indicate command is complete */
227 	if (!strncmp("OKAY", response, 4) ||
228 			!strncmp("FAIL", response, 4)) {
229 		cleanup_command_data();
230 	}
231 }
232 
233 /**
234  * Writes ascii string specified by cmd_parameter to response.
235  *
236  * @param repsonse    Pointer to fastboot response buffer
237  */
238 static void fb_getvar(char *response)
239 {
240 
241 	if (cmd_parameter == NULL) {
242 		write_fb_response("FAIL", "missing var", response);
243 	} else if (!strcmp("version", cmd_parameter)) {
244 		write_fb_response("OKAY", FASTBOOT_VERSION, response);
245 	} else if (!strcmp("bootloader-version", cmd_parameter) ||
246 			!strcmp("version-bootloader", cmd_parameter)) {
247 		write_fb_response("OKAY", U_BOOT_VERSION, response);
248 	} else if (!strcmp("downloadsize", cmd_parameter) ||
249 			!strcmp("max-download-size", cmd_parameter)) {
250 		char buf_size_str[12];
251 		sprintf(buf_size_str, "0x%08x", CONFIG_FASTBOOT_BUF_SIZE);
252 		write_fb_response("OKAY", buf_size_str, response);
253 	} else if (!strcmp("serialno", cmd_parameter)) {
254 		const char *tmp = getenv("serial#");
255 		if (tmp) {
256 			write_fb_response("OKAY", tmp, response);
257 		} else {
258 			write_fb_response("FAIL", "Value not set", response);
259 		}
260 	} else if (!strcmp("version-baseband", cmd_parameter)) {
261 		write_fb_response("OKAY", "N/A", response);
262 	} else if (!strcmp("product", cmd_parameter)) {
263 		const char *board = getenv("board");
264 		if (board) {
265 			write_fb_response("OKAY", board, response);
266 		} else {
267 			write_fb_response("FAIL", "Board not set", response);
268 		}
269 	} else if (!strcmp("current-slot", cmd_parameter)) {
270 		/* A/B not implemented, for now always return _a */
271 		write_fb_response("OKAY", "_a", response);
272 	} else if (!strcmp("slot-suffixes", cmd_parameter)) {
273 		write_fb_response("OKAY", "_a,_b", response);
274 	} else if (!strncmp("has-slot", cmd_parameter, 8)) {
275 		char *part_name = cmd_parameter;
276 
277 		cmd_parameter = strsep(&part_name, ":");
278 		if (!strcmp(part_name, "boot") || !strcmp(part_name, "system")) {
279 			write_fb_response("OKAY", "yes", response);
280 		} else {
281 			write_fb_response("OKAY", "no", response);
282 		}
283 	} else if (!strncmp("partition-type", cmd_parameter, 14)) {
284 		disk_partition_t part_info;
285 		struct blk_desc *dev_desc;
286 		char *part_name = cmd_parameter;
287 
288 		cmd_parameter = strsep(&part_name, ":");
289 		dev_desc = blk_get_dev("mmc", 0);
290 		if (dev_desc == NULL) {
291 			write_fb_response("FAIL", "block device not found", response);
292 		} else if (part_get_info_efi_by_name(dev_desc, part_name, &part_info) < 0) {
293 			write_fb_response("FAIL", "partition not found", response);
294 		} else {
295 			write_fb_response("OKAY", (char*)part_info.type, response);
296 		}
297 	} else {
298 		printf("WARNING: unknown variable: %s\n", cmd_parameter);
299 		write_fb_response("FAIL", "Variable not implemented", response);
300 	}
301 }
302 
303 /**
304  * Copies image data from fastboot_data to CONFIG_FASTBOOT_BUF_ADDR.
305  * Writes to response.
306  *
307  * @param fastboot_data        Pointer to received fastboot data
308  * @param fastboot_data_len    Length of received fastboot data
309  * @param repsonse             Pointer to fastboot response buffer
310  */
311 static void fb_download(char *fastboot_data, unsigned int fastboot_data_len,
312 		char *response)
313 {
314 	char *tmp;
315 
316 	if (bytes_expected == 0) {
317 		if (cmd_parameter == NULL) {
318 			write_fb_response("FAIL", "Expected command parameter", response);
319 			return;
320 		}
321 		bytes_expected = simple_strtoul(cmd_parameter, &tmp, 16);
322 		if (bytes_expected == 0) {
323 			write_fb_response("FAIL", "Expected nonzero image size", response);
324 			return;
325 		}
326 	}
327 	if (fastboot_data_len == 0 && bytes_received == 0) {
328 		/* Nothing to download yet. Response is of the form:
329 		 * [DATA|FAIL]$cmd_parameter
330 		 *
331 		 * where cmd_parameter is an 8 digit hexadecimal number
332 		 */
333 		if (bytes_expected > CONFIG_FASTBOOT_BUF_SIZE) {
334 			write_fb_response("FAIL", cmd_parameter, response);
335 		} else {
336 			write_fb_response("DATA", cmd_parameter, response);
337 		}
338 	} else if (fastboot_data_len == 0 && (bytes_received >= bytes_expected)) {
339 		/* Download complete. Respond with "OKAY" */
340 		write_fb_response("OKAY", "", response);
341 		image_size = bytes_received;
342 		bytes_expected = bytes_received = 0;
343 	} else {
344 		if (fastboot_data_len == 0 ||
345 				(bytes_received + fastboot_data_len) > bytes_expected) {
346 			write_fb_response("FAIL", "Received invalid data length", response);
347 			return;
348 		}
349 		/* Download data to CONFIG_FASTBOOT_BUF_ADDR */
350 		memcpy((void*)CONFIG_FASTBOOT_BUF_ADDR + bytes_received, fastboot_data,
351 				fastboot_data_len);
352 		bytes_received += fastboot_data_len;
353 	}
354 }
355 
356 /**
357  * Writes the previously downloaded image to the partition indicated by
358  * cmd_parameter. Writes to response.
359  *
360  * @param repsonse    Pointer to fastboot response buffer
361  */
362 static void fb_flash(char *response)
363 {
364 	fb_mmc_flash_write(cmd_parameter, (void*)CONFIG_FASTBOOT_BUF_ADDR,
365 			image_size, response);
366 }
367 
368 /**
369  * Erases the partition indicated by cmd_parameter (clear to 0x00s). Writes
370  * to response.
371  *
372  * @param repsonse    Pointer to fastboot response buffer
373  */
374 static void fb_erase(char *response)
375 {
376 	fb_mmc_erase(cmd_parameter, response);
377 }
378 
379 /**
380  * Continues normal boot process by running "bootcmd". Writes
381  * to response.
382  *
383  * @param repsonse    Pointer to fastboot response buffer
384  */
385 static void fb_continue(char *response)
386 {
387 	char *bootcmd;
388 	bootcmd = getenv("bootcmd");
389 	if (bootcmd) {
390 		write_fb_response("OKAY", "", response);
391 	} else {
392 		write_fb_response("FAIL", "bootcmd not set", response);
393 	}
394 }
395 
396 /**
397  * Sets reboot bootloader flag if requested. Writes to response.
398  *
399  * @param repsonse    Pointer to fastboot response buffer
400  */
401 static void fb_reboot(char *response)
402 {
403 	write_fb_response("OKAY", "", response);
404 	if (!strcmp("reboot-bootloader", cmd_string)) {
405 		strcpy((char*)CONFIG_FASTBOOT_BUF_ADDR, "reboot-bootloader");
406 	}
407 }
408 
409 /**
410  * Boots into downloaded image.
411  */
412 static void boot_downloaded_image(void)
413 {
414 	char kernel_addr[12];
415 	char *fdt_addr = getenv("fdt_addr_r");
416 	char *bootm_args[] = { "bootm", kernel_addr, "-", fdt_addr, NULL };
417 
418 	sprintf(kernel_addr, "0x%lx", (long)CONFIG_FASTBOOT_BUF_ADDR);
419 
420 	printf("\nBooting kernel at %s with fdt at %s...\n\n\n",
421 			kernel_addr, fdt_addr);
422 	do_bootm(NULL, 0, 4, bootm_args);
423 
424 	/* This only happens if image is faulty so we start over. */
425 	do_reset(NULL, 0, 0, NULL);
426 }
427 
428 /**
429  * Writes a response to response buffer of the form "$tag$reason".
430  *
431  * @param tag         The first part of the response
432  * @param reason      The second part of the response
433  * @param repsonse    Pointer to fastboot response buffer
434  */
435 static void write_fb_response(const char* tag, const char *reason,
436 		char *response)
437 {
438 	strncpy(response, tag, strlen(tag));
439 	strncat(response, reason, FASTBOOT_RESPONSE_LEN - strlen(tag) - 1);
440 }
441 
442 /**
443  * Frees any resources allocated during current fastboot command.
444  */
445 static void cleanup_command_data(void)
446 {
447 	/* cmd_parameter and cmd_string potentially point to memory allocated by
448 	 * strdup
449 	 */
450 	if (cmd_parameter) {
451 		free(cmd_parameter);
452 	}
453 	if (cmd_string) {
454 		free(cmd_string);
455 	}
456 	cmd_parameter = cmd_string = NULL;
457 }
458 
459 /**
460  * Incoming UDP packet handler.
461  *
462  * @param packet  Pointer to incoming UDP packet
463  * @param dport   Destination UDP port
464  * @param sip     Source IP address
465  * @param sport   Source UDP port
466  * @param len     Packet length
467  */
468 static void fastboot_handler(uchar *packet, unsigned dport, struct in_addr sip,
469 		unsigned sport, unsigned len)
470 {
471 	struct fastboot_header fb_header;
472 	char fastboot_data[DATA_SIZE] = {0};
473 	unsigned int fastboot_data_len = 0;
474 
475 	if (dport != fastboot_our_port) {
476 		return;
477 	}
478 
479 	fastboot_remote_ip = sip;
480 	fastboot_remote_port = sport;
481 
482 	if (len < FASTBOOT_HEADER_SIZE || len > PACKET_SIZE) {
483 		return;
484 	}
485 	memcpy(&fb_header, packet, sizeof(fb_header));
486 	fb_header.flags = 0;
487 	fb_header.seq = ntohs(fb_header.seq);
488 	packet += sizeof(fb_header);
489 	len -= sizeof(fb_header);
490 
491 	switch (fb_header.id) {
492 	case FASTBOOT_QUERY:
493 		fastboot_send(fb_header, fastboot_data, 0, 0);
494 		break;
495 	case FASTBOOT_INIT:
496 	case FASTBOOT_FASTBOOT:
497 		fastboot_data_len = len;
498 		if (len > 0) {
499 			memcpy(fastboot_data, packet, len);
500 		}
501 		if (fb_header.seq == fb_sequence_number) {
502 			fastboot_send(fb_header, fastboot_data, fastboot_data_len, 0);
503 			fb_sequence_number++;
504 		} else if (fb_header.seq == fb_sequence_number - 1) {
505 			/* Retransmit last sent packet */
506 			fastboot_send(fb_header, fastboot_data, fastboot_data_len, 1);
507 		}
508 		break;
509 	default:
510 		error("ID %d not implemented.\n", fb_header.id);
511 		fb_header.id = FASTBOOT_ERROR;
512 		fastboot_send(fb_header, fastboot_data, 0, 0);
513 		break;
514 	}
515 }
516 
517 void fastboot_start_server(void)
518 {
519 	printf("Using %s device\n", eth_get_name());
520 	printf("Listening for fastboot command on %pI4\n", &net_ip);
521 
522 	fastboot_our_port = WELL_KNOWN_PORT;
523 
524 	net_set_udp_handler(fastboot_handler);
525 
526 	/* zero out server ether in case the server ip has changed */
527 	memset(net_server_ethaddr, 0, 6);
528 }
529