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