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 | |
Simon Glass | ed38aef | 2020-05-10 11:40:03 -0600 | [diff] [blame] | 6 | #include <command.h> |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 7 | #include <fastboot.h> |
| 8 | #include <net.h> |
Dmitrii Merkurev | 308252d | 2023-04-12 19:49:30 +0100 | [diff] [blame] | 9 | #include <net/fastboot_udp.h> |
Simon Glass | bdd5f81 | 2023-09-14 18:21:46 -0600 | [diff] [blame] | 10 | #include <linux/printk.h> |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 11 | |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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 | |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 43 | /** |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 44 | * fastboot_udp_send_response() - Send an response into UDP |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 45 | * |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 46 | * @response: Response to send |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 47 | */ |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 48 | static void fastboot_udp_send_response(const char *response) |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 49 | { |
| 50 | uchar *packet; |
| 51 | uchar *packet_base; |
| 52 | int len = 0; |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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 */ |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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; |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 91 | char response[FASTBOOT_RESPONSE_LEN] = {0}; |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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); |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 100 | fastboot_response("INFO", response, "%s", msg); |
| 101 | fastboot_udp_send_response(response); |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 102 | } |
| 103 | } |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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; |
Ion Agorria | 1c38703 | 2024-01-05 09:22:06 +0200 | [diff] [blame] | 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 | } |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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 | |
Dmitrii Merkurev | 0788290 | 2023-04-12 19:49:31 +0100 | [diff] [blame] | 227 | fastboot_handle_boot(cmd, strncmp("OKAY", response, 4) == 0); |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 228 | |
| 229 | if (!strncmp("OKAY", response, 4) || !strncmp("FAIL", response, 4)) |
| 230 | cmd = -1; |
| 231 | } |
| 232 | |
| 233 | /** |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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 | |
Dmitrii Merkurev | 308252d | 2023-04-12 19:49:30 +0100 | [diff] [blame] | 291 | void fastboot_udp_start_server(void) |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 292 | { |
| 293 | printf("Using %s device\n", eth_get_name()); |
| 294 | printf("Listening for fastboot command on %pI4\n", &net_ip); |
| 295 | |
Christian Gmeiner | 7fd97aa | 2022-01-13 08:40:06 +0100 | [diff] [blame] | 296 | fastboot_our_port = CONFIG_UDP_FUNCTION_FASTBOOT_PORT; |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 297 | |
Simon Glass | 3eaba079 | 2023-02-05 15:39:53 -0700 | [diff] [blame] | 298 | if (IS_ENABLED(CONFIG_FASTBOOT_FLASH)) |
Patrick Delaunay | f82f9e4 | 2022-12-15 10:15:50 +0100 | [diff] [blame] | 299 | fastboot_set_progress_callback(fastboot_timed_send_info); |
| 300 | |
Alex Kiernan | d5aa57c | 2018-05-29 15:30:53 +0000 | [diff] [blame] | 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 | } |