public inbox for linux-usb@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] usb: typec: altmode: Fix altmode to handle multiple parners
@ 2026-04-02 12:04 François Scala
  2026-04-02 12:18 ` Greg KH
  2026-04-02 13:26 ` Heikki Krogerus
  0 siblings, 2 replies; 8+ messages in thread
From: François Scala @ 2026-04-02 12:04 UTC (permalink / raw)
  To: linux-usb; +Cc: Heikki Krogerus, François Scala

From: François Scala <francois@scala.name>

The partner field in the altmode struct is a single pointer and the sysfs
symlink uses a fixed name. This cause an error when a second partner is
registered.

  sysfs: cannot create duplicate filename '/devices/platform/USBC000:00/typec/port0/port0.0/partner'

The field is replaced by an array of pointers and the symlink use the
device name to avoid conflict.

  /sys/devices/platform/USBC000:00/typec/port0/port0.0/
  lrwxrwxrwx 1 root root    0 Apr  2 09:12 port0-partner.0 -> ../port0-partner/port0-partner.0
  lrwxrwxrwx 1 root root    0 Apr  2 09:12 port0-partner.1 -> ../port0-partner/port0-partner.1

Signed-off-by: François Scala <francois@scala.name>

Thanks
François
---
 drivers/usb/typec/bus.c   | 133 ++++++++++++++++++++++++++++++++------
 drivers/usb/typec/bus.h   |   8 ++-
 drivers/usb/typec/class.c |  99 +++++++++++++++++++++++++---
 3 files changed, 209 insertions(+), 31 deletions(-)

diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
index e84b134a3381..e09ceb844342 100644
--- a/drivers/usb/typec/bus.c
+++ b/drivers/usb/typec/bus.c
@@ -7,6 +7,8 @@
  */
 
 #include <linux/usb/pd_vdo.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
 
 #include "bus.h"
 #include "class.h"
@@ -62,7 +64,7 @@ static int typec_altmode_set_state(struct typec_altmode *adev,
 	bool is_port = is_typec_port(adev->dev.parent);
 	struct altmode *port_altmode;
 
-	port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
+	port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partners[0];
 
 	return typec_altmode_set_switches(port_altmode, conf, data);
 }
@@ -89,6 +91,7 @@ int typec_altmode_notify(struct typec_altmode *adev,
 	bool is_port;
 	struct altmode *altmode;
 	struct altmode *partner;
+	int partner_idx;
 	int ret;
 
 	if (!adev)
@@ -96,11 +99,17 @@ int typec_altmode_notify(struct typec_altmode *adev,
 
 	altmode = to_altmode(adev);
 
-	if (!altmode->partner)
+	partner_idx = typec_altmode_get_partner_idx_by_name(altmode, dev_name(&adev->dev));
+	if (partner_idx == -1) {
+		dev_info(&adev->dev, "%s adev is not in the partners array\n", __func__);
+		return -ENODEV;
+	}
+
+	if (!altmode->partners[partner_idx])
 		return -ENODEV;
 
 	is_port = is_typec_port(adev->dev.parent);
-	partner = altmode->partner;
+	partner = altmode->partners[partner_idx];
 
 	ret = typec_altmode_set_switches(is_port ? altmode : partner, conf, data);
 	if (ret)
@@ -125,13 +134,18 @@ EXPORT_SYMBOL_GPL(typec_altmode_notify);
  */
 int typec_altmode_enter(struct typec_altmode *adev, u32 *vdo)
 {
-	struct altmode *partner = to_altmode(adev)->partner;
-	struct typec_altmode *pdev = &partner->adev;
+	struct altmode *partner;
+	struct typec_altmode *pdev;
 	int ret;
 
+	partner = to_altmode(adev)->partners[0];
+	pdev = &partner->adev;
+
 	if (!adev || adev->active)
 		return 0;
 
+	typec_altmode_dump("typec_altmode_enter partner", partner);
+
 	if (!pdev->ops || !pdev->ops->enter)
 		return -EOPNOTSUPP;
 
@@ -156,13 +170,15 @@ EXPORT_SYMBOL_GPL(typec_altmode_enter);
  */
 int typec_altmode_exit(struct typec_altmode *adev)
 {
-	struct altmode *partner = to_altmode(adev)->partner;
+	struct altmode *partner = to_altmode(adev)->partners[0];
 	struct typec_altmode *pdev = &partner->adev;
 	int ret;
 
 	if (!adev || !adev->active)
 		return 0;
 
+	typec_altmode_dump("typec_altmode_exit partner", partner);
+
 	if (!pdev->ops || !pdev->ops->exit)
 		return -EOPNOTSUPP;
 
@@ -185,7 +201,7 @@ EXPORT_SYMBOL_GPL(typec_altmode_exit);
  */
 int typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
 {
-	struct altmode *partner = to_altmode(adev)->partner;
+	struct altmode *partner = to_altmode(adev)->partners[0];
 	struct typec_altmode *pdev;
 
 	if (!partner)
@@ -220,12 +236,15 @@ int typec_altmode_vdm(struct typec_altmode *adev,
 	if (!adev)
 		return 0;
 
+
 	altmode = to_altmode(adev);
 
-	if (!altmode->partner)
+	typec_altmode_dump("typec_altmode_vdm altmode", altmode);
+
+	if (!altmode->partners[0])
 		return -ENODEV;
 
-	pdev = &altmode->partner->adev;
+	pdev = &altmode->partners[0]->adev;
 
 	if (!pdev->ops || !pdev->ops->vdm)
 		return -EOPNOTSUPP;
@@ -237,10 +256,10 @@ EXPORT_SYMBOL_GPL(typec_altmode_vdm);
 const struct typec_altmode *
 typec_altmode_get_partner(struct typec_altmode *adev)
 {
-	if (!adev || !to_altmode(adev)->partner)
+	if (!adev || !to_altmode(adev)->partners[0])
 		return NULL;
 
-	return &to_altmode(adev)->partner->adev;
+	return &to_altmode(adev)->partners[0]->adev;
 }
 EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
 
@@ -258,7 +277,7 @@ EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
  */
 int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo)
 {
-	struct altmode *partner = to_altmode(adev)->partner;
+	struct altmode *partner = to_altmode(adev)->partners[0];
 	struct typec_altmode *pdev;
 
 	if (!adev || adev->active)
@@ -288,7 +307,7 @@ EXPORT_SYMBOL_GPL(typec_cable_altmode_enter);
  */
 int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop)
 {
-	struct altmode *partner = to_altmode(adev)->partner;
+	struct altmode *partner = to_altmode(adev)->partners[0];
 	struct typec_altmode *pdev;
 
 	if (!adev || !adev->active)
@@ -330,9 +349,9 @@ int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index so
 	altmode = to_altmode(adev);
 
 	if (is_typec_plug(adev->dev.parent)) {
-		if (!altmode->partner)
+		if (!altmode->partners[0])
 			return -ENODEV;
-		pdev = &altmode->partner->adev;
+		pdev = &altmode->partners[0]->adev;
 	} else {
 		if (!altmode->plug[sop])
 			return -ENODEV;
@@ -360,7 +379,7 @@ EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm);
 struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
 					     enum typec_plug_index index)
 {
-	struct altmode *port = to_altmode(adev)->partner;
+	struct altmode *port = to_altmode(adev)->partners[0];
 
 	if (port->plug[index]) {
 		get_device(&port->plug[index]->adev.dev);
@@ -494,17 +513,58 @@ static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env)
 	return add_uevent_var(env, "MODALIAS=typec:id%04X", altmode->svid);
 }
 
+void typec_altmode_dump(char *name, struct altmode *alt)
+{
+	if (alt) {
+		for (int i = 0; i < TYPEC_ALTMODE_MAX_PARTNERS; i++) {
+			if (alt->partners[i]) {
+				dev_info(&alt->adev.dev, "%s[%p]->partners[%d] = %p / adev name = %s",
+					name,
+					alt,
+					i,
+					alt->partners[i],
+					dev_name(&alt->partners[i]->adev.dev)
+				);
+			}
+		}
+		for (int i = 0; i < 2; i++) {
+			if (alt->plug[i]) {
+				dev_info(&alt->adev.dev, "%s[%p]->plug[%d] = %p / adev name = %s",
+					name,
+					alt,
+					i,
+					alt->plug[i],
+					dev_name(&alt->plug[i]->adev.dev)
+				);
+			}
+		}
+	} else {
+		dev_info(&alt->adev.dev, "alt == NULL !");
+	}
+}
+
 static int typec_altmode_create_links(struct altmode *alt)
 {
-	struct device *port_dev = &alt->partner->adev.dev;
+	struct device *port_dev = &alt->partners[0]->adev.dev;
 	struct device *dev = &alt->adev.dev;
 	int err;
 
+	dev_info(&alt->adev.dev, "%s \"port\" -> %s\n",
+		__func__,
+		dev_name(port_dev)
+	);
+
 	err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
 	if (err)
 		return err;
 
-	err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
+	dev_info(port_dev, "%s \"%s\" -> %s\n",
+		__func__,
+		dev_name(port_dev),
+		dev_name(dev)
+	);
+
+	err = sysfs_create_link(&port_dev->kobj, &dev->kobj, dev_name(dev));
 	if (err)
 		sysfs_remove_link(&dev->kobj, "port");
 
@@ -513,7 +573,17 @@ static int typec_altmode_create_links(struct altmode *alt)
 
 static void typec_altmode_remove_links(struct altmode *alt)
 {
-	sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
+	struct altmode *partner = alt->partners[0];
+
+	dev_info(&partner->adev.dev, "%s \"%s\"\n",
+		__func__,
+		dev_name(&alt->adev.dev)
+	);
+
+	sysfs_remove_link(&partner->adev.dev.kobj, dev_name(&alt->adev.dev));
+
+	dev_info(&alt->adev.dev, "%s \"port\"\n", __func__);
+
 	sysfs_remove_link(&alt->adev.dev.kobj, "port");
 }
 
@@ -525,7 +595,7 @@ static int typec_probe(struct device *dev)
 	int ret;
 
 	/* Fail if the port does not support the alternate mode */
-	if (!altmode->partner)
+	if (!altmode->partners[0])
 		return -ENODEV;
 
 	ret = typec_altmode_create_links(altmode);
@@ -561,6 +631,29 @@ static void typec_remove(struct device *dev)
 	adev->ops = NULL;
 }
 
+int typec_altmode_get_partner_idx_by_name(struct altmode *altmode, const char *name)
+{
+	if (!altmode)
+		return -1;
+
+	for (int i = 0; i < TYPEC_ALTMODE_MAX_PARTNERS; i++) {
+		if (!altmode->partners[i])
+			continue;
+		if (!strcmp(name, dev_name(&altmode->partners[i]->adev.dev)))
+			return i;
+	}
+	return -1;
+}
+
+bool typec_altmode_partners_is_empty(struct altmode *altmode)
+{
+	for (int i = 0; i < TYPEC_ALTMODE_MAX_PARTNERS; i++) {
+		if (altmode->partners[i])
+			return false;
+	}
+	return true;
+}
+
 const struct bus_type typec_bus = {
 	.name = "typec",
 	.dev_groups = typec_groups,
diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h
index 7df5deb1dd3a..3db1d407c322 100644
--- a/drivers/usb/typec/bus.h
+++ b/drivers/usb/typec/bus.h
@@ -8,6 +8,8 @@
 struct typec_mux;
 struct typec_retimer;
 
+#define TYPEC_ALTMODE_MAX_PARTNERS 2
+
 struct altmode {
 	unsigned int			id;
 	struct typec_altmode		adev;
@@ -21,10 +23,14 @@ struct altmode {
 	struct attribute_group		group;
 	const struct attribute_group	*groups[2];
 
-	struct altmode			*partner;
+	struct altmode			*partners[TYPEC_ALTMODE_MAX_PARTNERS];
 	struct altmode			*plug[2];
 };
 
 #define to_altmode(d) container_of(d, struct altmode, adev)
 
+void typec_altmode_dump(char *name, struct altmode *alt);
+int typec_altmode_get_partner_idx_by_name(struct altmode *altmode, const char *name);
+bool typec_altmode_partners_is_empty(struct altmode *altmode);
+
 #endif /* __USB_TYPEC_ALTMODE_H__ */
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 831430909471..0ad9d7488bb0 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -241,6 +241,15 @@ static int altmode_match(struct device *dev, const void *data)
 	return (adev->svid == id->svid);
 }
 
+static int typec_altmode_partners_get_free_slot(struct altmode *altmode)
+{
+	for (int i = 0; i < TYPEC_ALTMODE_MAX_PARTNERS; i++) {
+		if (altmode->partners[i] == NULL)
+			return i;
+	}
+	return -1;
+}
+
 static void typec_altmode_set_partner(struct altmode *altmode)
 {
 	struct typec_altmode *adev = &altmode->adev;
@@ -253,9 +262,18 @@ static void typec_altmode_set_partner(struct altmode *altmode)
 	if (!dev)
 		return;
 
+	dev_info(&altmode->adev.dev, "%s dev %s\n",
+		__func__,
+		dev_name(dev)
+	);
+
+	typec_altmode_dump("altmode before set_partner", altmode);
+
 	/* Bind the port alt mode to the partner/plug alt mode. */
 	partner = to_altmode(to_typec_altmode(dev));
-	altmode->partner = partner;
+	typec_altmode_dump("partner before set_partner", partner);
+
+	altmode->partners[0] = partner;
 
 	/* Bind the partner/plug alt mode to the port alt mode. */
 	if (is_typec_plug(adev->dev.parent)) {
@@ -263,18 +281,51 @@ static void typec_altmode_set_partner(struct altmode *altmode)
 
 		partner->plug[plug->index] = altmode;
 	} else {
-		partner->partner = altmode;
+		int free_spot = typec_altmode_partners_get_free_slot(partner);
+
+		if (free_spot == -1) {
+			dev_info(&altmode->adev.dev, "typec altmode, no free slot in partners table");
+			return;
+		}
+		partner->partners[free_spot] = altmode;
 	}
+
+	typec_altmode_dump("altmode after set_partner", altmode);
+	typec_altmode_dump("partner after set_partner", partner);
+
 }
 
 static void typec_altmode_put_partner(struct altmode *altmode)
 {
-	struct altmode *partner = altmode->partner;
+	struct altmode *partner = altmode->partners[0];
 	struct typec_altmode *adev;
 	struct typec_altmode *partner_adev;
 
-	if (!partner)
+	if (!partner) {
+		dev_info(&altmode->adev.dev, "%s partner is already NULL\n", __func__);
+		return;
+	}
+
+	typec_altmode_dump("altmode before put_partner", altmode);
+	typec_altmode_dump("partner before put_partner", partner);
+
+	int partner_idx = typec_altmode_get_partner_idx_by_name(
+		partner,
+		dev_name(&altmode->adev.dev)
+	);
+
+	dev_info(&altmode->adev.dev, "%s partner_idx = %d\n", __func__, partner_idx);
+
+	if (partner_idx == -1) {
+		dev_info(&altmode->adev.dev, "partner_idx altmod not found in partners list");
 		return;
+	}
+
+	dev_info(&altmode->adev.dev, "%s altmode->adev = %p partner->adev = %p\n",
+		__func__,
+		&altmode->adev,
+		&partner->adev
+	);
 
 	adev = &altmode->adev;
 	partner_adev = &partner->adev;
@@ -284,9 +335,22 @@ static void typec_altmode_put_partner(struct altmode *altmode)
 
 		partner->plug[plug->index] = NULL;
 	} else {
-		partner->partner = NULL;
+		partner->partners[partner_idx] = NULL;
 	}
-	put_device(&partner_adev->dev);
+
+	typec_altmode_dump("partner after set to NULL", partner);
+
+	if (typec_altmode_partners_is_empty(partner)) {
+		dev_info(&altmode->adev.dev, "%s partner->partners is empty -> put_device(...)\n",
+			__func__
+		);
+		put_device(&partner_adev->dev);
+	} else {
+		dev_info(&altmode->adev.dev, "%s partner->partners is not empty -> keeping it\n",
+			__func__
+		);
+	}
+
 }
 
 /**
@@ -370,9 +434,17 @@ static ssize_t active_store(struct device *dev, struct device_attribute *attr,
 {
 	struct typec_altmode *adev = to_typec_altmode(dev);
 	struct altmode *altmode = to_altmode(adev);
+	int partner_idx = typec_altmode_get_partner_idx_by_name(altmode, dev_name(dev));
 	bool enter;
 	int ret;
 
+	dev_info(dev, "%s buf %s altmode %s partner_idx %d\n",
+		__func__,
+		buf,
+		dev_name(&altmode->adev.dev),
+		partner_idx
+	);
+
 	ret = kstrtobool(buf, &enter);
 	if (ret)
 		return ret;
@@ -380,14 +452,21 @@ static ssize_t active_store(struct device *dev, struct device_attribute *attr,
 	if (adev->active == enter)
 		return size;
 
+	if (partner_idx == -1) {
+		dev_warn(dev, "dev is not in the altmode partners array");
+		return 0;
+	}
+
 	if (is_typec_port(adev->dev.parent)) {
 		typec_altmode_update_active(adev, enter);
 
 		/* Make sure that the partner exits the mode before disabling */
-		if (altmode->partner && !enter && altmode->partner->adev.active)
-			typec_altmode_exit(&altmode->partner->adev);
-	} else if (altmode->partner) {
-		if (enter && !altmode->partner->adev.active) {
+		if (altmode->partners[partner_idx]
+				&& !enter
+				&& altmode->partners[partner_idx]->adev.active)
+			typec_altmode_exit(&altmode->partners[partner_idx]->adev);
+	} else if (altmode->partners[partner_idx]) {
+		if (enter && !altmode->partners[partner_idx]->adev.active) {
 			dev_warn(dev, "port has the mode disabled\n");
 			return -EPERM;
 		}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-04-08 10:01 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-02 12:04 [PATCH] usb: typec: altmode: Fix altmode to handle multiple parners François Scala
2026-04-02 12:18 ` Greg KH
2026-04-02 12:27   ` François Scala
2026-04-02 13:35     ` Greg KH
2026-04-02 13:26 ` Heikki Krogerus
2026-04-02 18:10   ` François Scala
2026-04-07 12:17     ` Heikki Krogerus
     [not found]       ` <d627d2fe-415f-4489-b4fa-ec0575a33239@scala.name>
2026-04-08 10:00         ` Heikki Krogerus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox