xref: /rk3399_rockchip-uboot/net/fastboot.c (revision 8b464fa913ab295ac832e50d3964e04b34c2c337)
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 cleanup_command_data(void);
65 static void write_fb_response(const char*, const char*, char*);
66 
67 /**
68  * Constructs and sends a packet in response to received fastboot packet
69  *
70  * @param fb_header            Header for response packet
71  * @param fastboot_data        Pointer to received fastboot data
72  * @param fastboot_data_len    Length of received fastboot data
73  * @param retransmit           Nonzero if sending last sent packet
74  */
75 static void fastboot_send(struct fastboot_header fb_header, char *fastboot_data,
76 		unsigned int fastboot_data_len, uchar retransmit)
77 {
78 	uchar *packet;
79 	uchar *packet_base;
80 	int len = 0;
81 	const char *error_msg = "An error occurred.";
82 	short tmp;
83 	struct fastboot_header fb_response_header = fb_header;
84 	char response[FASTBOOT_RESPONSE_LEN] = {0};
85 	/*
86 	 *	We will always be sending some sort of packet, so
87 	 *	cobble together the packet headers now.
88 	 */
89 	packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
90 	packet_base = packet;
91 
92 	/* Resend last packet */
93 	if (retransmit) {
94 		memcpy(packet, last_packet, last_packet_len);
95 		net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
96 				    fastboot_remote_port, fastboot_our_port, last_packet_len);
97 		return;
98 	}
99 
100 	fb_response_header.seq = htons(fb_response_header.seq);
101 	memcpy(packet, &fb_response_header, sizeof(fb_response_header));
102 	packet += sizeof(fb_response_header);
103 
104 	switch (fb_header.id) {
105 	case FASTBOOT_QUERY:
106 		tmp = htons(fb_sequence_number);
107 		memcpy(packet, &tmp, sizeof(tmp));
108 		packet += sizeof(tmp);
109 		break;
110 	case FASTBOOT_INIT:
111 		tmp = htons(fb_udp_version);
112 		memcpy(packet, &tmp, sizeof(tmp));
113 		packet += sizeof(tmp);
114 		tmp = htons(fb_packet_size);
115 		memcpy(packet, &tmp, sizeof(tmp));
116 		packet += sizeof(tmp);
117 		break;
118 	case FASTBOOT_ERROR:
119 		memcpy(packet, error_msg, strlen(error_msg));
120 		packet += strlen(error_msg);
121 		break;
122 	case FASTBOOT_FASTBOOT:
123 		if (cmd_string == NULL) {
124 			/* Parse command and send ack */
125 			cmd_parameter = fastboot_data;
126 			cmd_string = strsep(&cmd_parameter, ":");
127 			cmd_string = strdup(cmd_string);
128 			if (cmd_parameter) {
129 				cmd_parameter = strdup(cmd_parameter);
130 			}
131 		} else if (!strcmp("getvar", cmd_string)) {
132 			fb_getvar(response);
133 		} else if (!strcmp("download", cmd_string)) {
134 			fb_download(fastboot_data, fastboot_data_len, response);
135 		} else if (!strcmp("flash", cmd_string)) {
136 			fb_flash(response);
137 		} else if (!strcmp("erase", cmd_string)) {
138 			fb_erase(response);
139 		} else {
140 			error("command %s not implemented.\n", cmd_string);
141 			write_fb_response("FAIL", "unrecognized command", response);
142 		}
143 		/* Write response to packet */
144 		memcpy(packet, response, strlen(response));
145 		packet += strlen(response);
146 		break;
147 	default:
148 		error("ID %d not implemented.\n", fb_header.id);
149 		return;
150 	}
151 
152 	len = packet-packet_base;
153 
154 	/* Save packet for retransmitting */
155 	last_packet_len = len;
156 	memcpy(last_packet, packet_base, last_packet_len);
157 
158 	net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
159 			    fastboot_remote_port, fastboot_our_port, len);
160 
161 	/* OKAY and FAIL indicate command is complete */
162 	if (!strncmp("OKAY", response, 4) ||
163 			!strncmp("FAIL", response, 4)) {
164 		cleanup_command_data();
165 	}
166 }
167 
168 /**
169  * Writes ascii string specified by cmd_parameter to response.
170  *
171  * @param repsonse    Pointer to fastboot response buffer
172  */
173 static void fb_getvar(char *response)
174 {
175 
176 	if (cmd_parameter == NULL) {
177 		write_fb_response("FAIL", "missing var", response);
178 	} else if (!strcmp("version", cmd_parameter)) {
179 		write_fb_response("OKAY", FASTBOOT_VERSION, response);
180 	} else if (!strcmp("bootloader-version", cmd_parameter)) {
181 		write_fb_response("OKAY", U_BOOT_VERSION, response);
182 	} else if (!strcmp("downloadsize", cmd_parameter) ||
183 		!strcmp("max-download-size", cmd_parameter)) {
184 		char buf_size_str[12];
185 		sprintf(buf_size_str, "0x%08x", CONFIG_FASTBOOT_BUF_SIZE);
186 		write_fb_response("OKAY", buf_size_str, response);
187 	} else if (!strcmp("serialno", cmd_parameter)) {
188 		const char *tmp = getenv("serial#");
189 		if (tmp) {
190 			write_fb_response("OKAY", tmp, response);
191 		} else {
192 			write_fb_response("FAIL", "Value not set", response);
193 		}
194 	} else {
195 		printf("WARNING: unknown variable: %s\n", cmd_parameter);
196 		write_fb_response("FAIL", "Variable not implemented", response);
197 	}
198 }
199 
200 /**
201  * Copies image data from fastboot_data to CONFIG_FASTBOOT_BUF_ADDR.
202  * Writes to response.
203  *
204  * @param fastboot_data        Pointer to received fastboot data
205  * @param fastboot_data_len    Length of received fastboot data
206  * @param repsonse             Pointer to fastboot response buffer
207  */
208 static void fb_download(char *fastboot_data, unsigned int fastboot_data_len,
209 		char *response)
210 {
211 	char *tmp;
212 
213 	if (bytes_expected == 0) {
214 		if (cmd_parameter == NULL) {
215 			write_fb_response("FAIL", "Expected command parameter", response);
216 			return;
217 		}
218 		bytes_expected = simple_strtoul(cmd_parameter, &tmp, 16);
219 		if (bytes_expected == 0) {
220 			write_fb_response("FAIL", "Expected nonzero image size", response);
221 			return;
222 		}
223 	}
224 	if (fastboot_data_len == 0 && bytes_received == 0) {
225 		/* Nothing to download yet. Response is of the form:
226 		 * [DATA|FAIL]$cmd_parameter
227 		 *
228 		 * where cmd_parameter is an 8 digit hexadecimal number
229 		 */
230 		if (bytes_expected > CONFIG_FASTBOOT_BUF_SIZE) {
231 			write_fb_response("FAIL", cmd_parameter, response);
232 		} else {
233 			write_fb_response("DATA", cmd_parameter, response);
234 		}
235 	} else if (fastboot_data_len == 0 && (bytes_received >= bytes_expected)) {
236 		/* Download complete. Respond with "OKAY" */
237 		write_fb_response("OKAY", "", response);
238 		image_size = bytes_received;
239 		bytes_expected = bytes_received = 0;
240 	} else {
241 		if (fastboot_data_len == 0 ||
242 				(bytes_received + fastboot_data_len) > bytes_expected) {
243 			write_fb_response("FAIL", "Received invalid data length", response);
244 			return;
245 		}
246 		/* Download data to CONFIG_FASTBOOT_BUF_ADDR */
247 		memcpy((void*)CONFIG_FASTBOOT_BUF_ADDR + bytes_received, fastboot_data,
248 				fastboot_data_len);
249 		bytes_received += fastboot_data_len;
250 	}
251 }
252 
253 /**
254  * Writes the previously downloaded image to the partition indicated by
255  * cmd_parameter. Writes to response.
256  *
257  * @param repsonse    Pointer to fastboot response buffer
258  */
259 static void fb_flash(char *response)
260 {
261 	fb_mmc_flash_write(cmd_parameter, (void*)CONFIG_FASTBOOT_BUF_ADDR,
262 			image_size, response);
263 }
264 
265 /**
266  * Erases the partition indicated by cmd_parameter (clear to 0x00s). Writes
267  * to response.
268  *
269  * @param repsonse    Pointer to fastboot response buffer
270  */
271 static void fb_erase(char *response)
272 {
273 	fb_mmc_erase(cmd_parameter, response);
274 }
275 
276 /**
277  * Writes a response to response buffer of the form "$tag$reason".
278  *
279  * @param tag         The first part of the response
280  * @param reason      The second part of the response
281  * @param repsonse    Pointer to fastboot response buffer
282  */
283 static void write_fb_response(const char* tag, const char *reason,
284 		char *response)
285 {
286 	strncpy(response, tag, strlen(tag));
287 	strncat(response, reason, FASTBOOT_RESPONSE_LEN - strlen(tag) - 1);
288 }
289 
290 /**
291  * Frees any resources allocated during current fastboot command.
292  */
293 static void cleanup_command_data(void)
294 {
295 	/* cmd_parameter and cmd_string potentially point to memory allocated by
296 	 * strdup
297 	 */
298 	if (cmd_parameter) {
299 		free(cmd_parameter);
300 	}
301 	if (cmd_string) {
302 		free(cmd_string);
303 	}
304 	cmd_parameter = cmd_string = NULL;
305 }
306 
307 /**
308  * Incoming UDP packet handler.
309  *
310  * @param packet  Pointer to incoming UDP packet
311  * @param dport   Destination UDP port
312  * @param sip     Source IP address
313  * @param sport   Source UDP port
314  * @param len     Packet length
315  */
316 static void fastboot_handler(uchar *packet, unsigned dport, struct in_addr sip,
317 		unsigned sport, unsigned len)
318 {
319 	struct fastboot_header fb_header;
320 	char fastboot_data[DATA_SIZE] = {0};
321 	unsigned int fastboot_data_len = 0;
322 
323 	if (dport != fastboot_our_port) {
324 		return;
325 	}
326 
327 	fastboot_remote_ip = sip;
328 	fastboot_remote_port = sport;
329 
330 	if (len < FASTBOOT_HEADER_SIZE || len > PACKET_SIZE) {
331 		return;
332 	}
333 	memcpy(&fb_header, packet, sizeof(fb_header));
334 	fb_header.flags = 0;
335 	fb_header.seq = ntohs(fb_header.seq);
336 	packet += sizeof(fb_header);
337 	len -= sizeof(fb_header);
338 
339 	switch (fb_header.id) {
340 	case FASTBOOT_QUERY:
341 		fastboot_send(fb_header, fastboot_data, 0, 0);
342 		break;
343 	case FASTBOOT_INIT:
344 	case FASTBOOT_FASTBOOT:
345 		fastboot_data_len = len;
346 		if (len > 0) {
347 			memcpy(fastboot_data, packet, len);
348 		}
349 		if (fb_header.seq == fb_sequence_number) {
350 			fastboot_send(fb_header, fastboot_data, fastboot_data_len, 0);
351 			fb_sequence_number++;
352 		} else if (fb_header.seq == fb_sequence_number - 1) {
353 			/* Retransmit last sent packet */
354 			fastboot_send(fb_header, fastboot_data, fastboot_data_len, 1);
355 		}
356 		break;
357 	default:
358 		error("ID %d not implemented.\n", fb_header.id);
359 		fb_header.id = FASTBOOT_ERROR;
360 		fastboot_send(fb_header, fastboot_data, 0, 0);
361 		break;
362 	}
363 }
364 
365 void fastboot_start_server(void)
366 {
367 	printf("Using %s device\n", eth_get_name());
368 	printf("Listening for fastboot command on %pI4\n", &net_ip);
369 
370 	fastboot_our_port = WELL_KNOWN_PORT;
371 
372 	net_set_udp_handler(fastboot_handler);
373 
374 	/* zero out server ether in case the server ip has changed */
375 	memset(net_server_ethaddr, 0, 6);
376 }
377