Back in February of this year, Qualcomm quietly published the source code for interacting with EUD. This is perhaps one of the most exciting things they’ve done lately - especially if you spend a lot of time debugging the kernel or U-Boot - let’s talk about it.
EUD stands for Embedded USB Debug: essentially, this is a debug interface built right into almost every Qualcomm SoC since ~2018. Internally it hooks deep into the SoC, providing debug facilities for not just the CPUs but also the myriad of Hexagon co-processor/DSPs; many of the exciting details can be found in this patent from way back in 2014.
In practise, for a non-production device (like a dev board, though some production devices seem to work too), EUD can be enabled by writing a few registers and then starting up the USB phy (though the details vary by generation). Instead of whatever typical gadget you might expect, what appears on your PC is a 7-port USB hub, with 1 port populated by the “EUD control interface”.
With the right USB commands, a second device will appear, this one exposes an SWD interface! Yes! SWD right over the USB cable, no external tools, no soldering, and no expensive debuggers. Closed case debug that (almost) puts Google’s Suzy-Q to shame!
For those unfamiliar: JTAG and SWD are both mechanisms for debugging the CPU cores inside a device, just like you can use GDB to debug programs on your computer (or your IDEs integrated debugger). They let you set breakpoints, halt execution, inspect the registers, single step instructions and all sorts of other useful things.
For quite a while there has been a tantalising fork of openOCD published by Qualcomm on CodeLinaro, promising EUD integration. However, it relied on an at-the-time proprietary EUD library, which was only available to Qualcomm employees and their OEM partners.
The device-side part of this (enabling the EUD interface so it shows up on your PC) has been somewhat supported in upstream Linux for a while. Back in August last year there was an attempt to extend this support for some newer platforms which have additional requirements. This sparked some discussion over the kernel policy: is it acceptable to have drivers in Linux that are only usable by some internal software, gatekept for Qualcomm and their paying partners? The answer appeared to be no, and this seemed to be enough to push Qualcomm in the right direction as after 8 months of silence, here we are!
The code has finally dropped (here’s the link again), and they even updated their openOCD branch to point to the now open library, awesome!
Let’s try to use it…
src/swd_api.cpp:408:64: error: cast from 'std::nullptr_t' to 'uint32_t' {aka 'unsigned
int'} loses precision [-fpermissive]
408 | queue_swd_packet_special(swd_handle_p, SWD_CMD_STATUS, (uint32_t)NULL,
&swd_status);`
Oh no.
Let’s be fair, it almost definitely builds just fine on Ubuntu 20.10 with Qualcomm’s GCC 8.x toolchain. But that’s not what most people are using, we have to fix this!
It turns out to be not too bad, just some minor stuff. Somehow they have -Wall
and -Werror
enabled though, and there is no way we’re gonna get that all passing just yet.
With everything building, the necessary fixes (and a shiny new .gitignore
) have been submitted to Qualcomm’s repo here.
Now we have EUD building, we can try it with OpenOCD. It looks like they based their changes on the latest OpenOCD release 0.12.0, very nice. But wait, this release came out in 2023, and OpenOCD is still in active development… So there’s 2 years worth of changes, and
; git log --oneline v0.12.0 up/master | wc -l
10808
Almost 11k commits! It would really be nice to get this upstream eventually, so maybe let’s just rebase it real quick, we need to point it at the cleaned up EUD fork anyways.
Among Qualcomm’s changes to support EUD, there are also patches adding Hexagon debugging support (and seemingly some improvements for LLDB as well). These got lost along the way but are almost certainly worth looking into at some point.
So here we are, a fun day of fixing up and rebasing some codebases, and a very tasty reward!
; ./src/openocd -f tcl/interface/eud.cfg -f tcl/target/qualcomm/qcom.cfg
Open On-Chip Debugger 0.12.0+dev-01935-g234bdc765544 (2025-04-02-15:20)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
force hard breakpoints
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using EUD 2.1.7
Error: Translation from adapter speed to khz not implemented
Info : adapter-specific clock speed value 6
Info : SWD DPIDR 0x5ba02477
Info : QCOM.cpu0: hardware has 6 breakpoints, 4 watchpoints
Info : [QCOM.cpu0] Examination succeed
Info : [QCOM.cpu0] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections
Info : QCOM.cpu0 cluster 0 core 0 multi core
QCOM.cpu0 halted in AArch64 state due to debug-request, current mode: EL0T
cpsr: 0x00000000 pc: 0xffff95cf9a4c
MMU: enabled, D-Cache: enabled, I-Cache: enabled
You can find the rebased OpenOCD patches over on the linux-msm GitHub along with some quickstart instructions in the README. So far this has been tested on the Snapdragon 845, it should work similarly for the 855 and 865 where we can get away with just poking the enable register and then using Linux or U-Boot to start a USB gadget. Newer SoCs however will probably require additional changes like these for SM8450. Let’s hope these old patch series get refreshed now that the tooling side of the story is in better shape!
Torvalds himself famously doesn’t support the use of debuggers with the kernel (though that certainly hasn’t stopped the wonderful work on kgdb), he wrote (all the way back in 2000):
I don’t like debuggers. Never have, probably never will. I do not condone single-stepping through code to find the bug.
So of course, how practically useful JTAG support is really depends on your workflow. In the Qualcomm Landing Team at Linaro, debuggers have never been a staple of our work for all the typical reasons you’d expect (cost and complexity being the main ones), however with more focus being spent on non-kernel things like U-Boot and the secure world this dynamic is shifting.
U-Boot is an obvious example for us, since it doesn’t currently provide stack traces when it crashes, diagnosis can sometimes be an arduous process which is made infinitely simpler with a (gdb) bt
.
We are particularly interested in the possibilities that EUD opens up for debugging a vertically integrated BSP, especially when TF-A, OP-TEE and U-Boot are in the mix via the Trusted Substrate layer for OpenEmbedded. If this is something you’d like to explore with us then don’t hesitate to get in touch.
In addition to the SWD peripheral, there is also a COM (UART) peripheral, and a trace peripheral. These haven’t yet been explored (and aren’t integrated into OpenOCD) but they should allow for a bidirectional serial port and MMIO tracing respectively. These do open up some more interesting use cases around Closed Cased Debugging in production - this appears to have been intentional on Qualcomm’s behalf with EUD being disabled as part of the production signing process, but with the ability to be re-enabled with a (cryptographically validated) “debug policy”.
Some different SoCs use different addresses for the debug base and CTI base registers, as well as the additional changes required to enable EUD. If you’re able to make this work on your board/SoC, please do open an issue on the linux-msm fork and let us know what worked for you.
Additionally, there is a strange quirk where the sticky reset bit of the PRSR register is always set, perhaps relating to SMP. For now the sticky reset behaviour of OpenOCD is stubbed out but it would be good to figure out what’s going on.
SMP support in general is also currently lacking. The config file has been updated (using rcar as a reference) to define multiple CPU cores, but this doesn’t seem to behave correctly in Linux. For now it’s recommended to boot with maxcpus=1
if you want to actually debug your kernel.
Whether or not EUD is available on your device seems to depend on a variety of options: there are fuses to configure what debug functionality is allowed, as well as support for an OEM signed “debug policy” which can override this behaviour. On at least one production device (the OnePlus 6) EUD appears to be disabled via fuse, and yet it just works anyway. This device also has “crashdump mode” enabled which is not typical, this suggests that maybe OnePlus shipped the device with a loose debug policy, perhaps by mistake.
Lastly, while it is of course extremely useful to have proper JTAG for debugging the kernel (especially when it’s so effortless!). The obvious question is: can this be used to gain control of higher execution levels? And unfortunately the answer appears to be no. If you do manage to halt execution in EL2, all registers will read as 0, and not much seems to be possible, at least on a production device. If your board behaves differently do let us know!
EUD gives us a huge new surface to explore, and offers the potential to greatly improve the experience of low level debugging on Qualcomm boards. We are extremely excited that this is now published and freely available to use, and we very much hope it will become a seamless experience as the tooling and drivers are better integrated.
It is awesome to see Qualcomm’s commitment to improving the developer experience and making their platforms more open is continuing to be demonstrated in their actions, EUD has the potential to save huge amounts of money on expensive debugging equipment, drastically reduce set-up times and make remote debugging easier too (no doubt it will eventually be integrated into our existing remote debugging tooling). Quite simply this raises the foundations for anyone working on Qualcomm platforms, and we can’t wait to see what’s next.