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