[PATCH v4 6/9] usb: misc: qcom_eud: add host mode coordination

Elson Serrao
2026-05-01T17:06:45Z

EUD functions by presenting itself as a USB device to the host PC for
debugging, making it incompatible with USB host mode configurations.
Enabling EUD while in host mode can also cause the USB controller to
misbehave, as the EUD hub supports only a single upstream-facing port.

Handle the following scenarios to prevent these conflicts:
1. Prevent the user from enabling EUD via sysfs when the USB port is in
host mode.
2. Automatically disable EUD when the USB port switches to host mode,
and re-enable it when exiting host mode.

This ensures consistent state management without creating conflicts
between the EUD debug hub and the USB controller.

Signed-off-by: Elson Serrao <elson.serrao@oss.qualcomm.com>
---
drivers/usb/misc/qcom_eud.c | 65 ++++++++++++++++++++++++++++++++++++-
1 file changed, 64 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
index f656ffc8818a..4734b6307a85 100644
--- a/drivers/usb/misc/qcom_eud.c
+++ b/drivers/usb/misc/qcom_eud.c
@@ -55,12 +55,15 @@ struct eud_chip {
struct device *dev;
void __iomem *base;
struct eud_path *paths[EUD_MAX_PORTS];
+ /* serializes EUD control operations */
+ struct mutex state_lock;
phys_addr_t mode_mgr;
unsigned int int_status;
int irq;
bool enabled;
bool usb_attached;
bool phy_enabled;
+ bool eud_disabled_for_host;
u8 port_idx;
};

@@ -156,17 +159,43 @@ static ssize_t enable_store(struct device *dev,
const char *buf, size_t count)
{
struct eud_chip *chip = dev_get_drvdata(dev);
+ struct eud_path *path;
bool enable;
int ret;

if (kstrtobool(buf, &enable))
return -EINVAL;

+ guard(mutex)(&chip->state_lock);
+
/* Skip operation if already in desired state */
if (chip->enabled == enable)
return count;

+ /*
+ * Handle double-disable scenario: User is disabling EUD that was already
+ * disabled due to host mode. Since the hardware is already disabled, we
+ * only need to clear the host-disabled flag to prevent unwanted re-enabling
+ * when exiting host mode. This respects the user's explicit disable request.
+ */
+ if (!enable && chip->eud_disabled_for_host) {
+ chip->eud_disabled_for_host = false;
+ chip->enabled = false;
+ return count;
+ }
+
if (enable) {
+ /*
+ * EUD functions by presenting itself as a USB device to the host PC for
+ * debugging, making it incompatible with USB host mode configuration.
+ * Prevent enabling EUD in this configuration to avoid hardware conflicts.
+ */
+ path = chip->paths[chip->port_idx];
+ if (path->curr_role == USB_ROLE_HOST) {
+ dev_err(chip->dev, "cannot enable EUD: USB port is in host mode\n");
+ return -EBUSY;
+ }
+
ret = enable_eud(chip);
if (ret) {
dev_err(chip->dev, "failed to enable eud\n");
@@ -308,9 +337,41 @@ static irqreturn_t handle_eud_irq_thread(int irq, void *data)
static int eud_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
{
struct eud_path *path = usb_role_switch_get_drvdata(sw);
+ struct eud_chip *chip = path->chip;
int ret;

- /* Forward the role request to the USB controller */
+ guard(mutex)(&chip->state_lock);
+
+ /*
+ * EUD must be disabled when USB operates in host mode. EUD functions by
+ * presenting itself as a USB device to the host PC for debugging, making
+ * it incompatible with host mode configuration.
+ *
+ * chip->enabled preserves user's sysfs configuration and is not modified
+ * during host mode transitions to maintain user intent.
+ */
+
+ /* Only act if EUD is enabled and this is the active path */
+ if (chip->enabled && path->num == chip->port_idx) {
+ if (role == USB_ROLE_HOST && !chip->eud_disabled_for_host) {
+ ret = disable_eud(chip);
+ if (ret) {
+ dev_err(chip->dev, "failed to disable EUD for host mode: %d\n",
+ ret);
+ return ret;
+ }
+ chip->eud_disabled_for_host = true;
+ } else if (role != USB_ROLE_HOST && chip->eud_disabled_for_host) {
+ ret = enable_eud(chip);
+ if (ret) {
+ dev_err(chip->dev, "failed to re-enable EUD after host mode: %d\n",
+ ret);
+ return ret;
+ }
+ chip->eud_disabled_for_host = false;
+ }
+ }
+
ret = usb_role_switch_set_role(path->controller_sw, role);
if (ret) {
dev_err(path->chip->dev, "failed to set role %s for port %u: %d\n",
@@ -424,6 +485,8 @@ static int eud_probe(struct platform_device *pdev)

chip->dev = &pdev->dev;

+ mutex_init(&chip->state_lock);
+
for_each_child_of_node_scoped(np, child) {
ret = eud_init_path(chip, child);
if (ret)
--
2.34.1