diff --git a/cdba-power.c b/cdba-power.c index cd907bf..8d3f786 100644 --- a/cdba-power.c +++ b/cdba-power.c @@ -71,14 +71,14 @@ int main(int argc, char **argv) } if (on) { - device_power(selected_device, true); + device_power_on(selected_device, MSG_POWER_ON_NORMAL); watch_main_loop(ready); selected_device->usb_always_on = true; selected_device->power_always_on = true; } else { device_usb(selected_device, false); - device_power(selected_device, false); + device_power_off(selected_device); } device_close(selected_device); diff --git a/cdba-server.c b/cdba-server.c index 46d375f..4920e15 100644 --- a/cdba-server.c +++ b/cdba-server.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,26 @@ static const char *username; struct device *selected_device; +struct edl_file { + struct list_head node; + + int fd; + + char *filename; + char *target; +}; + +static struct list_head edl_files = LIST_INIT(edl_files); + +static void edl_present(bool present) +{ + uint8_t value = present ? 1 : 0; + + warnx("edl is %spresent", present? "" : "not "); + + cdba_send_buf(MSG_EDL_PRESENT, 1, &value); +} + static void fastboot_opened(struct fastboot *fb, void *data) { const uint8_t one = 1; @@ -60,6 +81,7 @@ static void msg_select_board(const void *param) fprintf(stderr, "failed to open %s\n", (const char *)param); watch_quit(); } else { + device_edl_open(selected_device, edl_present); device_fastboot_open(selected_device, &fastboot_ops); } @@ -93,6 +115,79 @@ static void msg_fastboot_download(const void *data, size_t len) } } +static struct edl_file *current_edl_file; + +static void msg_edl_download(const void *data, size_t len) +{ + char template[] = "/tmp/cdba.XXXXXX"; + struct edl_file *edl; + + edl = current_edl_file; + + if (!edl) { + edl = calloc(1, sizeof(*edl)); + + edl->filename = strdup(template); + edl->fd = mkstemp(edl->filename); + if (edl->fd < 0) + err(1, "failed to create temporary file"); + + list_append(&edl_files, &edl->node); + + current_edl_file = edl; + } + + write(edl->fd, data, len); + + if (len == 0) + close(edl->fd); +} + +static void msg_edl_flash(const void *data, size_t len) +{ + const char *target = data; + + fprintf(stderr, "edl flash into '%s'\n", target); + + current_edl_file->target = strdup(target); + current_edl_file = NULL; +} + +static void msg_edl_reset(void) +{ + struct edl_file *edl; + const char **argv; + size_t args; + size_t arg = 0; + + fprintf(stderr, "edl reset\n"); + + args = 4 + list_len(&edl_files) * 3 + 1; + argv = calloc(args, sizeof(char *)); + + argv[arg++] = "qdl"; + argv[arg++] = "firehose-hamoa.elf"; + argv[arg++] = "--storage"; + argv[arg++] = "nvme"; + + list_for_each_entry(edl, &edl_files, node) { + argv[arg++] = "write"; + argv[arg++] = edl->target; + argv[arg++] = edl->filename; + } + argv[arg] = NULL; + + if (fork() == 0) { + dup2(STDERR_FILENO, STDOUT_FILENO); + execvp("qdl", (char **)argv); + err(127, "failed to spawn qdl failed"); + } + wait(NULL); + + list_for_each_entry(edl, &edl_files, node) + unlink(edl->filename); +} + static void msg_fastboot_continue(void) { device_fastboot_continue(selected_device); @@ -138,6 +233,7 @@ static int handle_stdin(int fd, void *buf) static struct circ_buf recv_buf = { }; struct msg *msg; struct msg hdr; + uint8_t mode; size_t n; int ret; @@ -171,12 +267,22 @@ static int handle_stdin(int fd, void *buf) // fprintf(stderr, "hard reset\n"); break; case MSG_POWER_ON: - device_power(selected_device, true); + if (msg->len == 1) + mode = *(uint8_t *)msg->data; + else + mode = MSG_POWER_ON_NORMAL; + + if (mode >= MSG_POWER_ON_COUNT) { + fprintf(stderr, "invalid power on mode requested\n"); + exit(1); + } + + device_power_on(selected_device, mode); cdba_send(MSG_POWER_ON); break; case MSG_POWER_OFF: - device_power(selected_device, false); + device_power_off(selected_device); cdba_send(MSG_POWER_OFF); break; @@ -210,6 +316,15 @@ static int handle_stdin(int fd, void *buf) case MSG_KEY_PRESS: msg_key_press(msg->data, msg->len); break; + case MSG_EDL_DOWNLOAD: + msg_edl_download(msg->data, msg->len); + break; + case MSG_EDL_FLASH: + msg_edl_flash(msg->data, msg->len); + break; + case MSG_EDL_RESET: + msg_edl_reset(); + break; default: fprintf(stderr, "unk %d len %d\n", msg->type, msg->len); exit(1); diff --git a/cdba.c b/cdba.c index 6c5dd3f..33795fb 100644 --- a/cdba.c +++ b/cdba.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,15 +25,40 @@ #include "circ_buf.h" #include "list.h" +#define TX_DATA_CHUNK_SIZE 2048 + static bool quit; static bool fastboot_repeat; static bool fastboot_done; static bool fastboot_continue; +static bool edl_pending; static int status_fd = -1; static const char *fastboot_file; +struct edl_file { + struct list_head node; + + char *target; + char *filename; +}; + +static struct list_head edl_files = LIST_INIT(edl_files); + +struct tx_item { + struct list_head node; + + uint8_t type; + uint16_t len; + + int fd; + + uint8_t payload[]; +}; + +static struct list_head tx_queue = LIST_INIT(tx_queue); + static struct termios *tty_unbuffer(void) { static struct termios orig_tios; @@ -128,40 +154,81 @@ static int fork_ssh(const char *host, const char *cmd, int *pipes) return 0; } -#define cdba_send(fd, type) cdba_send_buf(fd, type, 0, NULL) -static int cdba_send_buf(int fd, int type, size_t len, const void *buf) +static ssize_t cdba_tx_one(int fd, struct tx_item *item) { - int ret; + struct iovec iov[2]; + struct msg msg; + void *buf; + ssize_t n; - struct msg msg = { - .type = type, - .len = len - }; + msg.type = item->type; + msg.len = item->len; + + iov[0].iov_base = &msg; + iov[0].iov_len = sizeof(msg); + + if (item->fd != -1 && item->len) { + buf = alloca(item->len); + n = read(item->fd, buf, item->len); + if (n != item->len) + err(1, "failed to read %u bytes from file", item->len); + + iov[1].iov_base = buf; + iov[1].iov_len = item->len; + } else if (item->fd != -1) { + close(item->fd); + } else { + iov[1].iov_base = item->payload; + iov[1].iov_len = item->len; + } - ret = write(fd, &msg, sizeof(msg)); - if (ret < 0) - return ret; + return writev(fd, iov, item->len ? 2 : 1); +} + +static void cdba_queue_data(int type, size_t len, const void *buf) +{ + struct tx_item *item; + + item = calloc(1, sizeof(*item) + len); + item->type = type; + item->len = len; + item->fd = -1; + memcpy(item->payload, buf, len); + + list_append(&tx_queue, &item->node); +} + +static void cdba_queue_fd(int type, size_t len, int fd) +{ + struct tx_item *item; - if (len) - ret = write(fd, buf, len); + item = calloc(1, sizeof(*item) + len); + item->type = type; + item->len = len; + item->fd = fd; - return ret < 0 ? ret : 0; + list_append(&tx_queue, &item->node); } -static int cdba_send_key(int fd, int key, uint8_t state) +static void cdba_queue(int type) +{ + cdba_queue_data(type, 0, NULL); +} + +static void cdba_send_key(int fd, int key, uint8_t state) { struct key_press press = { .key = key, .state = state, }; - return cdba_send_buf(fd, MSG_KEY_PRESS, sizeof(press), &press); + cdba_queue_data(MSG_KEY_PRESS, sizeof(press), &press); } -static int cdba_toggle_key(int fd, int key, bool key_state[DEVICE_KEY_COUNT]) +static void cdba_toggle_key(int fd, int key, bool key_state[DEVICE_KEY_COUNT]) { key_state[key] = !key_state[key]; - return cdba_send_key(fd, key, key_state[key]); + cdba_send_key(fd, key, key_state[key]); } static int tty_callback(int *ssh_fds) @@ -186,25 +253,25 @@ static int tty_callback(int *ssh_fds) quit = true; break; case 'P': - cdba_send(ssh_fds[0], MSG_POWER_ON); + cdba_queue(MSG_POWER_ON); break; case 'p': - cdba_send(ssh_fds[0], MSG_POWER_OFF); + cdba_queue(MSG_POWER_OFF); break; case 's': - cdba_send(ssh_fds[0], MSG_STATUS_UPDATE); + cdba_queue(MSG_STATUS_UPDATE); break; case 'V': - cdba_send(ssh_fds[0], MSG_VBUS_ON); + cdba_queue(MSG_VBUS_ON); break; case 'v': - cdba_send(ssh_fds[0], MSG_VBUS_OFF); + cdba_queue(MSG_VBUS_OFF); break; case 'a': - cdba_send_buf(ssh_fds[0], MSG_CONSOLE, 1, &ctrl_a); + cdba_queue_data(MSG_CONSOLE, 1, &ctrl_a); break; case 'B': - cdba_send(ssh_fds[0], MSG_SEND_BREAK); + cdba_queue(MSG_SEND_BREAK); break; case 'o': cdba_send_key(ssh_fds[0], DEVICE_KEY_POWER, KEY_PRESS_PULSE); @@ -222,207 +289,145 @@ static int tty_callback(int *ssh_fds) special = false; } else { - cdba_send_buf(ssh_fds[0], MSG_CONSOLE, 1, buf + k); + cdba_queue_data(MSG_CONSOLE, 1, buf + k); } } return 0; } -struct work { - void (*fn)(struct work *work, int ssh_stdin); - - struct list_head node; -}; - -static struct list_head work_items = LIST_INIT(work_items); - -static void list_boards_fn(struct work *work, int ssh_stdin) -{ - int ret; - - ret = cdba_send(ssh_stdin, MSG_LIST_DEVICES); - if (ret < 0) - err(1, "failed to send board list request"); - - free(work); -} - +/** + * request_board_list() - Queue a request for a boards list + */ static void request_board_list(void) { - struct work *work; - - work = malloc(sizeof(*work)); - work->fn = list_boards_fn; - - list_add(&work_items, &work->node); -} - -struct board_info_request { - struct work work; - const char *board; -}; - -static void board_info_fn(struct work *work, int ssh_stdin) -{ - struct board_info_request *board = container_of(work, struct board_info_request, work); - int ret; - - ret = cdba_send_buf(ssh_stdin, MSG_BOARD_INFO, - strlen(board->board) + 1, - board->board); - if (ret < 0) - err(1, "failed to send board info request"); - - free(work); + cdba_queue(MSG_LIST_DEVICES); } +/** + * request_board_info() - Queue a request for a specific "board" + * @board: identifier of the board + * + * Note that @board is assumed to be alive until the message has been queued, + * and will not be freed. + */ static void request_board_info(const char *board) { - struct board_info_request *work; - - work = malloc(sizeof(*work)); - work->work.fn = board_info_fn; - work->board = board; - - list_add(&work_items, &work->work.node); -} - -struct select_board { - struct work work; - - const char *board; -}; - -static void select_board_fn(struct work *work, int ssh_stdin) -{ - struct select_board *board = container_of(work, struct select_board, work); - int ret; - - ret = cdba_send_buf(ssh_stdin, MSG_SELECT_BOARD, - strlen(board->board) + 1, - board->board); - if (ret < 0) - err(1, "failed to send power on request"); - - free(work); + cdba_queue_data(MSG_BOARD_INFO, strlen(board) + 1, board); } +/** + * request_select_board() - Queue a request for a specific "board" + * @board: identifier of the board + * + * Note that @board is assumed to be alive until the message has been queued, + * and will not be freed. + */ static void request_select_board(const char *board) { - struct select_board *work; - - work = malloc(sizeof(*work)); - work->work.fn = select_board_fn; - work->board = board; - - list_add(&work_items, &work->work.node); + cdba_queue_data(MSG_SELECT_BOARD, strlen(board) + 1, board); } -static void request_power_on_fn(struct work *work, int ssh_stdin) +/** + * request_power_on() - Queue a request to power on the selected board + */ +static void request_power_on(void) { - int ret; + uint8_t mode; - ret = cdba_send(ssh_stdin, MSG_POWER_ON); - if (ret < 0) - err(1, "failed to send power on request"); -} - -static void request_power_off_fn(struct work *work, int ssh_stdin) -{ - int ret; + if (edl_pending) + mode = MSG_POWER_ON_EDL; + else if (fastboot_file) + mode = MSG_POWER_ON_FASTBOOT; + else + mode = MSG_POWER_ON_NORMAL; - ret = cdba_send(ssh_stdin, MSG_POWER_OFF); - if (ret < 0) - err(1, "failed to send power off request"); + cdba_queue_data(MSG_POWER_ON, 1, &mode); } -static void request_power_on(void) +/** + * request_power_off() - Queue a request to power off the selected board + */ +static void request_power_off(void) { - static struct work work = { request_power_on_fn }; - - list_add(&work_items, &work.node); + cdba_queue(MSG_POWER_OFF); } -static void request_power_off(void) +/** + * request_fastboot_continue() - Queue a request to issue a fastboot continue + */ +static void request_fastboot_continue(void) { - static struct work work = { request_power_off_fn }; - - list_add(&work_items, &work.node); + cdba_queue(MSG_FASTBOOT_CONTINUE); } -static void request_fastboot_continue_fn(struct work *work, int ssh_stdin) +/** + * request_fastboot_files() - Queue the fastboot download (and boot) of fastboot_file + */ +static void request_fastboot_files(void) { - int ret; + struct stat sb; + size_t offset; + size_t len; + int fd; - ret = cdba_send(ssh_stdin, MSG_FASTBOOT_CONTINUE); - if (ret < 0) - err(1, "failed to send fastboot continue request"); -} + fd = open(fastboot_file, O_RDONLY); + if (fd < 0) + err(1, "failed to open \"%s\"", fastboot_file); -static void request_fastboot_continue(void) -{ - static struct work work = { request_fastboot_continue_fn }; + fstat(fd, &sb); - list_add(&work_items, &work.node); + for (offset = 0; offset < sb.st_size; offset += TX_DATA_CHUNK_SIZE) { + len = MIN(TX_DATA_CHUNK_SIZE, sb.st_size - offset); + cdba_queue_fd(MSG_FASTBOOT_DOWNLOAD, len, fd); + } + cdba_queue_fd(MSG_FASTBOOT_DOWNLOAD, 0, fd); } -struct fastboot_download_work { - struct work work; - - void *data; +static void edl_submit_one(struct edl_file *edl) +{ + struct stat sb; size_t offset; - size_t size; -}; + size_t len; + int fd; -static void fastboot_work_fn(struct work *_work, int ssh_stdin) -{ - struct fastboot_download_work *work = container_of(_work, struct fastboot_download_work, work); - ssize_t left; - int ret; + fd = open(edl->filename, O_RDONLY); + if (fd < 0) + err(1, "failed to open \"%s\" for EDL flashing", edl->filename); - left = MIN(2048, work->size - work->offset); + fstat(fd, &sb); - ret = cdba_send_buf(ssh_stdin, MSG_FASTBOOT_DOWNLOAD, - left, - (char *)work->data + work->offset); - if (ret < 0 && errno == EAGAIN) { - list_add(&work_items, &_work->node); - return; - } else if (ret < 0) { - err(1, "failed to write fastboot message"); + for (offset = 0; offset < sb.st_size; offset += TX_DATA_CHUNK_SIZE) { + len = MIN(TX_DATA_CHUNK_SIZE, sb.st_size - offset); + cdba_queue_fd(MSG_EDL_DOWNLOAD, len, fd); } + cdba_queue_fd(MSG_EDL_DOWNLOAD, 0, fd); - work->offset += left; - - /* We've sent the entire image, and a zero length packet */ - if (!left) - free(work); - else - list_add(&work_items, &_work->node); + cdba_queue_data(MSG_EDL_FLASH, strlen(edl->target) + 1, edl->target); } -static void request_fastboot_files(void) +static void handle_edl_present(uint8_t present) { - struct fastboot_download_work *work; - struct stat sb; - int fd; + struct edl_file *edl; - work = calloc(1, sizeof(*work)); - work->work.fn = fastboot_work_fn; + if (present) { + // printf("======================================== MSG_EDL_PRESENT(yes)\n"); - fd = open(fastboot_file, O_RDONLY); - if (fd < 0) - err(1, "failed to open \"%s\"", fastboot_file); + if (!edl_pending) { + fprintf(stderr, "device entered EDL unexpectedly, do we have a ramdump?\n"); + quit = true; + return; + } - fstat(fd, &sb); + list_for_each_entry(edl, &edl_files, node) + edl_submit_one(edl); - work->size = sb.st_size; - work->data = malloc(work->size); - read(fd, work->data, work->size); - close(fd); + cdba_queue(MSG_EDL_RESET); - list_add(&work_items, &work->work.node); + edl_pending = false; + } else { + // printf("======================================== MSG_EDL_PRESENT(no)\n"); + } } static void handle_status_update(const void *data, size_t len) @@ -433,16 +438,8 @@ static void handle_status_update(const void *data, size_t len) write(status_fd, data, len); } -static void status_enable_fn(struct work *work, int ssh_stdin) -{ - cdba_send(ssh_stdin, MSG_STATUS_UPDATE); - - free(work); -} - static void status_pipe_open(const char *path) { - struct work *work; int ret; int fd; @@ -456,11 +453,8 @@ static void status_pipe_open(const char *path) status_fd = fd; - /* Queue a MSG_STATUS_UPDATE request */ - work = malloc(sizeof(*work)); - work->fn = status_enable_fn; - - list_add(&work_items, &work->node); + /* Queue a MSG_STATUS_UPDATE request to start the status flow */ + cdba_queue(MSG_STATUS_UPDATE); } static void handle_list_devices(const void *data, size_t len) @@ -569,6 +563,9 @@ static int handle_message(struct circ_buf *buf) } } break; + case MSG_EDL_PRESENT: + handle_edl_present(*(uint8_t *)msg->data); + break; case MSG_FASTBOOT_DOWNLOAD: // printf("======================================== MSG_FASTBOOT_DOWNLOAD\n"); fastboot_done = true; @@ -644,8 +641,7 @@ int main(int argc, char **argv) const char *status_pipe = NULL; int timeout_inactivity = 0; int timeout_total = 600; - struct work *next; - struct work *work; + struct tx_item *tx_item; struct circ_buf recv_buf = { }; const char *board = NULL; const char *host = NULL; @@ -704,6 +700,22 @@ int main(int argc, char **argv) switch (verb) { case CDBA_BOOT: + while (optind < argc && strcmp(argv[optind], "write") == 0) { + struct edl_file *edl; + + if (optind + 3 > argc) + usage(); + + edl = calloc(1, sizeof(*edl)); + edl->target = argv[optind + 1]; + edl->filename = argv[optind + 2]; + + list_append(&edl_files, &edl->node); + + optind += 3; + edl_pending = true; + } + if (optind > argc || !board) usage(); @@ -775,7 +787,7 @@ int main(int argc, char **argv) } FD_ZERO(&wfds); - if (!list_empty(&work_items)) + if (!list_empty(&tx_queue)) FD_SET(ssh_fds[0], &wfds); if (timeout) { @@ -844,10 +856,14 @@ int main(int argc, char **argv) } if (FD_ISSET(ssh_fds[0], &wfds)) { - list_for_each_entry_safe(work, next, &work_items, node) { - list_del(&work->node); - - work->fn(work, ssh_fds[0]); + if (!list_empty(&tx_queue)) { + tx_item = list_entry_first(&tx_queue, struct tx_item, node); + n = cdba_tx_one(ssh_fds[0], tx_item); + if (n < 0) + err(1, "failed to write to SSH pipe"); + + list_del(&tx_item->node); + free(tx_item); } } } diff --git a/cdba.h b/cdba.h index e70c998..fe6c38d 100644 --- a/cdba.h +++ b/cdba.h @@ -32,6 +32,10 @@ enum { MSG_BOARD_INFO, MSG_FASTBOOT_CONTINUE, MSG_KEY_PRESS, + MSG_EDL_PRESENT, + MSG_EDL_DOWNLOAD, + MSG_EDL_FLASH, + MSG_EDL_RESET, }; struct key_press { @@ -42,6 +46,7 @@ struct key_press { enum { DEVICE_KEY_FASTBOOT, DEVICE_KEY_POWER, + DEVICE_KEY_EDL, DEVICE_KEY_COUNT }; @@ -51,4 +56,11 @@ enum { KEY_PRESS_PULSE, }; +enum power_on_mode { + MSG_POWER_ON_NORMAL, + MSG_POWER_ON_FASTBOOT, + MSG_POWER_ON_EDL, + MSG_POWER_ON_COUNT +}; + #endif diff --git a/device.c b/device.c index 55b77e6..079d170 100644 --- a/device.c +++ b/device.c @@ -19,6 +19,7 @@ #include "cdba-server.h" #include "device.h" +#include "edl.h" #include "fastboot.h" #include "list.h" #include "ppps.h" @@ -43,7 +44,7 @@ static struct list_head devices = LIST_INIT(devices); void device_add(struct device *device) { - list_add(&devices, &device->node); + list_append(&devices, &device->node); } static void device_lock(struct device *device) @@ -100,8 +101,6 @@ static bool device_check_access(struct device *device, return false; } -static int device_power_off(struct device *device); - struct device *device_open(const char *board, const char *username) { @@ -175,6 +174,7 @@ enum { DEVICE_STATE_PRESS, DEVICE_STATE_RELEASE_PWR, DEVICE_STATE_RELEASE_FASTBOOT, + DEVICE_STATE_RELEASE_EDL, DEVICE_STATE_RUNNING, }; @@ -185,7 +185,9 @@ static void device_tick(void *data) switch (device->state) { case DEVICE_STATE_START: /* Make sure power key is not engaged */ - if (device->fastboot_key_timeout) + if (device->power_on_mode == MSG_POWER_ON_EDL) + device_key(device, DEVICE_KEY_EDL, true); + else if (device->fastboot_key_timeout) device_key(device, DEVICE_KEY_FASTBOOT, true); if (device->has_power_key) device_key(device, DEVICE_KEY_POWER, false); @@ -201,6 +203,9 @@ static void device_tick(void *data) if (device->has_power_key) { device->state = DEVICE_STATE_PRESS; watch_timer_add(250, device_tick, device); + } else if (device->power_on_mode == MSG_POWER_ON_EDL) { + device->state = DEVICE_STATE_RELEASE_EDL; + watch_timer_add(device->fastboot_key_timeout * 1000, device_tick, device); } else if (device->fastboot_key_timeout) { device->state = DEVICE_STATE_RELEASE_FASTBOOT; watch_timer_add(device->fastboot_key_timeout * 1000, device_tick, device); @@ -219,13 +224,20 @@ static void device_tick(void *data) /* Release power key */ device_key(device, DEVICE_KEY_POWER, false); - if (device->fastboot_key_timeout) { + if (device->power_on_mode == MSG_POWER_ON_EDL) { + device->state = DEVICE_STATE_RELEASE_EDL; + watch_timer_add(device->fastboot_key_timeout * 1000, device_tick, device); + } else if (device->fastboot_key_timeout) { device->state = DEVICE_STATE_RELEASE_FASTBOOT; watch_timer_add(device->fastboot_key_timeout * 1000, device_tick, device); } else { device->state = DEVICE_STATE_RUNNING; } break; + case DEVICE_STATE_RELEASE_EDL: + device_key(device, DEVICE_KEY_EDL, false); + device->state = DEVICE_STATE_RUNNING; + break; case DEVICE_STATE_RELEASE_FASTBOOT: device_key(device, DEVICE_KEY_FASTBOOT, false); device->state = DEVICE_STATE_RUNNING; @@ -238,18 +250,19 @@ bool device_is_running(struct device *device) return device->state == DEVICE_STATE_RUNNING; } -static int device_power_on(struct device *device) +int device_power_on(struct device *device, enum power_on_mode mode) { if (!device || !device_has_control(device, power)) return 0; + device->power_on_mode = mode; device->state = DEVICE_STATE_START; device_tick(device); return 0; } -static int device_power_off(struct device *device) +int device_power_off(struct device *device) { if (!device || !device_has_control(device, power)) return 0; @@ -259,14 +272,6 @@ static int device_power_off(struct device *device) return 0; } -int device_power(struct device *device, bool on) -{ - if (on) - return device_power_on(device); - else - return device_power_off(device); -} - void device_status_enable(struct device *device) { if (device->status_enabled) @@ -297,6 +302,11 @@ int device_write(struct device *device, const void *buf, size_t len) return device_console(device, write, buf, len); } +void device_edl_open(struct device *device, void (*edl_present)(bool present)) +{ + device->edl = edl_open(device->serial, edl_present); +} + void device_fastboot_open(struct device *device, struct fastboot_ops *fastboot_ops) { @@ -400,7 +410,7 @@ void device_close(struct device *dev) if (!dev->usb_always_on) device_usb(dev, false); if (!dev->power_always_on) - device_power(dev, false); + device_power_off(dev); if (device_has_control(dev, close)) device_control(dev, close); diff --git a/device.h b/device.h index e465ea5..6cc2c17 100644 --- a/device.h +++ b/device.h @@ -6,6 +6,7 @@ #include "list.h" struct cdb_assist; +struct edl; struct fastboot; struct fastboot_ops; struct device; @@ -43,9 +44,11 @@ struct device { bool tickle_mmc; bool usb_always_on; bool power_always_on; + struct edl *edl; struct fastboot *fastboot; unsigned int fastboot_key_timeout; int state; + enum power_on_mode power_on_mode; bool has_power_key; bool status_enabled; @@ -76,7 +79,8 @@ void device_add(struct device *device); struct device *device_open(const char *board, const char *username); void device_close(struct device *dev); -int device_power(struct device *device, bool on); +int device_power_on(struct device *device, enum power_on_mode mode); +int device_power_off(struct device *device); void device_key(struct device *device, int key, bool asserted); void device_status_enable(struct device *device); @@ -85,6 +89,7 @@ int device_write(struct device *device, const void *buf, size_t len); void device_boot(struct device *device, const void *data, size_t len); +void device_edl_open(struct device *device, void (*edl_present)(bool present)); void device_fastboot_open(struct device *device, struct fastboot_ops *fastboot_ops); void device_fastboot_boot(struct device *device); diff --git a/device_parser.c b/device_parser.c index ce8e6f3..a0dd272 100644 --- a/device_parser.c +++ b/device_parser.c @@ -129,7 +129,7 @@ static void parse_board(struct device_parser *dp) user->username = strdup(key); - list_add(dev->users, &user->node); + list_append(dev->users, &user->node); } device_parser_expect(dp, YAML_SEQUENCE_END_EVENT, NULL, 0); diff --git a/drivers/ftdi-gpio.c b/drivers/ftdi-gpio.c index 8c0bb7a..070b3d3 100644 --- a/drivers/ftdi-gpio.c +++ b/drivers/ftdi-gpio.c @@ -34,6 +34,7 @@ enum { GPIO_USB0_DISCONNECT, // Simulate main USB connection GPIO_USB1_DISCONNECT, // Simulate secondary USB connection GPIO_OUTPUT_ENABLE, // Enable FTDI signals to flow to the board + GPIO_EDL_KEY, // Hold while power on to enter flashing mode GPIO_COUNT }; @@ -190,6 +191,8 @@ void *ftdi_gpio_parse_options(struct device_parser *dp) gpio_id = GPIO_USB1_DISCONNECT; } else if (!strcmp(key, "output_enable")) { gpio_id = GPIO_OUTPUT_ENABLE; + } else if (!strcmp(key, "edl")) { + gpio_id = GPIO_EDL_KEY; } else { if (!device_parser_accept(dp, YAML_SCALAR_EVENT, value, TOKEN_LENGTH)) errx(1, "%s: expected value for \"%s\"", __func__, key); @@ -391,6 +394,9 @@ static void ftdi_gpio_key(struct device *dev, int key, bool asserted) case DEVICE_KEY_POWER: ftdi_gpio_toggle_io(ftdi_gpio, GPIO_POWER_KEY, asserted); break; + case DEVICE_KEY_EDL: + ftdi_gpio_toggle_io(ftdi_gpio, GPIO_EDL_KEY, asserted); + break; } } diff --git a/edl.c b/edl.c new file mode 100644 index 0000000..c0282d8 --- /dev/null +++ b/edl.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edl.h" +#include "watch.h" + +#define MAX_USBFS_BULK_SIZE (16*1024) + +enum { + EDL_STATE_START, + EDL_STATE_OPENED, + EDL_STATE_CLOSED, +}; + +struct edl { + const char *serial; + const char *dev_path; + + void *data; + + struct edl_ops *ops; + void (*present)(bool present); + + int state; + + struct udev_monitor *mon; +}; + +static int parse_usb_desc(int usbfd) +{ + const struct usb_interface_descriptor *ifc; + const struct usb_endpoint_descriptor *ept; + const struct usb_device_descriptor *dev; + const struct usb_config_descriptor *cfg; + const struct usb_descriptor_header *hdr; + unsigned type; + unsigned k; + unsigned l; + ssize_t n; + char *ptr; + char *end; + char desc[1024]; + + n = read(usbfd, desc, sizeof(desc)); + if (n < 0) + return n; + + ptr = desc; + end = ptr + n; + + dev = (void *)ptr; + ptr += dev->bLength; + if (ptr >= end || dev->bDescriptorType != USB_DT_DEVICE) + return -EINVAL; + + if (dev->idVendor != 0x05c6) + return -ENOENT; + if (dev->idProduct != 0x9008) + return -ENOENT; + + cfg = (void *)ptr; + ptr += cfg->bLength; + if (ptr >= end || cfg->bDescriptorType != USB_DT_CONFIG) + return -EINVAL; + + for (k = 0; k < cfg->bNumInterfaces; k++) { + if (ptr >= end) + return -EINVAL; + + do { + ifc = (void *)ptr; + if (ifc->bLength < USB_DT_INTERFACE_SIZE) + return -EINVAL; + + ptr += ifc->bLength; + } while (ptr < end && ifc->bDescriptorType != USB_DT_INTERFACE); + + for (l = 0; l < ifc->bNumEndpoints; l++) { + if (ptr >= end) + return -EINVAL; + + do { + ept = (void *)ptr; + if (ept->bLength < USB_DT_ENDPOINT_SIZE) + return -EINVAL; + + ptr += ept->bLength; + } while (ptr < end && ept->bDescriptorType != USB_DT_ENDPOINT); + + type = ept->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (type != USB_ENDPOINT_XFER_BULK) + continue; + + if (ptr >= end) + break; + + hdr = (void *)ptr; + if (hdr->bDescriptorType == USB_DT_SS_ENDPOINT_COMP) + ptr += USB_DT_SS_EP_COMP_SIZE; + } + + if (ifc->bInterfaceClass != 0xff) + continue; + + if (ifc->bInterfaceSubClass != 0xff) + continue; + + if (ifc->bInterfaceProtocol != 0xff && + ifc->bInterfaceProtocol != 16 && + ifc->bInterfaceProtocol != 17) + continue; + + /* TODO: check serial number */ + + return 0; + } + + return -ENOENT; +} + +static int handle_edl_add(struct edl *edl, struct udev_device *dev) +{ + const char *dev_path; + const char *dev_node; + int usbfd; + int ret; + + dev_path = udev_device_get_devpath(dev); + dev_node = udev_device_get_devnode(dev); + + usbfd = open(dev_node, O_RDWR); + if (usbfd < 0) + return usbfd; + + ret = parse_usb_desc(usbfd); + if (ret < 0) { + close(usbfd); + return ret; + } + + edl->dev_path = strdup(dev_path); + + if (edl->present) + edl->present(true); + + return 0; +} + +static int handle_udev_event(int fd, void *data) +{ + struct edl *edl = data; + struct udev_device* dev; + const char *dev_path; + const char *action; + + dev = udev_monitor_receive_device(edl->mon); + + action = udev_device_get_action(dev); + dev_path = udev_device_get_devpath(dev); + + if (!action || !dev_path) + goto unref_dev; + + if (!strcmp(action, "add")) { + handle_edl_add(edl, dev); + } else if (!strcmp(action, "remove")) { + if (!edl->dev_path || strcmp(dev_path, edl->dev_path)) + goto unref_dev; + + edl->dev_path = NULL; + + if (edl->present) + edl->present(false); + } + +unref_dev: + udev_device_unref(dev); + + return 0; +} + +struct edl *edl_open(const char *serial, void (*present)(bool present)) +{ + struct edl *edl; + struct udev* udev; + int fd; + struct udev_enumerate* udev_enum; + struct udev_list_entry* first, *item; + + udev = udev_new(); + if (!udev) + err(1, "udev_new() failed"); + + edl = calloc(1, sizeof(struct edl)); + if (!edl) + err(1, "failed to allocate edl structure"); + + edl->serial = serial; + edl->present = present; + + edl->mon = udev_monitor_new_from_netlink(udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(edl->mon, "usb", NULL); + udev_monitor_enable_receiving(edl->mon); + + fd = udev_monitor_get_fd(edl->mon); + + watch_add_readfd(fd, handle_udev_event, edl); + + udev_enum = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(udev_enum, "usb"); + udev_enumerate_scan_devices(udev_enum); + + first = udev_enumerate_get_list_entry(udev_enum); + udev_list_entry_foreach(item, first) { + const char *path; + struct udev_device *dev; + + path = udev_list_entry_get_name(item); + dev = udev_device_new_from_syspath(udev, path); + handle_edl_add(edl, dev); + } + + udev_enumerate_unref(udev_enum); + + return edl; +} diff --git a/edl.h b/edl.h new file mode 100644 index 0000000..70c3632 --- /dev/null +++ b/edl.h @@ -0,0 +1,11 @@ +#ifndef __EDL_H__ +#define __EDL_H__ + +#include + +struct edl; + +struct edl *edl_open(const char *serial, void (*edl_present)(bool present)); + +#endif + diff --git a/list.h b/list.h index c6930c4..99493a4 100644 --- a/list.h +++ b/list.h @@ -22,6 +22,33 @@ struct list_head { #define LIST_INIT(list) { &(list), &(list) } +#define list_for_each(item, list) \ + for (item = (list)->next; item != list; item = item->next) + +#define list_for_each_safe(item, next, list) \ + for (item = (list)->next, next = item->next; item != list; item = next, next = item->next) + +#define list_entry(item, type, member) \ + container_of(item, type, member) + +#define list_entry_first(list, type, member) \ + container_of((list)->next, type, member) + +#define list_entry_next(item, member) \ + container_of((item)->member.next, typeof(*(item)), member) + +#define list_for_each_entry(item, list, member) \ + for (item = list_entry_first(list, typeof(*(item)), member); \ + &item->member != list; \ + item = list_entry_next(item, member)) + +#define list_for_each_entry_safe(item, next, list, member) \ + for (item = list_entry_first(list, typeof(*(item)), member), \ + next = list_entry_next(item, member); \ + &item->member != list; \ + item = next, \ + next = list_entry_next(item, member)) \ + static inline void list_init(struct list_head *list) { list->prev = list->next = list; @@ -32,7 +59,7 @@ static inline bool list_empty(struct list_head *list) return list->next == list; } -static inline void list_add(struct list_head *list, struct list_head *item) +static inline void list_append(struct list_head *list, struct list_head *item) { struct list_head *prev = list->prev; @@ -42,37 +69,31 @@ static inline void list_add(struct list_head *list, struct list_head *item) prev->next = list->prev = item; } +static inline void list_prepend(struct list_head *list, struct list_head *item) +{ + struct list_head *next = list->prev; + + item->next = next; + item->prev = list; + + list->next = next->prev = item; +} + static inline void list_del(struct list_head *item) { item->prev->next = item->next; item->next->prev = item->prev; } -#define list_for_each(item, list) \ - for (item = (list)->next; item != list; item = item->next) - -#define list_for_each_safe(item, next, list) \ - for (item = (list)->next, next = item->next; item != list; item = next, next = item->next) - -#define list_entry(item, type, member) \ - container_of(item, type, member) - -#define list_entry_first(list, type, member) \ - container_of((list)->next, type, member) - -#define list_entry_next(item, member) \ - container_of((item)->member.next, typeof(*(item)), member) +static inline size_t list_len(struct list_head *list) +{ + struct list_head *it; + size_t n = 0; -#define list_for_each_entry(item, list, member) \ - for (item = list_entry_first(list, typeof(*(item)), member); \ - &item->member != list; \ - item = list_entry_next(item, member)) + list_for_each(it, list) + n++; -#define list_for_each_entry_safe(item, next, list, member) \ - for (item = list_entry_first(list, typeof(*(item)), member), \ - next = list_entry_next(item, member); \ - &item->member != list; \ - item = next, \ - next = list_entry_next(item, member)) \ + return n; +} #endif diff --git a/meson.build b/meson.build index c9e22ee..50e48a7 100644 --- a/meson.build +++ b/meson.build @@ -84,6 +84,7 @@ endif cdbalib_srcs = ['circ_buf.c', 'device.c', 'device_parser.c', + 'edl.c', 'fastboot.c', 'console.c', 'ppps.c', diff --git a/watch.c b/watch.c index 65755f4..16fbd70 100644 --- a/watch.c +++ b/watch.c @@ -51,7 +51,7 @@ void watch_add_readfd(int fd, int (*cb)(int, void*), void *data) w->cb = cb; w->data = data; - list_add(&read_watches, &w->node); + list_append(&read_watches, &w->node); } void watch_timer_add(int timeout_ms, void (*cb)(void *), void *data) @@ -71,7 +71,7 @@ void watch_timer_add(int timeout_ms, void (*cb)(void *), void *data) t->data = data; timeradd(&now, &tv_timeout, &t->tv); - list_add(&timer_watches, &t->node); + list_append(&timer_watches, &t->node); } static struct timeval *watch_timer_next(void)