Files
qdl/auto.c
T
Igor Opaniuk 8e9082bc21 qdl: add auto backend that resolves USB/QUD inside the wait loop
Add a meta-backend QDL_DEVICE_AUTO that defers the transport choice
to qdl_open(): instead of picking libusb or QUD upfront, its open()
runs a unified wait loop and binds whichever transport first reaches
an EDL device. The "auto" --backend token (and the default) maps to
it.

Each 250 ms tick the loop does:

  1. usb_open_attempt() - one libusb scan-and-open. On success the
     libusb device is bound as the inner transport.
  2. On Windows, if libusb saw the device but could not open it
     (-EBUSY: the QDLoader 9008 driver has claimed the interface)
     or SetupAPI sees a Qualcomm COM port libusb missed, calls
     qud_open(). On success the QUD device is bound as the inner.
  3. Otherwise sleeps and retries. No timeout: the loop waits
     indefinitely, same cadence as the prior libusb-only loop.

qdl_read/write/close on the outer auto wrapper forward to the inner.
Pre-open out_chunk_size is stashed on the wrapper and applied at
bind time; vip_data and slot live on the outer where firehose
accesses them, so no forwarding is needed for those.

usb_open_attempt() (factored out of usb_open()) returns
0 / -ENODEV / -EBUSY / -EIO. The --backend usb path loops over it
with visibility-transition messaging. The auto loop owns its own
messaging and never emits the -EBUSY diagnostic because that
condition triggers the QUD attempt in the same tick.

qdl-ramdump now uses QDL_DEVICE_AUTO so Windows users with the
official driver can collect crash dumps without re-binding the USB
driver to WinUSB.

QDL_DEVICE_AUTO is explicitly a meta-backend, not a peer of the USB
and QUD backends: its job is to defer the transport choice to the
moment a probe reaches a device. The libusb and QUD backends remain
standalone and selectable via --backend usb / --backend qud.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
2026-05-14 21:40:37 -05:00

166 lines
3.6 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*
* QDL_DEVICE_AUTO: meta-backend that defers transport selection to the
* wait loop. Each 250 ms tick it runs both the libusb open attempt and
* (on Windows) a QUD SetupAPI probe; whichever first reaches an EDL
* device wins, and its concrete qdl_device is bound as the inner. All
* subsequent qdl_read/write/close calls on the outer forward to the
* inner.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "qdl.h"
struct qdl_device_auto {
struct qdl_device base;
struct qdl_device *inner;
long pending_chunk_size;
bool chunk_size_set;
};
static struct qdl_device_auto *to_auto(struct qdl_device *qdl)
{
return container_of(qdl, struct qdl_device_auto, base);
}
static void auto_bind_inner(struct qdl_device_auto *wrap, struct qdl_device *inner)
{
wrap->inner = inner;
wrap->base.max_payload_size = inner->max_payload_size;
if (wrap->chunk_size_set)
inner->set_out_chunk_size(inner, wrap->pending_chunk_size);
}
static int auto_open(struct qdl_device *qdl, const char *serial)
{
struct qdl_device_auto *wrap = to_auto(qdl);
struct qdl_device *usb_dev;
#ifdef _WIN32
struct qdl_device *qud_dev;
int qud_count;
#endif
int visible_prev = -1;
int visible;
int ret;
usb_dev = usb_init();
if (!usb_dev)
return -1;
#ifdef _WIN32
qud_dev = qud_init();
if (!qud_dev) {
qdl_deinit(usb_dev);
return -1;
}
#endif
for (;;) {
ret = try_usb_open(usb_dev, serial, &visible);
if (ret == 0) {
#ifdef _WIN32
qdl_deinit(qud_dev);
#endif
auto_bind_inner(wrap, usb_dev);
return 0;
}
if (ret == -EIO)
goto fail;
#ifdef _WIN32
qud_count = qud_probe_present();
if (qud_count > 0 || ret == -EBUSY) {
if (qud_dev->open(qud_dev, serial) == 0) {
qdl_deinit(usb_dev);
auto_bind_inner(wrap, qud_dev);
return 0;
}
}
visible += qud_count;
#endif
if (visible != visible_prev) {
if (visible == 0)
ux_info("Waiting for EDL device\n");
else if (serial)
ux_info("%d EDL device(s) visible, none match serial \"%s\"\n",
visible, serial);
else
ux_info("%d EDL device(s) visible, none could be opened\n",
visible);
visible_prev = visible;
}
usleep(250000);
}
fail:
qdl_deinit(usb_dev);
#ifdef _WIN32
qdl_deinit(qud_dev);
#endif
return -1;
}
static int auto_read(struct qdl_device *qdl, void *buf, size_t len, unsigned int timeout)
{
struct qdl_device *inner = to_auto(qdl)->inner;
return inner->read(inner, buf, len, timeout);
}
static int auto_write(struct qdl_device *qdl, const void *buf, size_t len, unsigned int timeout)
{
struct qdl_device *inner = to_auto(qdl)->inner;
return inner->write(inner, buf, len, timeout);
}
static void auto_close(struct qdl_device *qdl)
{
struct qdl_device_auto *wrap = to_auto(qdl);
if (!wrap->inner)
return;
wrap->inner->close(wrap->inner);
qdl_deinit(wrap->inner);
wrap->inner = NULL;
}
static void auto_set_out_chunk_size(struct qdl_device *qdl, long size)
{
struct qdl_device_auto *wrap = to_auto(qdl);
if (wrap->inner) {
wrap->inner->set_out_chunk_size(wrap->inner, size);
return;
}
wrap->pending_chunk_size = size;
wrap->chunk_size_set = true;
}
struct qdl_device *auto_init(void)
{
struct qdl_device_auto *wrap = calloc(1, sizeof(*wrap));
if (!wrap)
return NULL;
wrap->base.dev_type = QDL_DEVICE_AUTO;
wrap->base.open = auto_open;
wrap->base.read = auto_read;
wrap->base.write = auto_write;
wrap->base.close = auto_close;
wrap->base.set_out_chunk_size = auto_set_out_chunk_size;
wrap->base.max_payload_size = 1048576;
return &wrap->base;
}