From fc100a644225d53cd4e1e6c64c92940102020343 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 9 Apr 2026 11:06:13 -0500 Subject: [PATCH] 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_WRITE: 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_WRITE 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 | 132 +++++++++++++++++++ cdba.h | 4 + config-samples/sample13.yaml | 13 ++ device.c | 6 + device.h | 7 + device_parser.c | 6 + edl.c | 243 +++++++++++++++++++++++++++++++++++ edl.h | 11 ++ meson.build | 1 + schema.yaml | 12 ++ 10 files changed, 435 insertions(+) create mode 100644 config-samples/sample13.yaml create mode 100644 edl.c create mode 100644 edl.h diff --git a/cdba-server.c b/cdba-server.c index 27ea571..9851630 100644 --- a/cdba-server.c +++ b/cdba-server.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,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; @@ -61,6 +82,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); } @@ -94,6 +116,107 @@ 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; + } + + list_for_each_entry(edl, &edl_files, node) { + if (!edl->target) { + fprintf(stderr, "EDL image without flash target\n"); + 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"); + } + wait(NULL); + + list_for_each_entry(edl, &edl_files, node) + unlink(edl->filename); +} + static void msg_fastboot_continue(void) { device_fastboot_continue(selected_device); @@ -222,6 +345,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_WRITE: + 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..7e7e4e1 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_WRITE, + MSG_EDL_RESET, }; struct key_press { diff --git a/config-samples/sample13.yaml b/config-samples/sample13.yaml new file mode 100644 index 0000000..680c154 --- /dev/null +++ b/config-samples/sample13.yaml @@ -0,0 +1,13 @@ +--- +devices: + - board: myboard + name: "My Board" + description: | + My Awesome board with QDL flashing support + console: /dev/ttyABC0 + fastboot: cacafada + fastboot_set_active: true + fastboot_key_timeout: 2 + qdl_programmer: /path/to/prog_firehose_ddr.elf + qdl_serial: 1234567890ABCDEF + qdl_storage: ufs diff --git a/device.c b/device.c index d27dd1d..6f12989 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" @@ -306,6 +307,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 8131778..3d5bedc 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; unsigned int power_key_press_ms; @@ -62,6 +64,10 @@ struct device { void *cdb; void *console; + char *qdl_programmer; + char *qdl_serial; + char *qdl_storage; + char *status_cmd; struct list_head node; @@ -88,6 +94,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 978a43c..d61a98b 100644 --- a/device_parser.c +++ b/device_parser.c @@ -215,6 +215,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..cd9c7ad 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', diff --git a/schema.yaml b/schema.yaml index 65976e7..5e006e4 100644 --- a/schema.yaml +++ b/schema.yaml @@ -82,6 +82,18 @@ properties: description: Command to execute for generating status updates type: string + qdl_programmer: + description: path to the firehose programmer to use for QDL flashing + type: string + + qdl_serial: + description: serial number of the EDL device to use for QDL flashing + type: string + + qdl_storage: + description: storage type to pass to QDL when flashing + type: string + qcomlt_debug_board: description: Qlt Debug Board control tty device path $ref: "#/$defs/device_path"