xref: /openbmc/u-boot/net/fastboot.c (revision d4c7a9348f27c8e3fdb1b754d8f0d1fa27375d1c)
1f73a7df9SAlex Kiernan // SPDX-License-Identifier: BSD-2-Clause
2f73a7df9SAlex Kiernan /*
3f73a7df9SAlex Kiernan  * Copyright (C) 2016 The Android Open Source Project
4f73a7df9SAlex Kiernan  */
5f73a7df9SAlex Kiernan 
6f73a7df9SAlex Kiernan #include <common.h>
7f73a7df9SAlex Kiernan #include <fastboot.h>
8f73a7df9SAlex Kiernan #include <net.h>
9f73a7df9SAlex Kiernan #include <net/fastboot.h>
10f73a7df9SAlex Kiernan 
11f73a7df9SAlex Kiernan /* Fastboot port # defined in spec */
12f73a7df9SAlex Kiernan #define WELL_KNOWN_PORT 5554
13f73a7df9SAlex Kiernan 
14f73a7df9SAlex Kiernan enum {
15f73a7df9SAlex Kiernan 	FASTBOOT_ERROR = 0,
16f73a7df9SAlex Kiernan 	FASTBOOT_QUERY = 1,
17f73a7df9SAlex Kiernan 	FASTBOOT_INIT = 2,
18f73a7df9SAlex Kiernan 	FASTBOOT_FASTBOOT = 3,
19f73a7df9SAlex Kiernan };
20f73a7df9SAlex Kiernan 
21f73a7df9SAlex Kiernan struct __packed fastboot_header {
22f73a7df9SAlex Kiernan 	uchar id;
23f73a7df9SAlex Kiernan 	uchar flags;
24f73a7df9SAlex Kiernan 	unsigned short seq;
25f73a7df9SAlex Kiernan };
26f73a7df9SAlex Kiernan 
27f73a7df9SAlex Kiernan #define PACKET_SIZE 1024
28f73a7df9SAlex Kiernan #define DATA_SIZE (PACKET_SIZE - sizeof(struct fastboot_header))
29f73a7df9SAlex Kiernan 
30f73a7df9SAlex Kiernan /* Sequence number sent for every packet */
31f73a7df9SAlex Kiernan static unsigned short sequence_number = 1;
32f73a7df9SAlex Kiernan static const unsigned short packet_size = PACKET_SIZE;
33f73a7df9SAlex Kiernan static const unsigned short udp_version = 1;
34f73a7df9SAlex Kiernan 
35f73a7df9SAlex Kiernan /* Keep track of last packet for resubmission */
36f73a7df9SAlex Kiernan static uchar last_packet[PACKET_SIZE];
37f73a7df9SAlex Kiernan static unsigned int last_packet_len;
38f73a7df9SAlex Kiernan 
39f73a7df9SAlex Kiernan static struct in_addr fastboot_remote_ip;
40f73a7df9SAlex Kiernan /* The UDP port at their end */
41f73a7df9SAlex Kiernan static int fastboot_remote_port;
42f73a7df9SAlex Kiernan /* The UDP port at our end */
43f73a7df9SAlex Kiernan static int fastboot_our_port;
44f73a7df9SAlex Kiernan 
45f73a7df9SAlex Kiernan static void boot_downloaded_image(void);
46f73a7df9SAlex Kiernan 
47f73a7df9SAlex Kiernan #if CONFIG_IS_ENABLED(FASTBOOT_FLASH)
48f73a7df9SAlex Kiernan /**
49f73a7df9SAlex Kiernan  * fastboot_udp_send_info() - Send an INFO packet during long commands.
50f73a7df9SAlex Kiernan  *
51f73a7df9SAlex Kiernan  * @msg: String describing the reason for waiting
52f73a7df9SAlex Kiernan  */
fastboot_udp_send_info(const char * msg)53f73a7df9SAlex Kiernan static void fastboot_udp_send_info(const char *msg)
54f73a7df9SAlex Kiernan {
55f73a7df9SAlex Kiernan 	uchar *packet;
56f73a7df9SAlex Kiernan 	uchar *packet_base;
57f73a7df9SAlex Kiernan 	int len = 0;
58f73a7df9SAlex Kiernan 	char response[FASTBOOT_RESPONSE_LEN] = {0};
59f73a7df9SAlex Kiernan 
60f73a7df9SAlex Kiernan 	struct fastboot_header response_header = {
61f73a7df9SAlex Kiernan 		.id = FASTBOOT_FASTBOOT,
62f73a7df9SAlex Kiernan 		.flags = 0,
63f73a7df9SAlex Kiernan 		.seq = htons(sequence_number)
64f73a7df9SAlex Kiernan 	};
65f73a7df9SAlex Kiernan 	++sequence_number;
66f73a7df9SAlex Kiernan 	packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
67f73a7df9SAlex Kiernan 	packet_base = packet;
68f73a7df9SAlex Kiernan 
69f73a7df9SAlex Kiernan 	/* Write headers */
70f73a7df9SAlex Kiernan 	memcpy(packet, &response_header, sizeof(response_header));
71f73a7df9SAlex Kiernan 	packet += sizeof(response_header);
72f73a7df9SAlex Kiernan 	/* Write response */
73f73a7df9SAlex Kiernan 	fastboot_response("INFO", response, "%s", msg);
74f73a7df9SAlex Kiernan 	memcpy(packet, response, strlen(response));
75f73a7df9SAlex Kiernan 	packet += strlen(response);
76f73a7df9SAlex Kiernan 
77f73a7df9SAlex Kiernan 	len = packet - packet_base;
78f73a7df9SAlex Kiernan 
79f73a7df9SAlex Kiernan 	/* Save packet for retransmitting */
80f73a7df9SAlex Kiernan 	last_packet_len = len;
81f73a7df9SAlex Kiernan 	memcpy(last_packet, packet_base, last_packet_len);
82f73a7df9SAlex Kiernan 
83f73a7df9SAlex Kiernan 	net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
84f73a7df9SAlex Kiernan 			    fastboot_remote_port, fastboot_our_port, len);
85f73a7df9SAlex Kiernan }
86f73a7df9SAlex Kiernan 
87f73a7df9SAlex Kiernan /**
88f73a7df9SAlex Kiernan  * fastboot_timed_send_info() - Send INFO packet every 30 seconds
89f73a7df9SAlex Kiernan  *
90f73a7df9SAlex Kiernan  * @msg: String describing the reason for waiting
91f73a7df9SAlex Kiernan  *
92f73a7df9SAlex Kiernan  * Send an INFO packet during long commands based on timer. An INFO packet
93f73a7df9SAlex Kiernan  * is sent if the time is 30 seconds after start. Else, noop.
94f73a7df9SAlex Kiernan  */
fastboot_timed_send_info(const char * msg)95f73a7df9SAlex Kiernan static void fastboot_timed_send_info(const char *msg)
96f73a7df9SAlex Kiernan {
97f73a7df9SAlex Kiernan 	static ulong start;
98f73a7df9SAlex Kiernan 
99f73a7df9SAlex Kiernan 	/* Initialize timer */
100f73a7df9SAlex Kiernan 	if (start == 0)
101f73a7df9SAlex Kiernan 		start = get_timer(0);
102f73a7df9SAlex Kiernan 	ulong time = get_timer(start);
103f73a7df9SAlex Kiernan 	/* Send INFO packet to host every 30 seconds */
104f73a7df9SAlex Kiernan 	if (time >= 30000) {
105f73a7df9SAlex Kiernan 		start = get_timer(0);
106f73a7df9SAlex Kiernan 		fastboot_udp_send_info(msg);
107f73a7df9SAlex Kiernan 	}
108f73a7df9SAlex Kiernan }
109f73a7df9SAlex Kiernan #endif
110f73a7df9SAlex Kiernan 
111f73a7df9SAlex Kiernan /**
112f73a7df9SAlex Kiernan  * fastboot_send() - Sends a packet in response to received fastboot packet
113f73a7df9SAlex Kiernan  *
114f73a7df9SAlex Kiernan  * @header: Header for response packet
115f73a7df9SAlex Kiernan  * @fastboot_data: Pointer to received fastboot data
116f73a7df9SAlex Kiernan  * @fastboot_data_len: Length of received fastboot data
117f73a7df9SAlex Kiernan  * @retransmit: Nonzero if sending last sent packet
118f73a7df9SAlex Kiernan  */
fastboot_send(struct fastboot_header header,char * fastboot_data,unsigned int fastboot_data_len,uchar retransmit)119f73a7df9SAlex Kiernan static void fastboot_send(struct fastboot_header header, char *fastboot_data,
120f73a7df9SAlex Kiernan 			  unsigned int fastboot_data_len, uchar retransmit)
121f73a7df9SAlex Kiernan {
122f73a7df9SAlex Kiernan 	uchar *packet;
123f73a7df9SAlex Kiernan 	uchar *packet_base;
124f73a7df9SAlex Kiernan 	int len = 0;
125f73a7df9SAlex Kiernan 	const char *error_msg = "An error occurred.";
126f73a7df9SAlex Kiernan 	short tmp;
127f73a7df9SAlex Kiernan 	struct fastboot_header response_header = header;
128f73a7df9SAlex Kiernan 	static char command[FASTBOOT_COMMAND_LEN];
129f73a7df9SAlex Kiernan 	static int cmd = -1;
130f73a7df9SAlex Kiernan 	static bool pending_command;
131f73a7df9SAlex Kiernan 	char response[FASTBOOT_RESPONSE_LEN] = {0};
132f73a7df9SAlex Kiernan 
133f73a7df9SAlex Kiernan 	/*
134f73a7df9SAlex Kiernan 	 * We will always be sending some sort of packet, so
135f73a7df9SAlex Kiernan 	 * cobble together the packet headers now.
136f73a7df9SAlex Kiernan 	 */
137f73a7df9SAlex Kiernan 	packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE;
138f73a7df9SAlex Kiernan 	packet_base = packet;
139f73a7df9SAlex Kiernan 
140f73a7df9SAlex Kiernan 	/* Resend last packet */
141f73a7df9SAlex Kiernan 	if (retransmit) {
142f73a7df9SAlex Kiernan 		memcpy(packet, last_packet, last_packet_len);
143f73a7df9SAlex Kiernan 		net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
144f73a7df9SAlex Kiernan 				    fastboot_remote_port, fastboot_our_port,
145f73a7df9SAlex Kiernan 				    last_packet_len);
146f73a7df9SAlex Kiernan 		return;
147f73a7df9SAlex Kiernan 	}
148f73a7df9SAlex Kiernan 
149f73a7df9SAlex Kiernan 	response_header.seq = htons(response_header.seq);
150f73a7df9SAlex Kiernan 	memcpy(packet, &response_header, sizeof(response_header));
151f73a7df9SAlex Kiernan 	packet += sizeof(response_header);
152f73a7df9SAlex Kiernan 
153f73a7df9SAlex Kiernan 	switch (header.id) {
154f73a7df9SAlex Kiernan 	case FASTBOOT_QUERY:
155f73a7df9SAlex Kiernan 		tmp = htons(sequence_number);
156f73a7df9SAlex Kiernan 		memcpy(packet, &tmp, sizeof(tmp));
157f73a7df9SAlex Kiernan 		packet += sizeof(tmp);
158f73a7df9SAlex Kiernan 		break;
159f73a7df9SAlex Kiernan 	case FASTBOOT_INIT:
160f73a7df9SAlex Kiernan 		tmp = htons(udp_version);
161f73a7df9SAlex Kiernan 		memcpy(packet, &tmp, sizeof(tmp));
162f73a7df9SAlex Kiernan 		packet += sizeof(tmp);
163f73a7df9SAlex Kiernan 		tmp = htons(packet_size);
164f73a7df9SAlex Kiernan 		memcpy(packet, &tmp, sizeof(tmp));
165f73a7df9SAlex Kiernan 		packet += sizeof(tmp);
166f73a7df9SAlex Kiernan 		break;
167f73a7df9SAlex Kiernan 	case FASTBOOT_ERROR:
168f73a7df9SAlex Kiernan 		memcpy(packet, error_msg, strlen(error_msg));
169f73a7df9SAlex Kiernan 		packet += strlen(error_msg);
170f73a7df9SAlex Kiernan 		break;
171f73a7df9SAlex Kiernan 	case FASTBOOT_FASTBOOT:
172f73a7df9SAlex Kiernan 		if (cmd == FASTBOOT_COMMAND_DOWNLOAD) {
173f73a7df9SAlex Kiernan 			if (!fastboot_data_len && !fastboot_data_remaining()) {
174f73a7df9SAlex Kiernan 				fastboot_data_complete(response);
175f73a7df9SAlex Kiernan 			} else {
176f73a7df9SAlex Kiernan 				fastboot_data_download(fastboot_data,
177f73a7df9SAlex Kiernan 						       fastboot_data_len,
178f73a7df9SAlex Kiernan 						       response);
179f73a7df9SAlex Kiernan 			}
180f73a7df9SAlex Kiernan 		} else if (!pending_command) {
181f73a7df9SAlex Kiernan 			strlcpy(command, fastboot_data,
182f73a7df9SAlex Kiernan 				min((size_t)fastboot_data_len + 1,
183f73a7df9SAlex Kiernan 				    sizeof(command)));
184f73a7df9SAlex Kiernan 			pending_command = true;
185f73a7df9SAlex Kiernan 		} else {
186f73a7df9SAlex Kiernan 			cmd = fastboot_handle_command(command, response);
187f73a7df9SAlex Kiernan 			pending_command = false;
188f73a7df9SAlex Kiernan 		}
189f73a7df9SAlex Kiernan 		/*
190f73a7df9SAlex Kiernan 		 * Sent some INFO packets, need to update sequence number in
191f73a7df9SAlex Kiernan 		 * header
192f73a7df9SAlex Kiernan 		 */
193f73a7df9SAlex Kiernan 		if (header.seq != sequence_number) {
194f73a7df9SAlex Kiernan 			response_header.seq = htons(sequence_number);
195f73a7df9SAlex Kiernan 			memcpy(packet_base, &response_header,
196f73a7df9SAlex Kiernan 			       sizeof(response_header));
197f73a7df9SAlex Kiernan 		}
198f73a7df9SAlex Kiernan 		/* Write response to packet */
199f73a7df9SAlex Kiernan 		memcpy(packet, response, strlen(response));
200f73a7df9SAlex Kiernan 		packet += strlen(response);
201f73a7df9SAlex Kiernan 		break;
202f73a7df9SAlex Kiernan 	default:
203f73a7df9SAlex Kiernan 		pr_err("ID %d not implemented.\n", header.id);
204f73a7df9SAlex Kiernan 		return;
205f73a7df9SAlex Kiernan 	}
206f73a7df9SAlex Kiernan 
207f73a7df9SAlex Kiernan 	len = packet - packet_base;
208f73a7df9SAlex Kiernan 
209f73a7df9SAlex Kiernan 	/* Save packet for retransmitting */
210f73a7df9SAlex Kiernan 	last_packet_len = len;
211f73a7df9SAlex Kiernan 	memcpy(last_packet, packet_base, last_packet_len);
212f73a7df9SAlex Kiernan 
213f73a7df9SAlex Kiernan 	net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip,
214f73a7df9SAlex Kiernan 			    fastboot_remote_port, fastboot_our_port, len);
215f73a7df9SAlex Kiernan 
216f73a7df9SAlex Kiernan 	/* Continue boot process after sending response */
217f73a7df9SAlex Kiernan 	if (!strncmp("OKAY", response, 4)) {
218f73a7df9SAlex Kiernan 		switch (cmd) {
219f73a7df9SAlex Kiernan 		case FASTBOOT_COMMAND_BOOT:
220f73a7df9SAlex Kiernan 			boot_downloaded_image();
221f73a7df9SAlex Kiernan 			break;
222f73a7df9SAlex Kiernan 
223f73a7df9SAlex Kiernan 		case FASTBOOT_COMMAND_CONTINUE:
224f73a7df9SAlex Kiernan 			net_set_state(NETLOOP_SUCCESS);
225f73a7df9SAlex Kiernan 			break;
226f73a7df9SAlex Kiernan 
227f73a7df9SAlex Kiernan 		case FASTBOOT_COMMAND_REBOOT:
228f73a7df9SAlex Kiernan 		case FASTBOOT_COMMAND_REBOOT_BOOTLOADER:
229f73a7df9SAlex Kiernan 			do_reset(NULL, 0, 0, NULL);
230f73a7df9SAlex Kiernan 			break;
231f73a7df9SAlex Kiernan 		}
232f73a7df9SAlex Kiernan 	}
233f73a7df9SAlex Kiernan 
234f73a7df9SAlex Kiernan 	if (!strncmp("OKAY", response, 4) || !strncmp("FAIL", response, 4))
235f73a7df9SAlex Kiernan 		cmd = -1;
236f73a7df9SAlex Kiernan }
237f73a7df9SAlex Kiernan 
238f73a7df9SAlex Kiernan /**
239f73a7df9SAlex Kiernan  * boot_downloaded_image() - Boots into downloaded image.
240f73a7df9SAlex Kiernan  */
boot_downloaded_image(void)241f73a7df9SAlex Kiernan static void boot_downloaded_image(void)
242f73a7df9SAlex Kiernan {
243f73a7df9SAlex Kiernan 	fastboot_boot();
244f73a7df9SAlex Kiernan 	net_set_state(NETLOOP_SUCCESS);
245f73a7df9SAlex Kiernan }
246f73a7df9SAlex Kiernan 
247f73a7df9SAlex Kiernan /**
248f73a7df9SAlex Kiernan  * fastboot_handler() - Incoming UDP packet handler.
249f73a7df9SAlex Kiernan  *
250f73a7df9SAlex Kiernan  * @packet: Pointer to incoming UDP packet
251f73a7df9SAlex Kiernan  * @dport: Destination UDP port
252f73a7df9SAlex Kiernan  * @sip: Source IP address
253f73a7df9SAlex Kiernan  * @sport: Source UDP port
254f73a7df9SAlex Kiernan  * @len: Packet length
255f73a7df9SAlex Kiernan  */
fastboot_handler(uchar * packet,unsigned int dport,struct in_addr sip,unsigned int sport,unsigned int len)256f73a7df9SAlex Kiernan static void fastboot_handler(uchar *packet, unsigned int dport,
257f73a7df9SAlex Kiernan 			     struct in_addr sip, unsigned int sport,
258f73a7df9SAlex Kiernan 			     unsigned int len)
259f73a7df9SAlex Kiernan {
260f73a7df9SAlex Kiernan 	struct fastboot_header header;
261f73a7df9SAlex Kiernan 	char fastboot_data[DATA_SIZE] = {0};
262f73a7df9SAlex Kiernan 	unsigned int fastboot_data_len = 0;
263f73a7df9SAlex Kiernan 
264f73a7df9SAlex Kiernan 	if (dport != fastboot_our_port)
265f73a7df9SAlex Kiernan 		return;
266f73a7df9SAlex Kiernan 
267f73a7df9SAlex Kiernan 	fastboot_remote_ip = sip;
268f73a7df9SAlex Kiernan 	fastboot_remote_port = sport;
269f73a7df9SAlex Kiernan 
270f73a7df9SAlex Kiernan 	if (len < sizeof(struct fastboot_header) || len > PACKET_SIZE)
271f73a7df9SAlex Kiernan 		return;
272f73a7df9SAlex Kiernan 	memcpy(&header, packet, sizeof(header));
273f73a7df9SAlex Kiernan 	header.flags = 0;
274f73a7df9SAlex Kiernan 	header.seq = ntohs(header.seq);
275f73a7df9SAlex Kiernan 	packet += sizeof(header);
276f73a7df9SAlex Kiernan 	len -= sizeof(header);
277f73a7df9SAlex Kiernan 
278f73a7df9SAlex Kiernan 	switch (header.id) {
279f73a7df9SAlex Kiernan 	case FASTBOOT_QUERY:
280f73a7df9SAlex Kiernan 		fastboot_send(header, fastboot_data, 0, 0);
281f73a7df9SAlex Kiernan 		break;
282f73a7df9SAlex Kiernan 	case FASTBOOT_INIT:
283f73a7df9SAlex Kiernan 	case FASTBOOT_FASTBOOT:
284f73a7df9SAlex Kiernan 		fastboot_data_len = len;
285f73a7df9SAlex Kiernan 		if (len > 0)
286f73a7df9SAlex Kiernan 			memcpy(fastboot_data, packet, len);
287f73a7df9SAlex Kiernan 		if (header.seq == sequence_number) {
288f73a7df9SAlex Kiernan 			fastboot_send(header, fastboot_data,
289f73a7df9SAlex Kiernan 				      fastboot_data_len, 0);
290f73a7df9SAlex Kiernan 			sequence_number++;
291f73a7df9SAlex Kiernan 		} else if (header.seq == sequence_number - 1) {
292f73a7df9SAlex Kiernan 			/* Retransmit last sent packet */
293f73a7df9SAlex Kiernan 			fastboot_send(header, fastboot_data,
294f73a7df9SAlex Kiernan 				      fastboot_data_len, 1);
295f73a7df9SAlex Kiernan 		}
296f73a7df9SAlex Kiernan 		break;
297f73a7df9SAlex Kiernan 	default:
298f73a7df9SAlex Kiernan 		pr_err("ID %d not implemented.\n", header.id);
299f73a7df9SAlex Kiernan 		header.id = FASTBOOT_ERROR;
300f73a7df9SAlex Kiernan 		fastboot_send(header, fastboot_data, 0, 0);
301f73a7df9SAlex Kiernan 		break;
302f73a7df9SAlex Kiernan 	}
303f73a7df9SAlex Kiernan }
304f73a7df9SAlex Kiernan 
fastboot_start_server(void)305f73a7df9SAlex Kiernan void fastboot_start_server(void)
306f73a7df9SAlex Kiernan {
307f73a7df9SAlex Kiernan 	printf("Using %s device\n", eth_get_name());
308f73a7df9SAlex Kiernan 	printf("Listening for fastboot command on %pI4\n", &net_ip);
309f73a7df9SAlex Kiernan 
310f73a7df9SAlex Kiernan 	fastboot_our_port = WELL_KNOWN_PORT;
311f73a7df9SAlex Kiernan 
312*6dc73df7SAlex Kiernan #if CONFIG_IS_ENABLED(FASTBOOT_FLASH)
313f73a7df9SAlex Kiernan 	fastboot_set_progress_callback(fastboot_timed_send_info);
314*6dc73df7SAlex Kiernan #endif
315f73a7df9SAlex Kiernan 	net_set_udp_handler(fastboot_handler);
316f73a7df9SAlex Kiernan 
317f73a7df9SAlex Kiernan 	/* zero out server ether in case the server ip has changed */
318f73a7df9SAlex Kiernan 	memset(net_server_ethaddr, 0, 6);
319f73a7df9SAlex Kiernan }
320