| /* |
| * Copyright 2015 Google Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but without any warranty; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <libpayload.h> |
| |
| #include "drivers/video/coreboot_fb.h" |
| #include "drivers/video/display.h" |
| #include "fastboot/backend.h" |
| #include "fastboot/capabilities.h" |
| #include "fastboot/fastboot.h" |
| #include "fastboot/udc.h" |
| #include "vboot/boot.h" |
| #include "vboot/boot_policy.h" |
| |
| #define FASTBOOT_DEBUG |
| |
| #ifdef FASTBOOT_DEBUG |
| #define FB_LOG(args...) printf(args); |
| #else |
| #define FB_LOG(args...) |
| #endif |
| |
| #define MAX_COMMAND_LENGTH 64 |
| #define MAX_RESPONSE_LENGTH 64 |
| |
| /* Pointer to memory location where image is downloaded for further action */ |
| static void *image_addr; |
| static size_t image_size; |
| |
| /********************* Stubs *************************/ |
| |
| int __attribute__((weak)) board_should_enter_device_mode(void) |
| { |
| return 0; |
| } |
| |
| /************* Responses to Host **********************/ |
| /* |
| * Func: fb_send |
| * Desc: Send message from output buffer after attaching the prefix. It also |
| * resets the output buffer. |
| * |
| * This function expects that the output buffer has first 4 bytes unused so that |
| * it can prepend the message type (INFO, DATA, FAIL, OKAY). Also, the output |
| * buffer is expected to be in the following state: |
| * head = 0, tail = pointing to end of data |
| * Thus, length = tail - head (So, length = PREFIX_LEN + output str len). |
| * |
| * On return, buffer state is: head = 0, tail = PREFIX_LEN. |
| */ |
| static void fb_send(struct fb_buffer *output, const char *prefix) |
| { |
| const size_t prefix_size = PREFIX_LEN; |
| size_t response_length = fb_buffer_length(output); |
| |
| fb_buffer_rewind(output); |
| |
| char *resp = fb_buffer_tail(output); |
| memcpy(resp, prefix, prefix_size); |
| |
| FB_LOG("Response: %.*s\n", (int)response_length, resp); |
| |
| usb_gadget_send(resp, response_length); |
| |
| fb_buffer_push(output, PREFIX_LEN); |
| } |
| |
| /* |
| * Func: fb_execute_send |
| * Desc: Execute send command based on the cmd response type. It also performs |
| * rewind operation on the output buffer. |
| */ |
| static void fb_execute_send(struct fb_cmd *cmd) |
| { |
| static const char prefix[][PREFIX_LEN + 1] = { |
| [FB_DATA] = "DATA", |
| [FB_FAIL] = "FAIL", |
| [FB_INFO] = "INFO", |
| [FB_OKAY] = "OKAY", |
| }; |
| |
| fb_send(&cmd->output, prefix[cmd->type]); |
| } |
| |
| /************** Command Handlers ***********************/ |
| /* |
| * Default weak implementation returning -1. Board should implement this |
| * function. |
| */ |
| int __attribute__((weak)) get_board_var(struct fb_cmd *cmd, fb_getvar_t var) |
| { |
| return -1; |
| } |
| |
| void fb_add_string(struct fb_buffer *buff, const char *str, const char *args) |
| { |
| if (str == NULL) |
| return; |
| |
| char *data = fb_buffer_tail(buff); |
| size_t rem_len = fb_buffer_remaining(buff); |
| int ret; |
| |
| ret = snprintf(data, rem_len, str, args); |
| |
| if (ret > rem_len) |
| ret = rem_len; |
| else if (ret < 0) |
| ret = 0; |
| |
| fb_buffer_push(buff, ret); |
| } |
| |
| void fb_add_number(struct fb_buffer *buff, const char *format, |
| unsigned long long num) |
| { |
| if (format == NULL) |
| return; |
| |
| char *data = fb_buffer_tail(buff); |
| size_t rem_len = fb_buffer_remaining(buff); |
| |
| int ret = snprintf(data, rem_len, format, num); |
| |
| if (ret > rem_len) |
| ret = rem_len; |
| else if (ret < 0) |
| ret = 0; |
| |
| fb_buffer_push(buff, ret); |
| } |
| |
| static void fb_copy_buffer_bytes(struct fb_buffer *out, const char *src, |
| size_t len) |
| { |
| char *dst = fb_buffer_tail(out); |
| memcpy(dst, src, len); |
| fb_buffer_push(out, len); |
| } |
| |
| static void fb_copy_buffer_data(struct fb_buffer *out, struct fb_buffer *in) |
| { |
| const char *src = fb_buffer_head(in); |
| size_t len = fb_buffer_length(in); |
| fb_copy_buffer_bytes(out, src, len); |
| } |
| |
| static char *fb_get_string(const char *src, size_t len) |
| { |
| char *dst = xmalloc(len + 1); |
| memcpy(dst, src, len); |
| dst[len] = '\0'; |
| return dst; |
| } |
| |
| static inline void fb_free_string(char *dst) |
| { |
| free(dst); |
| } |
| |
| static int fb_read_var(struct fb_cmd *cmd, fb_getvar_t var) |
| { |
| size_t input_len = fb_buffer_length(&cmd->input); |
| |
| struct fb_buffer *output = &cmd->output; |
| |
| switch (var) { |
| case FB_VERSION: |
| fb_add_string(output, FB_VERSION_STRING, NULL); |
| break; |
| |
| case FB_PART_SIZE: { |
| if (input_len == 0) { |
| fb_add_string(output, "invalid partition", NULL); |
| return -1; |
| } |
| |
| char *data = fb_buffer_pull(&cmd->input, input_len); |
| char *part_name = fb_get_string(data, input_len); |
| unsigned long long part_size; |
| |
| part_size = backend_get_part_size_bytes(part_name); |
| fb_free_string(part_name); |
| |
| if (part_size == 0) { |
| fb_add_string(output, "invalid partition", NULL); |
| return -1; |
| } |
| |
| fb_add_number(output, "0x%llx", part_size); |
| break; |
| } |
| case FB_PART_TYPE: { |
| if (input_len == 0) { |
| fb_add_string(output, "invalid partition", NULL); |
| return -1; |
| } |
| |
| char *data = fb_buffer_pull(&cmd->input, input_len); |
| |
| char *part_name = fb_get_string(data, input_len); |
| const char *str = backend_get_part_fs_type(part_name); |
| fb_free_string(part_name); |
| |
| if (str == NULL) { |
| fb_add_string(output, "invalid partition", NULL); |
| return -1; |
| } |
| |
| fb_add_string(output, str, NULL); |
| break; |
| } |
| case FB_BDEV_SIZE: { |
| if (input_len == 0) { |
| fb_add_string(output, "invalid bdev", NULL); |
| return -1; |
| } |
| |
| char *data = fb_buffer_pull(&cmd->input, input_len); |
| char *bdev_name = fb_get_string(data, input_len); |
| unsigned long long bdev_size; |
| |
| bdev_size = backend_get_bdev_size_bytes(bdev_name); |
| fb_free_string(bdev_name); |
| |
| if (bdev_size == 0) { |
| fb_add_string(output, "invalid bdev", NULL); |
| return -1; |
| } |
| |
| fb_add_number(output, "%llu", bdev_size); |
| break; |
| } |
| case FB_SECURE: { |
| if (fb_cap_func_allowed(FB_ID_FLASH) == FB_CAP_FUNC_NOT_ALLOWED) |
| fb_add_string(output, "%s", "no"); |
| else |
| fb_add_string(output, "%s", "yes"); |
| break; |
| } |
| default: |
| goto board_read; |
| } |
| |
| return 0; |
| |
| board_read: |
| return get_board_var(cmd, var); |
| } |
| |
| struct name_string { |
| const char *str; |
| int expects_args; |
| char delim; |
| }; |
| |
| #define NAME_NO_ARGS(s) {.str = s, .expects_args = 0} |
| #define NAME_ARGS(s, d) {.str = s, .expects_args = 1, .delim = d} |
| |
| static size_t name_check_match(const char *str, size_t len, |
| const struct name_string *name) |
| { |
| size_t str_len = strlen(name->str); |
| |
| /* If name len is greater than input, return 0. */ |
| if (str_len > len) |
| return 0; |
| |
| /* If name str does not match input string, return 0. */ |
| if (memcmp(name->str, str, str_len)) |
| return 0; |
| |
| if (name->expects_args) { |
| /* string should have space for delim */ |
| if (len == str_len) |
| return 0; |
| |
| /* Check delim match */ |
| if (name->delim != str[str_len]) |
| return 0; |
| } else { |
| /* Name str len should match input len */ |
| if (str_len != len) |
| return 0; |
| } |
| |
| return str_len + name->expects_args; |
| } |
| |
| static const struct { |
| struct name_string name; |
| fb_getvar_t var; |
| } getvar_table[] = { |
| { NAME_NO_ARGS("version"), FB_VERSION}, |
| { NAME_NO_ARGS("version-bootloader"), FB_BOOTLOADER_VERSION}, |
| { NAME_NO_ARGS("version-baseband"), FB_BASEBAND_VERSION}, |
| { NAME_NO_ARGS("product"), FB_PRODUCT}, |
| { NAME_NO_ARGS("serialno"), FB_SERIAL_NO}, |
| { NAME_NO_ARGS("secure"), FB_SECURE}, |
| { NAME_NO_ARGS("max-download-size"), FB_DWNLD_SIZE}, |
| { NAME_ARGS("partition-type", ':'), FB_PART_TYPE}, |
| { NAME_ARGS("partition-size", ':'), FB_PART_SIZE}, |
| /* |
| * OEM specific : |
| * Spec says names starting with lowercase letter are reserved. |
| */ |
| { NAME_ARGS("Bdev-size", ':'), FB_BDEV_SIZE}, |
| }; |
| |
| /* |
| * Func: fb_getvar_single |
| * Desc: Returns value of a single variable requested by host. |
| */ |
| static fb_ret_type fb_getvar_single(struct fb_cmd *cmd) |
| { |
| int i; |
| size_t match_len = 0; |
| const char *input = fb_buffer_head(&cmd->input); |
| size_t len = fb_buffer_length(&cmd->input); |
| size_t out_len = fb_buffer_length(&cmd->output); |
| |
| for (i = 0; i < ARRAY_SIZE(getvar_table); i++) { |
| match_len = name_check_match(input, len, &getvar_table[i].name); |
| if (match_len) |
| break; |
| } |
| |
| if (match_len == 0) { |
| fb_add_string(&cmd->output, "unknown variable", NULL); |
| cmd->type = FB_OKAY; |
| return FB_SUCCESS; |
| } |
| |
| fb_buffer_pull(&cmd->input, match_len); |
| |
| if (fb_read_var(cmd, getvar_table[i].var) == 0) |
| return FB_SUCCESS; |
| |
| /* |
| * Since fb_read_var returned non-zero value it means that we were not |
| * able to read variable value. Thus, send message type to be sent back |
| * to host as FAIL. |
| */ |
| cmd->type = FB_OKAY; |
| |
| /* |
| * If no new information was added by board about the failure, put in |
| * nonexistent string. |
| */ |
| if (fb_buffer_length(&cmd->output) == out_len) |
| fb_add_string(&cmd->output, "unknown variable", NULL); |
| |
| return FB_SUCCESS; |
| } |
| |
| /* |
| * Func: fb_getvar_all |
| * Desc: Send to host values of all possible variables. |
| */ |
| static fb_ret_type fb_getvar_all(struct fb_cmd *host_cmd) |
| { |
| int i; |
| |
| char pkt[MAX_COMMAND_LENGTH]; |
| char rsp[MAX_RESPONSE_LENGTH]; |
| |
| struct fb_cmd cmd = { |
| .input = {.data = pkt, .capacity = MAX_COMMAND_LENGTH}, |
| .output = {.data = rsp, .capacity = MAX_RESPONSE_LENGTH}, |
| }; |
| |
| struct fb_buffer *cmd_in = &cmd.input; |
| struct fb_buffer *cmd_out = &cmd.output; |
| |
| for (i = 0; i < ARRAY_SIZE(getvar_table); i++) { |
| |
| fb_getvar_t var = getvar_table[i].var; |
| |
| /* Leave space for prefix. */ |
| fb_buffer_push(cmd_out, PREFIX_LEN); |
| |
| const struct name_string *vname = &getvar_table[i].name; |
| |
| /* |
| * Add variable name to input string to mimic a real host |
| * command. |
| */ |
| fb_add_string(cmd_in, "%s", vname->str); |
| if (vname->expects_args) |
| fb_copy_buffer_bytes(cmd_in, &vname->delim, 1); |
| |
| /* |
| * For the getvar all command from host, we need to send back |
| * multiple INFO packets, with each INFO describing a variable |
| * and its value. We mimic a host command in the input buffer |
| * here. |
| * |
| * However, for variables that require additional argument, we |
| * need to ensure that it is filled in properly in the input |
| * buffer. Thus, we check for the variables that we know have an |
| * extra argument and handle them accordingly. Since, the |
| * argument can have different values, we run a loop to append |
| * different arguments one at a time to the input buffer. Then, |
| * we call getvar_single with our own host command. |
| * e.g. |
| * var:a |
| * var:b |
| * var:c |
| * |
| * The output buffer needs to contain the input string in |
| * addition to the variable value. Thus, we use |
| * fb_copy_buffer_data to copy data from input buffer to eoutput |
| * buffer and append ": " and the value of the variable. Then, |
| * the output buffer is sent back to the host as INFO message. |
| * e.g. |
| * INFOvar:a: x |
| * INFOvar:b: y |
| * INFOvar:c: z |
| */ |
| |
| switch(var) { |
| case FB_PART_TYPE: |
| case FB_PART_SIZE: |
| case FB_BDEV_SIZE: { |
| int j; |
| |
| struct fb_cmd curr_cmd; |
| |
| struct fb_buffer *input = &curr_cmd.input; |
| struct fb_buffer *output = &curr_cmd.output; |
| |
| int count = (var == FB_BDEV_SIZE)? fb_bdev_count: |
| fb_part_count; |
| |
| for (j = 0; j < count; j++) { |
| const char *pname; |
| if (var == FB_BDEV_SIZE) |
| pname = fb_bdev_list[j].name; |
| else |
| pname = fb_part_list[j].part_name; |
| |
| fb_buffer_clone(cmd_in, input); |
| fb_buffer_clone(cmd_out, output); |
| |
| fb_add_string(input, pname, NULL); |
| fb_copy_buffer_data(output, input); |
| fb_add_string(output, ": ", NULL); |
| |
| curr_cmd.type = FB_INFO; |
| fb_getvar_single(&curr_cmd); |
| |
| if (curr_cmd.type == FB_INFO) |
| fb_execute_send(&curr_cmd); |
| } |
| break; |
| } |
| default: |
| fb_copy_buffer_data(cmd_out, cmd_in); |
| fb_add_string(cmd_out, ": ", NULL); |
| cmd.type = FB_INFO; |
| fb_getvar_single(&cmd); |
| if (cmd.type == FB_INFO) |
| fb_execute_send(&cmd); |
| } |
| |
| /* Reset cmd buffers */ |
| fb_buffer_rewind(cmd_out); |
| fb_buffer_rewind(cmd_in); |
| } |
| |
| fb_add_string(&host_cmd->output, "Done!", NULL); |
| host_cmd->type = FB_OKAY; |
| |
| return FB_SUCCESS; |
| } |
| |
| static fb_ret_type fb_getvar(struct fb_cmd *cmd) |
| { |
| const char *input = fb_buffer_head(&cmd->input); |
| size_t len = fb_buffer_length(&cmd->input); |
| |
| if (len == 0) { |
| fb_add_string(&cmd->output, "invalid length", NULL); |
| cmd->type = FB_FAIL; |
| return FB_SUCCESS; |
| } |
| |
| const char *str_read_all = "all"; |
| |
| if ((len == strlen(str_read_all)) && |
| (strncmp(input, str_read_all, len) == 0)) |
| return fb_getvar_all(cmd); |
| else { |
| cmd->type = FB_OKAY; |
| return fb_getvar_single(cmd); |
| } |
| } |
| |
| static void free_image_space(void) |
| { |
| if (image_addr) { |
| free(image_addr); |
| image_addr = NULL; |
| image_size = 0; |
| } |
| } |
| |
| static void alloc_image_space(size_t bytes) |
| { |
| free_image_space(); |
| image_addr = xmalloc(bytes); |
| if (image_addr) |
| image_size = bytes; |
| } |
| |
| /* |
| * Func: fb_recv_data |
| * Desc: Download data from host and store it in image_addr |
| * |
| */ |
| static void fb_recv_data(struct fb_cmd *cmd) |
| { |
| size_t curr_len = 0; |
| |
| while (curr_len < image_size) { |
| void *curr = (uint8_t *)image_addr + curr_len; |
| curr_len += usb_gadget_recv(curr, image_size - curr_len); |
| } |
| |
| cmd->type = FB_OKAY; |
| } |
| |
| /* |
| * Func: fb_download |
| * Desc: Allocate space for downloading image and receive image from host. |
| */ |
| static fb_ret_type fb_download(struct fb_cmd *cmd) |
| { |
| const char *input = fb_buffer_head(&cmd->input); |
| size_t len = fb_buffer_length(&cmd->input); |
| struct fb_buffer *output = &cmd->output; |
| |
| cmd->type = FB_FAIL; |
| |
| /* Length should be 8 bytes */ |
| if (len != 8) { |
| fb_add_string(output, "invalid length", NULL); |
| return FB_SUCCESS; |
| } |
| |
| char *num = fb_get_string(input, len); |
| |
| /* num of bytes are passed in hex(0x) format */ |
| unsigned long bytes = strtoul(num, NULL, 16); |
| |
| fb_free_string(num); |
| |
| alloc_image_space(bytes); |
| |
| if ((image_addr == NULL) || (image_size == 0)) { |
| fb_add_string(output, "not sufficient memory", NULL); |
| return FB_SUCCESS; |
| } |
| |
| cmd->type = FB_DATA; |
| fb_add_number(output, "%08lx", bytes); |
| fb_execute_send(cmd); |
| |
| fb_recv_data(cmd); |
| |
| return FB_SUCCESS; |
| } |
| |
| /* TODO(furquan): Do we need this? */ |
| static fb_ret_type fb_verify(struct fb_cmd *cmd) |
| { |
| fb_add_string(&cmd->output, "unsupported command", NULL); |
| cmd->type = FB_FAIL; |
| |
| return FB_SUCCESS; |
| } |
| |
| const char *backend_error_string[] = { |
| [BE_PART_NOT_FOUND] = "partition not found", |
| [BE_BDEV_NOT_FOUND] = "block device not found", |
| [BE_IMAGE_SIZE_MULTIPLE_ERR] = "image not multiple of block size", |
| [BE_IMAGE_OVERFLOW_ERR] = "image greater than partition size", |
| [BE_WRITE_ERR] = "image write failed", |
| [BE_SPARSE_HDR_ERR] = "sparse header error", |
| [BE_CHUNK_HDR_ERR] = "sparse chunk header error", |
| [BE_GPT_ERR] = "GPT error", |
| }; |
| |
| static fb_ret_type fb_erase(struct fb_cmd *cmd) |
| { |
| backend_ret_t ret; |
| struct fb_buffer *input = &cmd->input; |
| size_t len = fb_buffer_length(input); |
| char *data = fb_buffer_pull(input, len); |
| |
| FB_LOG("erasing flash\n"); |
| cmd->type = FB_INFO; |
| fb_add_string(&cmd->output, "erasing flash", NULL); |
| fb_execute_send(cmd); |
| |
| cmd->type = FB_OKAY; |
| |
| char *partition = fb_get_string(data, len); |
| ret = backend_erase_partition(partition); |
| fb_free_string(partition); |
| |
| if (ret != BE_SUCCESS) { |
| cmd->type = FB_FAIL; |
| fb_add_string(&cmd->output, backend_error_string[ret], NULL); |
| } |
| |
| free(partition); |
| |
| return FB_SUCCESS; |
| } |
| |
| static fb_ret_type fb_flash(struct fb_cmd *cmd) |
| { |
| backend_ret_t ret; |
| |
| if (image_addr == NULL) { |
| fb_add_string(&cmd->output, "no image downloaded", NULL); |
| cmd->type = FB_FAIL; |
| return FB_SUCCESS; |
| } |
| |
| FB_LOG("writing flash\n"); |
| cmd->type = FB_INFO; |
| fb_add_string(&cmd->output, "writing flash", NULL); |
| fb_execute_send(cmd); |
| |
| struct fb_buffer *input = &cmd->input; |
| size_t len = fb_buffer_length(input); |
| char *data = fb_buffer_pull(input, len); |
| |
| cmd->type = FB_OKAY; |
| |
| char *partition = fb_get_string(data, len); |
| ret = backend_write_partition(partition, image_addr, image_size); |
| fb_free_string(partition); |
| |
| if (ret != BE_SUCCESS) { |
| cmd->type = FB_FAIL; |
| fb_add_string(&cmd->output, backend_error_string[ret], NULL); |
| } |
| |
| free(partition); |
| |
| return FB_SUCCESS; |
| } |
| |
| /* |
| * TODO(furquan): Change this function once verified boot stuff is |
| * resolved. Currently, we boot unsigned kernel bootimg from memory. However, we |
| * will have to update this so that we check the signature on the image using |
| * recovery key. Also, need to ensure that fastboot boot command is not enabled |
| * by default on entering recovery mode. It should be enabled only after some |
| * flag VB_ALLOW_FB_BOOT is set by user. |
| */ |
| static fb_ret_type fb_boot(struct fb_cmd *cmd) |
| { |
| VbSelectAndLoadKernelParams kparams; |
| struct boot_info bi; |
| |
| cmd->type = FB_FAIL; |
| |
| if (image_addr == NULL) { |
| fb_add_string(&cmd->output, "no image downloaded", NULL); |
| return FB_SUCCESS; |
| } |
| |
| kparams.kernel_buffer = image_addr; |
| kparams.flags = KERNEL_IMAGE_BOOTIMG; |
| |
| if (fill_boot_info(&bi, &kparams)) { |
| fb_add_string(&cmd->output, "bootimg parse failed", NULL); |
| return FB_SUCCESS; |
| } |
| |
| cmd->type = FB_OKAY; |
| fb_execute_send(cmd); |
| |
| boot(&bi); |
| |
| /* We should never reach here, if boot successful. */ |
| return FB_SUCCESS; |
| } |
| |
| static fb_ret_type fb_continue(struct fb_cmd *cmd) |
| { |
| FB_LOG("Continue booting\n"); |
| cmd->type = FB_OKAY; |
| return FB_NORMAL_BOOT; |
| } |
| |
| static fb_ret_type fb_reboot(struct fb_cmd *cmd) |
| { |
| FB_LOG("Rebooting device into normal mode\n"); |
| cmd->type = FB_OKAY; |
| return FB_REBOOT; |
| } |
| |
| static fb_ret_type fb_reboot_bootloader(struct fb_cmd *cmd) |
| { |
| FB_LOG("Rebooting into bootloader\n"); |
| cmd->type = FB_OKAY; |
| return FB_REBOOT_BOOTLOADER; |
| } |
| |
| static fb_ret_type fb_powerdown(struct fb_cmd *cmd) |
| { |
| FB_LOG("Powering off device\n"); |
| cmd->type = FB_OKAY; |
| return FB_POWEROFF; |
| } |
| |
| /************** Command Function Table *****************/ |
| struct fastboot_func { |
| struct name_string name; |
| fb_func_id_t id; |
| fb_ret_type (*fn)(struct fb_cmd *cmd); |
| }; |
| |
| const struct fastboot_func fb_func_table[] = { |
| { NAME_ARGS("getvar", ':'), FB_ID_GETVAR, fb_getvar}, |
| { NAME_ARGS("download", ':'), FB_ID_DOWNLOAD, fb_download}, |
| { NAME_ARGS("verify", ':'), FB_ID_VERIFY, fb_verify}, |
| { NAME_ARGS("flash", ':'), FB_ID_FLASH, fb_flash}, |
| { NAME_ARGS("erase", ':'), FB_ID_ERASE, fb_erase}, |
| { NAME_NO_ARGS("boot"), FB_ID_BOOT, fb_boot}, |
| { NAME_NO_ARGS("continue"), FB_ID_CONTINUE, fb_continue}, |
| { NAME_NO_ARGS("reboot"), FB_ID_REBOOT, fb_reboot}, |
| { NAME_NO_ARGS("reboot-bootloader"), FB_ID_REBOOT_BOOTLOADER, |
| fb_reboot_bootloader}, |
| { NAME_NO_ARGS("powerdown"), FB_ID_POWERDOWN, fb_powerdown}, |
| }; |
| |
| /************** Protocol Handler ************************/ |
| |
| /* |
| * Func: fastboot_proto_handler |
| * Desc: Handles fastboot commands received from host, takes appropriate action |
| * (if required) and sends back response packets to host |
| * Input is an ascii string without a trailing 0 byte. Max len is 64 bytes. |
| */ |
| static fb_ret_type fastboot_proto_handler(struct fb_cmd *cmd) |
| { |
| struct fb_buffer *in_buff = &cmd->input; |
| struct fb_buffer *out_buff = &cmd->output; |
| |
| size_t len = fb_buffer_length(in_buff); |
| |
| /* Ignore zero-size packets */ |
| if (len == 0) { |
| FB_LOG("Ignoring zero-size packets\n"); |
| cmd->type = FB_OKAY; |
| return FB_SUCCESS; |
| } |
| |
| int i; |
| size_t match_len = 0; |
| const char *input = fb_buffer_head(in_buff); |
| |
| for (i = 0; i < ARRAY_SIZE(fb_func_table); i++) { |
| match_len = name_check_match(input, len, |
| &fb_func_table[i].name); |
| if (match_len) |
| break; |
| } |
| |
| if (match_len == 0) { |
| FB_LOG("Unknown command\n"); |
| fb_add_string(out_buff, "unknown command", NULL); |
| cmd->type = FB_FAIL; |
| return FB_SUCCESS; |
| } |
| |
| /* Check if this function is allowed to be executed */ |
| if (fb_cap_func_allowed(fb_func_table[i].id) == |
| FB_CAP_FUNC_NOT_ALLOWED) { |
| FB_LOG("Unsupported command\n"); |
| fb_add_string(out_buff, "unsupported command", NULL); |
| cmd->type = FB_FAIL; |
| return FB_SUCCESS; |
| } |
| |
| fb_buffer_pull(in_buff, match_len); |
| |
| return fb_func_table[i].fn(cmd); |
| } |
| |
| static void print_input(struct fb_cmd *cmd) |
| { |
| #ifdef FASTBOOT_DEBUG |
| const char *pkt = fb_buffer_head(&cmd->input); |
| size_t len = fb_buffer_length(&cmd->input); |
| printf("Received: %.*s\n", (int)len, pkt); |
| #endif |
| } |
| |
| static void print_string(const char *str) |
| { |
| int str_len = strlen(str); |
| while (str_len--) { |
| if (*str == '\n') |
| video_console_putchar('\r'); |
| video_console_putchar(*str++); |
| } |
| } |
| |
| /* |
| * TODO(furquan): Get rid of this once the vboot flows and fastboot interactions |
| * are finalized. |
| */ |
| static void fb_print_on_screen() |
| { |
| const char *msg = "Entered fastboot mode"; |
| unsigned int rows, cols; |
| |
| if (display_init()) |
| return; |
| |
| if (backlight_update(1)) |
| return; |
| |
| video_init(); |
| video_console_cursor_enable(0); |
| |
| video_get_rows_cols(&rows, &cols); |
| video_console_set_cursor((cols - strlen(msg)) / 2, rows / 2); |
| |
| print_string(msg); |
| } |
| |
| /* |
| * Func: device_mode_enter |
| * Desc: This function handles the entry into the device mode. It is responsible |
| * for: |
| * 1) Driving the USB gadget. |
| * 2) Calling the protocol handler. |
| * 3) Exiting fastboot and booting / rebooting / powering off the device. |
| */ |
| fb_ret_type device_mode_enter(void) |
| { |
| fb_ret_type ret = FB_CONTINUE_RECOVERY; |
| |
| /* Initialize USB gadget driver */ |
| if (!usb_gadget_init()) { |
| FB_LOG("Gadget not initialized\n"); |
| return ret; |
| } |
| |
| /* |
| * Extra +1 is to store the '\0' character to ease string |
| * operations. Host never ends a string with '\0'. |
| */ |
| char pkt[MAX_COMMAND_LENGTH]; |
| char rsp[MAX_RESPONSE_LENGTH]; |
| |
| struct fb_cmd cmd = { |
| .input = {.data = pkt, .capacity = MAX_COMMAND_LENGTH}, |
| .output = {.data = rsp, .capacity = MAX_RESPONSE_LENGTH}, |
| }; |
| |
| /* Leave space for prefix */ |
| fb_buffer_push(&cmd.output, PREFIX_LEN); |
| |
| FB_LOG("********** Entered fastboot mode *****************\n"); |
| fb_print_on_screen(); |
| |
| /* |
| * Keep looping until we get boot, reboot or poweroff command from host. |
| */ |
| do { |
| size_t len; |
| |
| /* Receive a packet from the host */ |
| len = usb_gadget_recv(pkt, MAX_COMMAND_LENGTH); |
| |
| fb_buffer_push(&cmd.input, len); |
| |
| print_input(&cmd); |
| |
| /* Process the packet as per fastboot protocol */ |
| ret = fastboot_proto_handler(&cmd); |
| |
| fb_execute_send(&cmd); |
| |
| fb_buffer_rewind(&cmd.input); |
| } while (ret == FB_SUCCESS); |
| |
| /* Done with usb gadget driver - Stop it */ |
| usb_gadget_stop(); |
| |
| /* Ret should be either poweroff, reboot or reboot bootloader */ |
| if ((ret != FB_POWEROFF) && (ret != FB_REBOOT) && |
| (ret != FB_REBOOT_BOOTLOADER)) |
| ret = FB_REBOOT; |
| |
| FB_LOG("********** Exit fastboot mode *****************\n"); |
| return ret; |
| } |