Skip to content

qdl: add QUD backend so on Windows flashing via the official QDLoader 9008 driver works#233

Open
igoropaniuk wants to merge 4 commits into
linux-msm:masterfrom
igoropaniuk:feat/qud-backend
Open

qdl: add QUD backend so on Windows flashing via the official QDLoader 9008 driver works#233
igoropaniuk wants to merge 4 commits into
linux-msm:masterfrom
igoropaniuk:feat/qud-backend

Conversation

@igoropaniuk
Copy link
Copy Markdown
Contributor

On Windows, the official Qualcomm Userspace Driver / QDLoader 9008 kernel-mode driver claims the EDL device's USB interface during PnP enumeration and exposes it as \\.\COMx. libusb can't reach the bulk endpoints in that state, so today the only way to flash with qdl on Windows is to swap the binding to WinUSB (for example via Zadig - which is awkward, sometimes blocked by IT policy, and breaks any other QC tooling that expects the COM-port view.

This PR adds a second transport ("QUD" - Qualcomm USB Driver) that talks to the device through the kernel-mode driver's COM-port file directly, leaving Sahara/Firehose framing untouched. Linux is untouched and continues to use libusb.

Comment thread firehose.c Outdated
@andersson
Copy link
Copy Markdown
Collaborator

I don't think it's good UX to rely on the user to tell us which USB driver they have installed. In my work-in-progress, I integrated the QUD support directly into usb.c to make it try both on Windows - to make this choice fully transparent... Code is a little bit messier still, which is why I haven't posted it, but UX is much better.

Remember, we don't want the user to have to know how flashing works to flash the device.

@igoropaniuk
Copy link
Copy Markdown
Contributor Author

Remember, we don't want the user to have to know how flashing works to flash the device.

By default, the "backend" is selected automatically ("auto"). The--backend CLI option is needed to override this selection and choose manually.

@quic-bjorande
Copy link
Copy Markdown
Contributor

By default, the "backend" is selected automatically ("auto"). The--backend CLI option is needed to override this selection and choose manually.

Right, but unless I'm reading the code incorrectly, decode_backend("auto") will return QDL_DEVICE_QUD on Windows, regardless of me having the QUD installed or not.

So, a Windows user that can run QDL today, will face exactly the same problem that the QUD-users are facing today, and they will have to know that they need to say "--backend usb" to get things working again.

Similarly, but hopefully a less frequent problem, is that you will have QUD-users who connect the USB cable and then go "--backend usb", because that sounds like the obvious choice.

@igoropaniuk igoropaniuk force-pushed the feat/qud-backend branch 2 times, most recently from 7ca72bf to ed71d83 Compare May 12, 2026 12:12
@igoropaniuk
Copy link
Copy Markdown
Contributor Author

@andersson added proper auto resultion of the backend:

  1. Probes libusb. If a Qualcomm EDL device is visible AND
    libusb_open() succeeds on it, pick QDL_DEVICE_USB. This keeps
    every WinUSB user on the existing libusb path.
  2. Otherwise probes the QUD SetupAPI enumeration. If a Qualcomm
    QDLoader COM port is present, OR libusb saw the device but
    could not open it (the no-driver / QUD-claimed signal),
    pick QDL_DEVICE_QUD.
  3. Otherwise polls both probes for up to ~2 seconds, to catch the
    common case of the user plugging the cable in just after launch.
    If the bus is still empty, fall back to QDL_DEVICE_USB so the
    existing "Waiting for EDL device" loop keeps running.

@andersson
Copy link
Copy Markdown
Collaborator

This is better, but I think the auto-probe timeout is going to come back and bit us.
The technical solution certainly has its merits, but it is not good UX and the average user won't agree.

I think we need to make the USB probe loop check both cases - even though it means that this can't be a clean "backend".

Introduce QDL_DEVICE_QUD as a third device-transport flavour alongside
the libusb-based USB backend and the SIM backend used by --dry-run.

After this patch --backend=qud parses successfully but errors out at
init time on every host. The actual transport is added in a follow-up.

The intent is to support kernel-mode drivers that expose the EDL
device as a character file (e.g. the official QDLoader 9008 driver on
Windows, where the device shows up as \\.\COMx), so libusb is not
required on hosts where claiming the USB interface from user space is
inconvenient.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
Wire up an actual transport for the QUD backend on Windows. The
official QDLoader 9008 driver exposes a Qualcomm EDL device as a
serial port (\\.\COMx). The Sahara/Firehose framing on top is
identical to the libusb backend; this patch only deals with how
user space reaches the underlying bulk pipes.

Non-Windows hosts keep the stub from the previous patch.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
firehose_read() previously assumed each qdl_read() returns a single
Firehose XML document. That holds on libusb (each USB bulk-in
transfer surfaces as its own read return), but not on stream-oriented
transports. The Windows COM port driver behind the QUD backend just
streams bytes into its RX buffer, and a single ReadFile() can deliver
several back-to-back Firehose responses concatenated; on top of that
the binary payload of a rawmode response (e.g. the sector data that
follows a <read> ACK) can be spliced onto the same buffer right after
the closing </data> tag.

Walk the read buffer using "<?xml" as the document start and
"</data>" as the envelope end, then dispatch each XML chunk through
the existing firehose_response_parse() + response_parser path.
<log/> and <response/> elements are still honoured in arrival order,
and rawmode is observed on whichever chunk carries it. When that
chunk is followed by trailing bytes, those bytes are the first
sectors of the binary payload: hand them to qdl_push_back() so the
next qdl_read() picks them up before the transport is touched again.

firehose_issue_read() now accumulates each sector-chunk across
qdl_read() calls instead of requiring a single full-sized return,
since on a stream transport the rawmode tail and the remaining
sector bytes generally arrive in several reads. libusb keeps
delivering an entire bulk transfer in one call, so the inner loop
runs once there.

The change is a no-op on libusb where each read already contains
exactly one document and the rawmode payload arrives in its own
bulk transfer; it unblocks all current and future stream-oriented
transports without touching the libusb path's behaviour.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
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>
@igoropaniuk
Copy link
Copy Markdown
Contributor Author

@andersson I've completely reworked the usb backend selection based on your suggestions and also rebased on the latest master. Could you take a look?

Comment thread auto.c
{
struct qdl_device_auto *wrap = to_auto(qdl);
struct qdl_device *usb_dev;
#ifdef _WIN32
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can drop this _WIN32 later, when there is support for Linux QUD driver

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants