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