From 10ad47d4ad7cdbf8a72c76ea84a4759df7b04edb Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 7 Jan 2026 14:43:28 -0600 Subject: [PATCH 1/7] list: Clarify list_add(), add list_prepend() and list_len() Moving between the Linux kernel's list.h and this one is confusing, as list_add() prepends in one case and appends in the other. Rename the function to clarify the API. Introduce an operation for those cases where a prepend is desired. Also introduce a list_len() operation, which can be useful to save clients from open-coding this. Signed-off-by: Bjorn Andersson --- cdba.c | 20 +++++++------- device.c | 2 +- device_parser.c | 2 +- list.h | 71 ++++++++++++++++++++++++++++++++----------------- watch.c | 4 +-- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/cdba.c b/cdba.c index 6c5dd3f..14b7655 100644 --- a/cdba.c +++ b/cdba.c @@ -255,7 +255,7 @@ static void request_board_list(void) work = malloc(sizeof(*work)); work->fn = list_boards_fn; - list_add(&work_items, &work->node); + list_append(&work_items, &work->node); } struct board_info_request { @@ -285,7 +285,7 @@ static void request_board_info(const char *board) work->work.fn = board_info_fn; work->board = board; - list_add(&work_items, &work->work.node); + list_append(&work_items, &work->work.node); } struct select_board { @@ -316,7 +316,7 @@ static void request_select_board(const char *board) work->work.fn = select_board_fn; work->board = board; - list_add(&work_items, &work->work.node); + list_append(&work_items, &work->work.node); } static void request_power_on_fn(struct work *work, int ssh_stdin) @@ -341,14 +341,14 @@ static void request_power_on(void) { static struct work work = { request_power_on_fn }; - list_add(&work_items, &work.node); + list_append(&work_items, &work.node); } static void request_power_off(void) { static struct work work = { request_power_off_fn }; - list_add(&work_items, &work.node); + list_append(&work_items, &work.node); } static void request_fastboot_continue_fn(struct work *work, int ssh_stdin) @@ -364,7 +364,7 @@ static void request_fastboot_continue(void) { static struct work work = { request_fastboot_continue_fn }; - list_add(&work_items, &work.node); + list_append(&work_items, &work.node); } struct fastboot_download_work { @@ -387,7 +387,7 @@ static void fastboot_work_fn(struct work *_work, int ssh_stdin) left, (char *)work->data + work->offset); if (ret < 0 && errno == EAGAIN) { - list_add(&work_items, &_work->node); + list_append(&work_items, &_work->node); return; } else if (ret < 0) { err(1, "failed to write fastboot message"); @@ -399,7 +399,7 @@ static void fastboot_work_fn(struct work *_work, int ssh_stdin) if (!left) free(work); else - list_add(&work_items, &_work->node); + list_append(&work_items, &_work->node); } static void request_fastboot_files(void) @@ -422,7 +422,7 @@ static void request_fastboot_files(void) read(fd, work->data, work->size); close(fd); - list_add(&work_items, &work->work.node); + list_append(&work_items, &work->work.node); } static void handle_status_update(const void *data, size_t len) @@ -460,7 +460,7 @@ static void status_pipe_open(const char *path) work = malloc(sizeof(*work)); work->fn = status_enable_fn; - list_add(&work_items, &work->node); + list_append(&work_items, &work->node); } static void handle_list_devices(const void *data, size_t len) diff --git a/device.c b/device.c index 55b77e6..6d387e3 100644 --- a/device.c +++ b/device.c @@ -43,7 +43,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) diff --git a/device_parser.c b/device_parser.c index ea05712..d2c6467 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/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/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) From 8d71bacdab4ea461dfccc1fd0f636a34819427c0 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 7 Jan 2026 21:12:05 -0600 Subject: [PATCH 2/7] cdba-server: Allow booting into EDL mode In order to implement flash support, as well as aid in maintenance situations, support for requesting the board to be powered on into EDL/flashing mode is useful. So far the payload of the MSG_POWER_ON message has been ignored by the server, so we can tread the absence of payload as "NORMAL" power on request and introduce the power_on_mode enumeration to define the values in this (optional) byte. A new key definition is also added to the protocol, representing the holding down the EDL "key", but not exposed on the client side yet. The device logic is wired up such that the power-on sequence will now request the drivers to hold the EDL key while powering on, if the power-on mode is "EDL". Signed-off-by: Bjorn Andersson --- cdba-power.c | 4 ++-- cdba-server.c | 15 +++++++++++++-- cdba.h | 8 ++++++++ device.c | 34 +++++++++++++++++++--------------- device.h | 4 +++- 5 files changed, 45 insertions(+), 20 deletions(-) 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..360cde3 100644 --- a/cdba-server.c +++ b/cdba-server.c @@ -138,6 +138,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 +172,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; diff --git a/cdba.h b/cdba.h index e70c998..fcd0c76 100644 --- a/cdba.h +++ b/cdba.h @@ -42,6 +42,7 @@ struct key_press { enum { DEVICE_KEY_FASTBOOT, DEVICE_KEY_POWER, + DEVICE_KEY_EDL, DEVICE_KEY_COUNT }; @@ -51,4 +52,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 6d387e3..7b45358 100644 --- a/device.c +++ b/device.c @@ -100,8 +100,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 +173,7 @@ enum { DEVICE_STATE_PRESS, DEVICE_STATE_RELEASE_PWR, DEVICE_STATE_RELEASE_FASTBOOT, + DEVICE_STATE_RELEASE_EDL, DEVICE_STATE_RUNNING, }; @@ -185,7 +184,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 +202,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 +223,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 +249,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 +271,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) @@ -400,7 +404,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 773e115..57d3e2a 100644 --- a/device.h +++ b/device.h @@ -46,6 +46,7 @@ struct device { 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 +77,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); From c2544fadb6104ef1a03eec4f3342057c95c856fd Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 9 Apr 2026 16:38:16 +0000 Subject: [PATCH 3/7] drivers: ftdi-gpio: Implement support for EDL key When the EDL key is held during power up, the device will enter EDL mode, making the board ready for firmware flashing. Signed-off-by: Bjorn Andersson --- drivers/ftdi-gpio.c | 6 ++++++ 1 file changed, 6 insertions(+) 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; } } From 72914b791295816bfc0a260f76f2d84f67a5f8bc Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 9 Apr 2026 11:06:13 -0500 Subject: [PATCH 4/7] cdba-server: Add support for flashing, using QDL The client can now request the device to enter EDL mode and the server's device and driver model can power up the board to this state. Introduce four new messages to the cdba protocol: - EDL_PRESENT: used by the server to signal the presence of the EDL device - EDL_DOWNLOAD: used by the client to transfer a binary blob to the server, mimicing the fastboot protocol - EDL_FLASH: used by the client to indicate the target for the downloaded binary blob. The server may start flashing the image if supported. - EDL_RESET: used by the client to request that the flash operations are finalized and the board restarts. Introduce a new "EDL" module that will look for EDL USB devices and if found signal the client about its presence. Implement EDL_DOWNLOAD by streaming the incoming blob into a temporary file, let EDL_FLASH tag the image with information about where it should be written. Then as we're relying on QDL, invoke the external tool as part of the EDL_RESET, "flushing out" the pending operations. Support for matching EDL device by serial number is left as a todo item. Signed-off-by: Bjorn Andersson --- cdba-server.c | 124 ++++++++++++++++++++++++ cdba.h | 4 + device.c | 6 ++ device.h | 7 ++ device_parser.c | 6 ++ edl.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++ edl.h | 11 +++ meson.build | 1 + 8 files changed, 402 insertions(+) create mode 100644 edl.c create mode 100644 edl.h diff --git a/cdba-server.c b/cdba-server.c index 360cde3..120d84a 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,99 @@ 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"); + + if (!selected_device) { + fprintf(stderr, "no device selected\n"); + watch_quit(); + return; + } + + if (!selected_device->qdl_programmer) { + fprintf(stderr, "no EDL support configured for %s\n", selected_device->name); + watch_quit(); + return; + } + + args = 7 + list_len(&edl_files) * 3 + 1; + argv = calloc(args, sizeof(char *)); + + argv[arg++] = "qdl"; + argv[arg++] = selected_device->qdl_programmer; + + if (selected_device->qdl_storage) { + argv[arg++] = "--storage"; + argv[arg++] = selected_device->qdl_storage; + } + + if (selected_device->qdl_serial) { + argv[arg++] = "--serial"; + argv[arg++] = selected_device->qdl_serial; + } + + 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); @@ -221,6 +336,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.h b/cdba.h index fcd0c76..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 { diff --git a/device.c b/device.c index 7b45358..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" @@ -301,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) { diff --git a/device.h b/device.h index 57d3e2a..e8f1cb8 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,6 +44,7 @@ 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; @@ -61,6 +63,10 @@ struct device { void *cdb; void *console; + char *qdl_programmer; + char *qdl_serial; + char *qdl_storage; + char *status_cmd; struct list_head node; @@ -87,6 +93,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 d2c6467..4a4aadc 100644 --- a/device_parser.c +++ b/device_parser.c @@ -213,6 +213,12 @@ static void parse_board(struct device_parser *dp) dev->status_cmd = strdup(value); } else if (!strcmp(key, "power_always_on")) { dev->power_always_on = !strcmp(value, "true"); + } else if (!strcmp(key, "qdl_programmer")) { + dev->qdl_programmer = strdup(value); + } else if (!strcmp(key, "qdl_serial")) { + dev->qdl_serial = strdup(value); + } else if (!strcmp(key, "qdl_storage")) { + dev->qdl_storage = strdup(value); } else { fprintf(stderr, "device parser: unknown key \"%s\"\n", key); exit(1); 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/meson.build b/meson.build index bc39e84..ed62a49 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ endif cdbalib_srcs = ['circ_buf.c', 'device.c', 'device_parser.c', + 'edl.c', 'fastboot.c', 'console.c', 'ppps.c', From 721405033259090e2fe2629a3ab97725f0770747 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 7 Jan 2026 15:57:54 -0600 Subject: [PATCH 5/7] cdba: Clean up construction of TX messages For some unknown reason, when I wrote this I choose to build the outgoing messages in an event-based manner, queuing the operation that will produce the message, rather than just queuing the message directly. Let's simplify this by just queueing the actual messages directly and change the main loop to simply drain the queue. Signed-off-by: Bjorn Andersson --- cdba.c | 317 +++++++++++++++++++++------------------------------------ 1 file changed, 114 insertions(+), 203 deletions(-) diff --git a/cdba.c b/cdba.c index 14b7655..76c78b6 100644 --- a/cdba.c +++ b/cdba.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,8 @@ #include "circ_buf.h" #include "list.h" +#define TX_DATA_CHUNK_SIZE 2048 + static bool quit; static bool fastboot_repeat; static bool fastboot_done; @@ -33,6 +36,16 @@ static int status_fd = -1; static const char *fastboot_file; +struct tx_item { + struct list_head node; + + uint8_t type; + uint16_t len; + 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 +141,54 @@ 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; - struct msg msg = { - .type = type, - .len = len - }; + msg.type = item->type; + msg.len = item->len; - ret = write(fd, &msg, sizeof(msg)); - if (ret < 0) - return ret; + iov[0].iov_base = &msg; + iov[0].iov_len = sizeof(msg); + + iov[1].iov_base = item->payload; + iov[1].iov_len = item->len; + + 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; - if (len) - ret = write(fd, buf, len); + item = calloc(1, sizeof(*item) + len); + item->type = type; + item->len = len; + memcpy(item->payload, buf, len); - 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 +213,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 +249,99 @@ 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_append(&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_append(&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_append(&work_items, &work->work.node); -} - -static void request_power_on_fn(struct work *work, int ssh_stdin) -{ - int ret; - - 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; - - ret = cdba_send(ssh_stdin, MSG_POWER_OFF); - if (ret < 0) - err(1, "failed to send power off request"); + cdba_queue_data(MSG_SELECT_BOARD, strlen(board) + 1, board); } +/** + * request_power_on() - Queue a request to power on the selected board + */ static void request_power_on(void) { - static struct work work = { request_power_on_fn }; - - list_append(&work_items, &work.node); + cdba_queue(MSG_POWER_ON); } +/** + * request_power_off() - Queue a request to power off the selected board + */ static void request_power_off(void) { - static struct work work = { request_power_off_fn }; - - list_append(&work_items, &work.node); -} - -static void request_fastboot_continue_fn(struct work *work, int ssh_stdin) -{ - int ret; - - ret = cdba_send(ssh_stdin, MSG_FASTBOOT_CONTINUE); - if (ret < 0) - err(1, "failed to send fastboot continue request"); + cdba_queue(MSG_POWER_OFF); } +/** + * request_fastboot_continue() - Queue a request to issue a fastboot continue + */ static void request_fastboot_continue(void) { - static struct work work = { request_fastboot_continue_fn }; - - list_append(&work_items, &work.node); -} - -struct fastboot_download_work { - struct work work; - - void *data; - size_t offset; - size_t size; -}; - -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; - - left = MIN(2048, work->size - work->offset); - - ret = cdba_send_buf(ssh_stdin, MSG_FASTBOOT_DOWNLOAD, - left, - (char *)work->data + work->offset); - if (ret < 0 && errno == EAGAIN) { - list_append(&work_items, &_work->node); - return; - } else if (ret < 0) { - err(1, "failed to write fastboot message"); - } - - work->offset += left; - - /* We've sent the entire image, and a zero length packet */ - if (!left) - free(work); - else - list_append(&work_items, &_work->node); + cdba_queue(MSG_FASTBOOT_CONTINUE); } +/** + * request_fastboot_files() - Queue the fastboot download (and boot) of fastboot_file + */ static void request_fastboot_files(void) { - struct fastboot_download_work *work; struct stat sb; + size_t offset; + size_t len; + ssize_t n; + char buf[TX_DATA_CHUNK_SIZE]; int fd; - work = calloc(1, sizeof(*work)); - work->work.fn = fastboot_work_fn; - fd = open(fastboot_file, O_RDONLY); if (fd < 0) err(1, "failed to open \"%s\"", fastboot_file); fstat(fd, &sb); - work->size = sb.st_size; - work->data = malloc(work->size); - read(fd, work->data, work->size); - close(fd); + for (offset = 0; offset < sb.st_size; offset += TX_DATA_CHUNK_SIZE) { + len = MIN(TX_DATA_CHUNK_SIZE, sb.st_size - offset); + + n = read(fd, buf, len); + if (n != len) + errx(1, "failed to read fastboot payload"); + + cdba_queue_data(MSG_FASTBOOT_DOWNLOAD, len, buf); + } + cdba_queue(MSG_FASTBOOT_DOWNLOAD); - list_append(&work_items, &work->work.node); + close(fd); } static void handle_status_update(const void *data, size_t len) @@ -433,16 +352,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 +367,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_append(&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) @@ -644,8 +552,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; @@ -775,7 +682,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 +751,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); } } } From ce85084926c63f3ecc6deee724793a2477bcaef4 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 7 Jan 2026 16:13:18 -0600 Subject: [PATCH 6/7] cdba: Allow queueing file descriptors The boot payload can be massive (GBs) so queueing it upfront will consume a lot of RAM, unnecessarily. Allow the transmit queue to carry references to a file descriptor instead of the actual payload. As the queue is consumed each chunk is read from the file and transmitted. As we use zero-length messages to represent EOF, let's use that as our signal for closing the file descriptor as well. Signed-off-by: Bjorn Andersson --- cdba.c | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/cdba.c b/cdba.c index 76c78b6..86351a7 100644 --- a/cdba.c +++ b/cdba.c @@ -41,6 +41,9 @@ struct tx_item { uint8_t type; uint16_t len; + + int fd; + uint8_t payload[]; }; @@ -145,6 +148,8 @@ static ssize_t cdba_tx_one(int fd, struct tx_item *item) { struct iovec iov[2]; struct msg msg; + void *buf; + ssize_t n; msg.type = item->type; msg.len = item->len; @@ -152,8 +157,20 @@ static ssize_t cdba_tx_one(int fd, struct tx_item *item) iov[0].iov_base = &msg; iov[0].iov_len = sizeof(msg); - iov[1].iov_base = item->payload; - iov[1].iov_len = item->len; + 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; + } return writev(fd, iov, item->len ? 2 : 1); } @@ -165,11 +182,24 @@ static void cdba_queue_data(int type, size_t len, const void *buf) 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; + + item = calloc(1, sizeof(*item) + len); + item->type = type; + item->len = len; + item->fd = fd; + + list_append(&tx_queue, &item->node); +} + static void cdba_queue(int type) { cdba_queue_data(type, 0, NULL); @@ -320,8 +350,6 @@ static void request_fastboot_files(void) struct stat sb; size_t offset; size_t len; - ssize_t n; - char buf[TX_DATA_CHUNK_SIZE]; int fd; fd = open(fastboot_file, O_RDONLY); @@ -332,16 +360,9 @@ static void request_fastboot_files(void) for (offset = 0; offset < sb.st_size; offset += TX_DATA_CHUNK_SIZE) { len = MIN(TX_DATA_CHUNK_SIZE, sb.st_size - offset); - - n = read(fd, buf, len); - if (n != len) - errx(1, "failed to read fastboot payload"); - - cdba_queue_data(MSG_FASTBOOT_DOWNLOAD, len, buf); + cdba_queue_fd(MSG_FASTBOOT_DOWNLOAD, len, fd); } - cdba_queue(MSG_FASTBOOT_DOWNLOAD); - - close(fd); + cdba_queue_fd(MSG_FASTBOOT_DOWNLOAD, 0, fd); } static void handle_status_update(const void *data, size_t len) From 1322ab3b4a128bebbffba9e9395a886d58ad8f9e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 7 Jan 2026 20:40:37 -0600 Subject: [PATCH 7/7] cdba: Wire up flashing support in the client With the server supporting alternative power-on mechanisms, EDL notifications and flashing support, the client interface can now be implemented. Allow a series of "write " arguments on the command line register images and their target. Request the server to power on the device into the newly supported EDL state, then as the EDL notification arrives push the images and request them to be flashed by the server. It is assumed that the flashing operations is only desired on the first discovery of a device in EDL mode at this time, as test-scenarios across reboot typically doesn't involve resetting storage content. Signed-off-by: Bjorn Andersson --- cdba.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/cdba.c b/cdba.c index 86351a7..33795fb 100644 --- a/cdba.c +++ b/cdba.c @@ -31,11 +31,21 @@ 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; @@ -323,7 +333,16 @@ static void request_select_board(const char *board) */ static void request_power_on(void) { - cdba_queue(MSG_POWER_ON); + uint8_t mode; + + if (edl_pending) + mode = MSG_POWER_ON_EDL; + else if (fastboot_file) + mode = MSG_POWER_ON_FASTBOOT; + else + mode = MSG_POWER_ON_NORMAL; + + cdba_queue_data(MSG_POWER_ON, 1, &mode); } /** @@ -365,6 +384,52 @@ static void request_fastboot_files(void) cdba_queue_fd(MSG_FASTBOOT_DOWNLOAD, 0, fd); } +static void edl_submit_one(struct edl_file *edl) +{ + struct stat sb; + size_t offset; + size_t len; + int fd; + + fd = open(edl->filename, O_RDONLY); + if (fd < 0) + err(1, "failed to open \"%s\" for EDL flashing", edl->filename); + + fstat(fd, &sb); + + 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); + + cdba_queue_data(MSG_EDL_FLASH, strlen(edl->target) + 1, edl->target); +} + +static void handle_edl_present(uint8_t present) +{ + struct edl_file *edl; + + if (present) { + // printf("======================================== MSG_EDL_PRESENT(yes)\n"); + + if (!edl_pending) { + fprintf(stderr, "device entered EDL unexpectedly, do we have a ramdump?\n"); + quit = true; + return; + } + + list_for_each_entry(edl, &edl_files, node) + edl_submit_one(edl); + + cdba_queue(MSG_EDL_RESET); + + edl_pending = false; + } else { + // printf("======================================== MSG_EDL_PRESENT(no)\n"); + } +} + static void handle_status_update(const void *data, size_t len) { if (status_fd < 0) @@ -498,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; @@ -632,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();