| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #include <common.h> |
| #include <fastboot.h> |
| #include <fb_mmc.h> |
| #include <net.h> |
| #include <net/fastboot.h> |
| #include <part.h> |
| #include <stdlib.h> |
| #include <version.h> |
| |
| /* Fastboot port # defined in spec */ |
| #define WELL_KNOWN_PORT 5554 |
| |
| enum { |
| FASTBOOT_ERROR = 0, |
| FASTBOOT_QUERY = 1, |
| FASTBOOT_INIT = 2, |
| FASTBOOT_FASTBOOT = 3, |
| }; |
| |
| struct __attribute__((packed)) fastboot_header { |
| uchar id; |
| uchar flags; |
| unsigned short seq; |
| }; |
| |
| #define PACKET_SIZE 1024 |
| #define FASTBOOT_HEADER_SIZE sizeof(struct fastboot_header) |
| #define DATA_SIZE (PACKET_SIZE - FASTBOOT_HEADER_SIZE) |
| #define FASTBOOT_VERSION "0.4" |
| |
| /* Sequence number sent for every packet */ |
| static unsigned short fb_sequence_number = 1; |
| static const unsigned short fb_packet_size = PACKET_SIZE; |
| static const unsigned short fb_udp_version = 1; |
| |
| /* Keep track of last packet for resubmission */ |
| static uchar last_packet[PACKET_SIZE]; |
| static unsigned int last_packet_len = 0; |
| |
| /* Parsed from first fastboot command packet */ |
| static char *cmd_string = NULL; |
| static char *cmd_parameter = NULL; |
| |
| /* Fastboot download parameters */ |
| static unsigned int bytes_received = 0; |
| static unsigned int bytes_expected = 0; |
| static unsigned int image_size = 0; |
| |
| static struct in_addr fastboot_remote_ip; |
| /* The UDP port at their end */ |
| static int fastboot_remote_port; |
| /* The UDP port at our end */ |
| static int fastboot_our_port; |
| |
| static void fb_getvar(char*); |
| static void fb_download(char*, unsigned int, char*); |
| static void fb_flash(char*); |
| static void fb_erase(char*); |
| static void fb_continue(char*); |
| static void fb_reboot(char*); |
| static void boot_downloaded_image(void); |
| static void cleanup_command_data(void); |
| static void write_fb_response(const char*, const char*, char*); |
| |
| void fastboot_send_info(const char *msg) |
| { |
| uchar *packet; |
| uchar *packet_base; |
| int len = 0; |
| char response[FASTBOOT_RESPONSE_LEN] = {0}; |
| |
| struct fastboot_header fb_response_header = |
| { |
| .id = FASTBOOT_FASTBOOT, |
| .flags = 0, |
| .seq = htons(fb_sequence_number) |
| }; |
| ++fb_sequence_number; |
| packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE; |
| packet_base = packet; |
| |
| /* Write headers */ |
| memcpy(packet, &fb_response_header, sizeof(fb_response_header)); |
| packet += sizeof(fb_response_header); |
| /* Write response */ |
| write_fb_response("INFO", msg, response); |
| memcpy(packet, response, strlen(response)); |
| packet += strlen(response); |
| |
| len = packet-packet_base; |
| |
| /* Save packet for retransmitting */ |
| last_packet_len = len; |
| memcpy(last_packet, packet_base, last_packet_len); |
| |
| net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, |
| fastboot_remote_port, fastboot_our_port, len); |
| } |
| |
| /** |
| * Constructs and sends a packet in response to received fastboot packet |
| * |
| * @param fb_header Header for response packet |
| * @param fastboot_data Pointer to received fastboot data |
| * @param fastboot_data_len Length of received fastboot data |
| * @param retransmit Nonzero if sending last sent packet |
| */ |
| static void fastboot_send(struct fastboot_header fb_header, char *fastboot_data, |
| unsigned int fastboot_data_len, uchar retransmit) |
| { |
| uchar *packet; |
| uchar *packet_base; |
| int len = 0; |
| const char *error_msg = "An error occurred."; |
| short tmp; |
| struct fastboot_header fb_response_header = fb_header; |
| char response[FASTBOOT_RESPONSE_LEN] = {0}; |
| /* |
| * We will always be sending some sort of packet, so |
| * cobble together the packet headers now. |
| */ |
| packet = net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE; |
| packet_base = packet; |
| |
| /* Resend last packet */ |
| if (retransmit) { |
| memcpy(packet, last_packet, last_packet_len); |
| net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, |
| fastboot_remote_port, fastboot_our_port, last_packet_len); |
| return; |
| } |
| |
| fb_response_header.seq = htons(fb_response_header.seq); |
| memcpy(packet, &fb_response_header, sizeof(fb_response_header)); |
| packet += sizeof(fb_response_header); |
| |
| switch (fb_header.id) { |
| case FASTBOOT_QUERY: |
| tmp = htons(fb_sequence_number); |
| memcpy(packet, &tmp, sizeof(tmp)); |
| packet += sizeof(tmp); |
| break; |
| case FASTBOOT_INIT: |
| tmp = htons(fb_udp_version); |
| memcpy(packet, &tmp, sizeof(tmp)); |
| packet += sizeof(tmp); |
| tmp = htons(fb_packet_size); |
| memcpy(packet, &tmp, sizeof(tmp)); |
| packet += sizeof(tmp); |
| break; |
| case FASTBOOT_ERROR: |
| memcpy(packet, error_msg, strlen(error_msg)); |
| packet += strlen(error_msg); |
| break; |
| case FASTBOOT_FASTBOOT: |
| if (cmd_string == NULL) { |
| /* Parse command and send ack */ |
| cmd_parameter = fastboot_data; |
| cmd_string = strsep(&cmd_parameter, ":"); |
| cmd_string = strdup(cmd_string); |
| if (cmd_parameter) { |
| cmd_parameter = strdup(cmd_parameter); |
| } |
| } else if (!strcmp("getvar", cmd_string)) { |
| fb_getvar(response); |
| } else if (!strcmp("download", cmd_string)) { |
| fb_download(fastboot_data, fastboot_data_len, response); |
| } else if (!strcmp("flash", cmd_string)) { |
| fb_flash(response); |
| } else if (!strcmp("erase", cmd_string)) { |
| fb_erase(response); |
| } else if (!strcmp("boot", cmd_string)) { |
| write_fb_response("OKAY", "", response); |
| } else if (!strcmp("continue", cmd_string)) { |
| fb_continue(response); |
| } else if (!strncmp("reboot", cmd_string, 6)) { |
| fb_reboot(response); |
| } else if (!strcmp("set_active", cmd_string)) { |
| /* A/B not implemented, for now do nothing */ |
| write_fb_response("OKAY", "", response); |
| } else { |
| pr_err("command %s not implemented.\n", cmd_string); |
| write_fb_response("FAIL", "unrecognized command", response); |
| } |
| /* Sent some INFO packets, need to update sequence number in header */ |
| if (fb_header.seq != fb_sequence_number) { |
| fb_response_header.seq = htons(fb_sequence_number); |
| memcpy(packet_base, &fb_response_header, sizeof(fb_response_header)); |
| } |
| /* Write response to packet */ |
| memcpy(packet, response, strlen(response)); |
| packet += strlen(response); |
| break; |
| default: |
| pr_err("ID %d not implemented.\n", fb_header.id); |
| return; |
| } |
| |
| len = packet-packet_base; |
| |
| /* Save packet for retransmitting */ |
| last_packet_len = len; |
| memcpy(last_packet, packet_base, last_packet_len); |
| |
| net_send_udp_packet(net_server_ethaddr, fastboot_remote_ip, |
| fastboot_remote_port, fastboot_our_port, len); |
| |
| /* Continue boot process after sending response */ |
| if (!strncmp("OKAY", response, 4)) { |
| if (!strcmp("boot", cmd_string)) { |
| boot_downloaded_image(); |
| } else if (!strcmp("continue", cmd_string)) { |
| run_command(env_get("bootcmd"), CMD_FLAG_ENV); |
| } else if (!strncmp("reboot", cmd_string, 6)) { |
| /* Matches reboot or reboot-bootloader */ |
| do_reset(NULL, 0, 0, NULL); |
| } |
| } |
| |
| /* OKAY and FAIL indicate command is complete */ |
| if (!strncmp("OKAY", response, 4) || |
| !strncmp("FAIL", response, 4)) { |
| cleanup_command_data(); |
| } |
| } |
| |
| /** |
| * Writes ascii string specified by cmd_parameter to response. |
| * |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_getvar(char *response) |
| { |
| |
| if (cmd_parameter == NULL) { |
| write_fb_response("FAIL", "missing var", response); |
| } else if (!strcmp("version", cmd_parameter)) { |
| write_fb_response("OKAY", FASTBOOT_VERSION, response); |
| } else if (!strcmp("bootloader-version", cmd_parameter) || |
| !strcmp("version-bootloader", cmd_parameter)) { |
| write_fb_response("OKAY", U_BOOT_VERSION, response); |
| } else if (!strcmp("downloadsize", cmd_parameter) || |
| !strcmp("max-download-size", cmd_parameter)) { |
| char buf_size_str[12]; |
| sprintf(buf_size_str, "0x%08x", CONFIG_FASTBOOT_BUF_SIZE); |
| write_fb_response("OKAY", buf_size_str, response); |
| } else if (!strcmp("serialno", cmd_parameter)) { |
| const char *tmp = env_get("serial#"); |
| if (tmp) { |
| write_fb_response("OKAY", tmp, response); |
| } else { |
| write_fb_response("FAIL", "Value not set", response); |
| } |
| } else if (!strcmp("version-baseband", cmd_parameter)) { |
| write_fb_response("OKAY", "N/A", response); |
| } else if (!strcmp("product", cmd_parameter)) { |
| const char *board = env_get("board"); |
| if (board) { |
| write_fb_response("OKAY", board, response); |
| } else { |
| write_fb_response("FAIL", "Board not set", response); |
| } |
| } else if (!strcmp("current-slot", cmd_parameter)) { |
| /* A/B not implemented, for now always return _a */ |
| write_fb_response("OKAY", "_a", response); |
| } else if (!strcmp("slot-suffixes", cmd_parameter)) { |
| write_fb_response("OKAY", "_a,_b", response); |
| } else if (!strncmp("has-slot", cmd_parameter, 8)) { |
| char *part_name = cmd_parameter; |
| |
| cmd_parameter = strsep(&part_name, ":"); |
| if (!strcmp(part_name, "boot") || !strcmp(part_name, "system")) { |
| write_fb_response("OKAY", "yes", response); |
| } else { |
| write_fb_response("OKAY", "no", response); |
| } |
| } else if (!strncmp("partition-type", cmd_parameter, 14) || |
| !strncmp("partition-size", cmd_parameter, 14)) { |
| disk_partition_t part_info; |
| struct blk_desc *dev_desc; |
| char *part_name = cmd_parameter; |
| char part_size_str[20]; |
| |
| cmd_parameter = strsep(&part_name, ":"); |
| dev_desc = blk_get_dev("mmc", 0); |
| if (!dev_desc) { |
| write_fb_response("FAIL", "block device not found", response); |
| } else if (part_get_info_by_name(dev_desc, part_name, &part_info) < 0) { |
| write_fb_response("FAIL", "partition not found", response); |
| } else if (!strncmp("partition-type", cmd_parameter, 14)) { |
| write_fb_response("OKAY", (char*)part_info.type, response); |
| } else if (!strncmp("partition-size", cmd_parameter, 14)) { |
| sprintf(part_size_str, "0x%016x", (int)part_info.size); |
| write_fb_response("OKAY", part_size_str, response); |
| } |
| } else { |
| printf("WARNING: unknown variable: %s\n", cmd_parameter); |
| write_fb_response("FAIL", "Variable not implemented", response); |
| } |
| } |
| |
| /** |
| * Copies image data from fastboot_data to CONFIG_FASTBOOT_BUF_ADDR. |
| * Writes to response. |
| * |
| * @param fastboot_data Pointer to received fastboot data |
| * @param fastboot_data_len Length of received fastboot data |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_download(char *fastboot_data, unsigned int fastboot_data_len, |
| char *response) |
| { |
| char *tmp; |
| |
| if (bytes_expected == 0) { |
| if (cmd_parameter == NULL) { |
| write_fb_response("FAIL", "Expected command parameter", response); |
| return; |
| } |
| bytes_expected = simple_strtoul(cmd_parameter, &tmp, 16); |
| if (bytes_expected == 0) { |
| write_fb_response("FAIL", "Expected nonzero image size", response); |
| return; |
| } |
| } |
| if (fastboot_data_len == 0 && bytes_received == 0) { |
| /* Nothing to download yet. Response is of the form: |
| * [DATA|FAIL]$cmd_parameter |
| * |
| * where cmd_parameter is an 8 digit hexadecimal number |
| */ |
| if (bytes_expected > CONFIG_FASTBOOT_BUF_SIZE) { |
| write_fb_response("FAIL", cmd_parameter, response); |
| } else { |
| write_fb_response("DATA", cmd_parameter, response); |
| } |
| } else if (fastboot_data_len == 0 && (bytes_received >= bytes_expected)) { |
| /* Download complete. Respond with "OKAY" */ |
| write_fb_response("OKAY", "", response); |
| image_size = bytes_received; |
| bytes_expected = bytes_received = 0; |
| } else { |
| if (fastboot_data_len == 0 || |
| (bytes_received + fastboot_data_len) > bytes_expected) { |
| write_fb_response("FAIL", "Received invalid data length", response); |
| return; |
| } |
| /* Download data to CONFIG_FASTBOOT_BUF_ADDR */ |
| memcpy((void*)CONFIG_FASTBOOT_BUF_ADDR + bytes_received, fastboot_data, |
| fastboot_data_len); |
| bytes_received += fastboot_data_len; |
| } |
| } |
| |
| /** |
| * Writes the previously downloaded image to the partition indicated by |
| * cmd_parameter. Writes to response. |
| * |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_flash(char *response) |
| { |
| fb_mmc_flash_write(cmd_parameter, (void*)CONFIG_FASTBOOT_BUF_ADDR, |
| image_size, response); |
| } |
| |
| /** |
| * Erases the partition indicated by cmd_parameter (clear to 0x00s). Writes |
| * to response. |
| * |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_erase(char *response) |
| { |
| fb_mmc_erase(cmd_parameter, response); |
| } |
| |
| /** |
| * Continues normal boot process by running "bootcmd". Writes |
| * to response. |
| * |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_continue(char *response) |
| { |
| char *bootcmd; |
| bootcmd = env_get("bootcmd"); |
| if (bootcmd) { |
| write_fb_response("OKAY", "", response); |
| } else { |
| write_fb_response("FAIL", "bootcmd not set", response); |
| } |
| } |
| |
| /** |
| * Sets reboot bootloader flag if requested. Writes to response. |
| * |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void fb_reboot(char *response) |
| { |
| write_fb_response("OKAY", "", response); |
| if (!strcmp("reboot-bootloader", cmd_string)) { |
| strcpy((char*)CONFIG_FASTBOOT_BUF_ADDR, "reboot-bootloader"); |
| } |
| } |
| |
| /** |
| * Boots into downloaded image. |
| */ |
| static void boot_downloaded_image(void) |
| { |
| char kernel_addr[12]; |
| char *fdt_addr = env_get("fdt_addr_r"); |
| char *bootm_args[] = { "bootm", kernel_addr, "-", fdt_addr, NULL }; |
| |
| sprintf(kernel_addr, "0x%lx", (long)CONFIG_FASTBOOT_BUF_ADDR); |
| |
| printf("\nBooting kernel at %s with fdt at %s...\n\n\n", |
| kernel_addr, fdt_addr); |
| do_bootm(NULL, 0, 4, bootm_args); |
| |
| /* This only happens if image is faulty so we start over. */ |
| do_reset(NULL, 0, 0, NULL); |
| } |
| |
| /** |
| * Writes a response to response buffer of the form "$tag$reason". |
| * |
| * @param tag The first part of the response |
| * @param reason The second part of the response |
| * @param repsonse Pointer to fastboot response buffer |
| */ |
| static void write_fb_response(const char* tag, const char *reason, |
| char *response) |
| { |
| strncpy(response, tag, strlen(tag)); |
| strncat(response, reason, FASTBOOT_RESPONSE_LEN - strlen(tag) - 1); |
| } |
| |
| /** |
| * Frees any resources allocated during current fastboot command. |
| */ |
| static void cleanup_command_data(void) |
| { |
| /* cmd_parameter and cmd_string potentially point to memory allocated by |
| * strdup |
| */ |
| if (cmd_parameter) { |
| free(cmd_parameter); |
| } |
| if (cmd_string) { |
| free(cmd_string); |
| } |
| cmd_parameter = cmd_string = NULL; |
| } |
| |
| /** |
| * Incoming UDP packet handler. |
| * |
| * @param packet Pointer to incoming UDP packet |
| * @param dport Destination UDP port |
| * @param sip Source IP address |
| * @param sport Source UDP port |
| * @param len Packet length |
| */ |
| static void fastboot_handler(uchar *packet, unsigned dport, struct in_addr sip, |
| unsigned sport, unsigned len) |
| { |
| struct fastboot_header fb_header; |
| char fastboot_data[DATA_SIZE] = {0}; |
| unsigned int fastboot_data_len = 0; |
| |
| if (dport != fastboot_our_port) { |
| return; |
| } |
| |
| fastboot_remote_ip = sip; |
| fastboot_remote_port = sport; |
| |
| if (len < FASTBOOT_HEADER_SIZE || len > PACKET_SIZE) { |
| return; |
| } |
| memcpy(&fb_header, packet, sizeof(fb_header)); |
| fb_header.flags = 0; |
| fb_header.seq = ntohs(fb_header.seq); |
| packet += sizeof(fb_header); |
| len -= sizeof(fb_header); |
| |
| switch (fb_header.id) { |
| case FASTBOOT_QUERY: |
| fastboot_send(fb_header, fastboot_data, 0, 0); |
| break; |
| case FASTBOOT_INIT: |
| case FASTBOOT_FASTBOOT: |
| fastboot_data_len = len; |
| if (len > 0) { |
| memcpy(fastboot_data, packet, len); |
| } |
| if (fb_header.seq == fb_sequence_number) { |
| fastboot_send(fb_header, fastboot_data, fastboot_data_len, 0); |
| fb_sequence_number++; |
| } else if (fb_header.seq == fb_sequence_number - 1) { |
| /* Retransmit last sent packet */ |
| fastboot_send(fb_header, fastboot_data, fastboot_data_len, 1); |
| } |
| break; |
| default: |
| pr_err("ID %d not implemented.\n", fb_header.id); |
| fb_header.id = FASTBOOT_ERROR; |
| fastboot_send(fb_header, fastboot_data, 0, 0); |
| break; |
| } |
| } |
| |
| void fastboot_start_server(void) |
| { |
| printf("Using %s device\n", eth_get_name()); |
| printf("Listening for fastboot command on %pI4\n", &net_ip); |
| |
| fastboot_our_port = WELL_KNOWN_PORT; |
| |
| net_set_udp_handler(fastboot_handler); |
| |
| /* zero out server ether in case the server ip has changed */ |
| memset(net_server_ethaddr, 0, 6); |
| } |