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