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