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