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