[PATCH v3] platform/chrome: cros_ec_typec: Stop altmode work during partner removal
From: Myeonghun Pak
Date: Sat Oct 17 2026 - 22:34:50 EST
DisplayPort and Thunderbolt port altmodes queue work to deliver VDM
responses to partner drivers. Partner altmode unregistration does not
drain this work, so it can run while the partner driver is being removed
and access released driver data.
Look up the corresponding port altmode with typec_altmode_get_partner()
and disable its work before unregistering the partner altmode. Keep the
work disabled while the partner driver is removed, then clear pending
response and DP status state before re-enabling it for a new connection.
Use the same cleanup when list-node allocation fails after registering
an altmode.
This issue was identified as part of our ongoing research while
reviewing kernel code.
Fixes: dbb3fc0ffa95 ("platform/chrome: cros_ec_typec: Displayport support")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Co-developed-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Ijae Kim <ae878000@xxxxxxxxx>
Signed-off-by: Myeonghun Pak <mhun512@xxxxxxxxx>
---
Changes in v3:
- Use typec_altmode_get_partner() while walking the existing altmode list
instead of separately iterating over all port altmodes.
- Replace the port-wide helper with cros_typec_enable_altmode() and drop
the redundant cancellation during port altmode unregistration.
- Drop the allocation-order change. Reuse a single-altmode cleanup helper
for list-node allocation failures instead.
Previous version:
https://lore.kernel.org/all/20261002200108.399258-1-mhun512@xxxxxxxxx/
Validation: GCC 15 x86_64 W=1 composite-object builds passed with DP/TBT
both enabled, DP only, TBT only, and both disabled. Patch application and
checkpatch checks also passed.
No hardware or runtime testing.
drivers/platform/chrome/cros_ec_typec.c | 20 +++++++++++++++++--
drivers/platform/chrome/cros_typec_altmode.c | 28 ++++++++++++++++++++++++++
drivers/platform/chrome/cros_typec_altmode.h | 7 +++++++
3 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c
index 50a68819ceb7bbfbd888ca919d678d4c75237c26..09a5c58e1edbb1599e3903c15cab2766a849a891 100644
--- a/drivers/platform/chrome/cros_ec_typec.c
+++ b/drivers/platform/chrome/cros_ec_typec.c
@@ -274,6 +274,22 @@ static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num,
return ret;
}
+static void cros_typec_unregister_altmode(struct typec_altmode *amode, bool is_partner)
+{
+ const struct typec_altmode *port_altmode =
+ is_partner ? typec_altmode_get_partner(amode) : NULL;
+
+ /* Partner drivers can queue port work until their removal completes. */
+ if (port_altmode)
+ cros_typec_enable_altmode(port_altmode, false);
+
+ typec_unregister_altmode(amode);
+
+ /* Port altmodes are reused when a partner reconnects. */
+ if (port_altmode)
+ cros_typec_enable_altmode(port_altmode, true);
+}
+
static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int port_num,
bool is_partner)
{
@@ -284,7 +300,7 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po
head = is_partner ? &port->partner_mode_list : &port->plug_mode_list;
list_for_each_entry_safe(node, tmp, head, list) {
list_del(&node->list);
- typec_unregister_altmode(node->amode);
+ cros_typec_unregister_altmode(node->amode, is_partner);
devm_kfree(typec->dev, node);
}
}
@@ -915,7 +931,7 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_
node = devm_kzalloc(typec->dev, sizeof(*node), GFP_KERNEL);
if (!node) {
ret = -ENOMEM;
- typec_unregister_altmode(amode);
+ cros_typec_unregister_altmode(amode, is_partner);
goto err_cleanup;
}
diff --git a/drivers/platform/chrome/cros_typec_altmode.c b/drivers/platform/chrome/cros_typec_altmode.c
index 66c546bf89b532d3bae1de322a1cfb1205e0190f..c06ad9238ad740d21d38015b7ece831660a475da 100644
--- a/drivers/platform/chrome/cros_typec_altmode.c
+++ b/drivers/platform/chrome/cros_typec_altmode.c
@@ -37,6 +37,34 @@ struct cros_typec_dp_data {
bool pending_status_update;
};
+void cros_typec_enable_altmode(const struct typec_altmode *alt, bool enable)
+{
+ struct cros_typec_altmode_data *adata = dev_get_drvdata(&alt->dev);
+ struct cros_typec_dp_data *dp_data;
+
+ /* An altmode without its optional implementation has no work. */
+ if (!adata)
+ return;
+
+ if (!enable) {
+ disable_work_sync(&adata->work);
+ return;
+ }
+
+ mutex_lock(&adata->lock);
+ adata->header = 0;
+ adata->vdo_data = NULL;
+ adata->vdo_size = 0;
+ if (adata->sid == USB_TYPEC_DP_SID) {
+ dp_data = container_of(adata, struct cros_typec_dp_data, adata);
+ dp_data->configured = false;
+ dp_data->pending_status_update = false;
+ }
+ mutex_unlock(&adata->lock);
+
+ enable_work(&adata->work);
+}
+
static void cros_typec_altmode_work(struct work_struct *work)
{
struct cros_typec_altmode_data *data =
diff --git a/drivers/platform/chrome/cros_typec_altmode.h b/drivers/platform/chrome/cros_typec_altmode.h
index 3f2aa95d065af709643ad653df487a9987780da4..7f47eae66b0c23d14f158296075d1092df704676 100644
--- a/drivers/platform/chrome/cros_typec_altmode.h
+++ b/drivers/platform/chrome/cros_typec_altmode.h
@@ -11,6 +11,13 @@ struct typec_altmode;
struct typec_altmode_desc;
struct typec_displayport_data;
+#if IS_ENABLED(CONFIG_CROS_EC_TYPEC_ALTMODES)
+void cros_typec_enable_altmode(const struct typec_altmode *alt, bool enable);
+#else
+static inline void cros_typec_enable_altmode(const struct typec_altmode *alt,
+ bool enable) {}
+#endif
+
#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
struct typec_altmode *
cros_typec_register_displayport(struct cros_typec_port *port,
--
2.53.0