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"