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 <bjorn.andersson@oss.qualcomm.com>
This commit is contained in:
Bjorn Andersson committed 2026-05-20 22:26:14 +00:00
1 parent 403e5c36ac
commit fc100a6442
10 files changed
+435

No files matched your search

+132
View File
@@ -11,6 +11,7 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <syslog.h>
@@ -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);
+4
View File
@@ -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 {
+13
View File
@@ -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
+6
View File
@@ -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)
{
+7
View File
@@ -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);
+6
View File
@@ -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);
+243
View File
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef __EDL_H__
#define __EDL_H__
#include <stdbool.h>
struct edl;
struct edl *edl_open(const char *serial, void (*edl_present)(bool present));
#endif
+1
View File
@@ -85,6 +85,7 @@ endif
cdbalib_srcs = ['circ_buf.c',
'device.c',
'device_parser.c',
'edl.c',
'fastboot.c',
'console.c',
'ppps.c',
+12
View File
@@ -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"