The patch below does not apply to the 6.12-stable tree. If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to stable@vger.kernel.org.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y git checkout FETCH_HEAD git cherry-pick -x 1e61f6ab08786d66a11cfc51e13d6f08a6b06c56 # <resolve conflicts, build, test, etc.> git commit -s git send-email --to 'stable@vger.kernel.org' --in-reply-to '2025081830-selected-dandelion-46ec@gregkh' --subject-prefix 'PATCH 6.12.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 1e61f6ab08786d66a11cfc51e13d6f08a6b06c56 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel sebastian.reichel@collabora.com Date: Fri, 4 Jul 2025 19:55:06 +0200 Subject: [PATCH] usb: typec: fusb302: cache PD RX state
This patch fixes a race condition communication error, which ends up in PD hard resets when losing the race. Some systems, like the Radxa ROCK 5B are powered through USB-C without any backup power source and use a FUSB302 chip to do the PD negotiation. This means it is quite important to avoid hard resets, since that effectively kills the system's power-supply.
I've found the following race condition while debugging unplanned power loss during booting the board every now and then:
1. lots of TCPM/FUSB302/PD initialization stuff 2. TCPM ends up in SNK_WAIT_CAPABILITIES (tcpm_set_pd_rx is enabled here) 3. the remote PD source does not send anything, so TCPM does a SOFT RESET 4. TCPM ends up in SNK_WAIT_CAPABILITIES for the second time (tcpm_set_pd_rx is enabled again, even though it is still on)
At this point I've seen broken CRC good messages being send by the FUSB302 with a logic analyzer sniffing the CC lines. Also it looks like messages are being lost and things generally going haywire with one of the two sides doing a hard reset once a broken CRC good message was send to the bus.
I think the system is running into a race condition, that the FIFOs are being cleared and/or the automatic good CRC message generation flag is being updated while a message is already arriving.
Let's avoid this by caching the PD RX enabled state, as we have already processed anything in the FIFOs and are in a good state. As a side effect that this also optimizes I2C bus usage :)
As far as I can tell the problem theoretically also exists when TCPM enters SNK_WAIT_CAPABILITIES the first time, but I believe this is less critical for the following reason:
On devices like the ROCK 5B, which are powered through a TCPM backed USB-C port, the bootloader must have done some prior PD communication (initial communication must happen within 5 seconds after plugging the USB-C plug). This means the first time the kernel TCPM state machine reaches SNK_WAIT_CAPABILITIES, the remote side is not sending messages actively. On other devices a hard reset simply adds some extra delay and things should be good afterwards.
Fixes: c034a43e72dda ("staging: typec: Fairchild FUSB302 Type-c chip driver") Cc: stable stable@kernel.org Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.com Reviewed-by: Heikki Krogerus heikki.krogerus@linux.intel.com Link: https://lore.kernel.org/r/20250704-fusb302-race-condition-fix-v1-1-239012c0e... Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index f2801279c4b5..a4ff2403ddd6 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -104,6 +104,7 @@ struct fusb302_chip { bool vconn_on; bool vbus_on; bool charge_on; + bool pd_rx_on; bool vbus_present; enum typec_cc_polarity cc_polarity; enum typec_cc_status cc1; @@ -841,6 +842,11 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) int ret = 0;
mutex_lock(&chip->lock); + if (chip->pd_rx_on == on) { + fusb302_log(chip, "pd is already %s", str_on_off(on)); + goto done; + } + ret = fusb302_pd_rx_flush(chip); if (ret < 0) { fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); @@ -863,6 +869,8 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) str_on_off(on), ret); goto done; } + + chip->pd_rx_on = on; fusb302_log(chip, "pd := %s", str_on_off(on)); done: mutex_unlock(&chip->lock);
From: Krzysztof Kozlowski krzysztof.kozlowski@linaro.org
[ Upstream commit 13b3af26a41538e5051baedba8678eba521a27d3 ]
Replace ternary (condition ? "enable" : "disable") syntax with helpers from string_choices.h because: 1. Simple function call with one argument is easier to read. Ternary operator has three arguments and with wrapping might lead to quite long code. 2. Is slightly shorter thus also easier to read. 3. It brings uniformity in the text - same string. 4. Allows deduping by the linker, which results in a smaller binary file.
Signed-off-by: Krzysztof Kozlowski krzysztof.kozlowski@linaro.org Link: https://lore.kernel.org/r/20250114-str-enable-disable-usb-v1-3-c8405df47c19@... Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Stable-dep-of: 1e61f6ab0878 ("usb: typec: fusb302: cache PD RX state") Signed-off-by: Sasha Levin sashal@kernel.org --- drivers/usb/typec/class.c | 7 +++--- drivers/usb/typec/tcpm/fusb302.c | 24 +++++++++---------- .../typec/tcpm/qcom/qcom_pmic_typec_pdphy.c | 3 ++- .../tcpm/qcom/qcom_pmic_typec_pdphy_stub.c | 3 ++- .../typec/tcpm/qcom/qcom_pmic_typec_port.c | 4 +++- drivers/usb/typec/tcpm/tcpm.c | 7 +++--- 6 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 5c75634b8fa3..c9c3dea8ba07 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -10,6 +10,7 @@ #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> @@ -354,7 +355,7 @@ active_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev);
- return sprintf(buf, "%s\n", alt->active ? "yes" : "no"); + return sprintf(buf, "%s\n", str_yes_no(alt->active)); }
static ssize_t active_store(struct device *dev, struct device_attribute *attr, @@ -630,7 +631,7 @@ static ssize_t supports_usb_power_delivery_show(struct device *dev, { struct typec_partner *p = to_typec_partner(dev);
- return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); + return sprintf(buf, "%s\n", str_yes_no(p->usb_pd)); } static DEVICE_ATTR_RO(supports_usb_power_delivery);
@@ -1688,7 +1689,7 @@ static ssize_t vconn_source_show(struct device *dev, struct typec_port *port = to_typec_port(dev);
return sprintf(buf, "%s\n", - port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); + str_yes_no(port->vconn_role == TYPEC_SOURCE)); } static DEVICE_ATTR_RW(vconn_source);
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index e2fe479e16ad..f15c63d3a8f4 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -24,6 +24,7 @@ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/string.h> +#include <linux/string_choices.h> #include <linux/types.h> #include <linux/usb.h> #include <linux/usb/typec.h> @@ -733,7 +734,7 @@ static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
mutex_lock(&chip->lock); if (chip->vconn_on == on) { - fusb302_log(chip, "vconn is already %s", on ? "On" : "Off"); + fusb302_log(chip, "vconn is already %s", str_on_off(on)); goto done; } if (on) { @@ -746,7 +747,7 @@ static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) if (ret < 0) goto done; chip->vconn_on = on; - fusb302_log(chip, "vconn := %s", on ? "On" : "Off"); + fusb302_log(chip, "vconn := %s", str_on_off(on)); done: mutex_unlock(&chip->lock);
@@ -761,7 +762,7 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge)
mutex_lock(&chip->lock); if (chip->vbus_on == on) { - fusb302_log(chip, "vbus is already %s", on ? "On" : "Off"); + fusb302_log(chip, "vbus is already %s", str_on_off(on)); } else { if (on) ret = regulator_enable(chip->vbus); @@ -769,15 +770,14 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) ret = regulator_disable(chip->vbus); if (ret < 0) { fusb302_log(chip, "cannot %s vbus regulator, ret=%d", - on ? "enable" : "disable", ret); + str_enable_disable(on), ret); goto done; } chip->vbus_on = on; - fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); + fusb302_log(chip, "vbus := %s", str_on_off(on)); } if (chip->charge_on == charge) - fusb302_log(chip, "charge is already %s", - charge ? "On" : "Off"); + fusb302_log(chip, "charge is already %s", str_on_off(charge)); else chip->charge_on = charge;
@@ -854,16 +854,16 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) ret = fusb302_pd_set_auto_goodcrc(chip, on); if (ret < 0) { fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d", - on ? "on" : "off", ret); + str_on_off(on), ret); goto done; } ret = fusb302_pd_set_interrupts(chip, on); if (ret < 0) { fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d", - on ? "on" : "off", ret); + str_on_off(on), ret); goto done; } - fusb302_log(chip, "pd := %s", on ? "on" : "off"); + fusb302_log(chip, "pd := %s", str_on_off(on)); done: mutex_unlock(&chip->lock);
@@ -1531,7 +1531,7 @@ static void fusb302_irq_work(struct work_struct *work) if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", - vbus_present ? "On" : "Off"); + str_on_off(vbus_present)); if (vbus_present != chip->vbus_present) { chip->vbus_present = vbus_present; tcpm_vbus_change(chip->tcpm_port); @@ -1562,7 +1562,7 @@ static void fusb302_irq_work(struct work_struct *work) if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", - comp_result ? "true" : "false"); + str_true_false(comp_result)); if (comp_result) { /* cc level > Rd_threshold, detach */ chip->cc1 = TYPEC_CC_OPEN; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index 726423684bae..18303b34594b 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -12,6 +12,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/pd.h> #include <linux/usb/tcpm.h> #include "qcom_pmic_typec.h" @@ -418,7 +419,7 @@ static int qcom_pmic_typec_pdphy_set_pd_rx(struct tcpc_dev *tcpc, bool on)
spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags);
- dev_dbg(pmic_typec_pdphy->dev, "set_pd_rx: %s\n", on ? "on" : "off"); + dev_dbg(pmic_typec_pdphy->dev, "set_pd_rx: %s\n", str_on_off(on));
return ret; } diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c index df79059cda67..8fac171778da 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c @@ -12,6 +12,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/pd.h> #include <linux/usb/tcpm.h> #include "qcom_pmic_typec.h" @@ -38,7 +39,7 @@ static int qcom_pmic_typec_pdphy_stub_set_pd_rx(struct tcpc_dev *tcpc, bool on) struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); struct device *dev = tcpm->dev;
- dev_dbg(dev, "set_pd_rx: %s\n", on ? "on" : "off"); + dev_dbg(dev, "set_pd_rx: %s\n", str_on_off(on));
return 0; } diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index c37dede62e12..4fc83dcfae64 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -13,6 +13,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/tcpm.h> #include <linux/usb/typec_mux.h> #include <linux/workqueue.h> @@ -562,7 +563,8 @@ static int qcom_pmic_typec_port_set_vconn(struct tcpc_dev *tcpc, bool on) spin_unlock_irqrestore(&pmic_typec_port->lock, flags);
dev_dbg(dev, "set_vconn: orientation %d control 0x%08x state %s cc %s vconn %s\n", - orientation, value, on ? "on" : "off", misc_to_vconn(misc), misc_to_cc(misc)); + orientation, value, str_on_off(on), misc_to_vconn(misc), + misc_to_cc(misc));
return ret; } diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index aa2fa720af15..43e3dac5129f 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -21,6 +21,7 @@ #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/spinlock.h> +#include <linux/string_choices.h> #include <linux/usb.h> #include <linux/usb/pd.h> #include <linux/usb/pd_ado.h> @@ -874,8 +875,8 @@ static int tcpm_enable_auto_vbus_discharge(struct tcpm_port *port, bool enable)
if (port->tcpc->enable_auto_vbus_discharge) { ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, enable); - tcpm_log_force(port, "%s vbus discharge ret:%d", enable ? "enable" : "disable", - ret); + tcpm_log_force(port, "%s vbus discharge ret:%d", + str_enable_disable(enable), ret); if (!ret) port->auto_vbus_discharge_enabled = enable; } @@ -4429,7 +4430,7 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port)
static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable) { - tcpm_log(port, "Setting usb_comm capable %s", capable ? "true" : "false"); + tcpm_log(port, "Setting usb_comm capable %s", str_true_false(capable));
if (port->tcpc->set_partner_usb_comm_capable) port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable);
From: Sebastian Reichel sebastian.reichel@collabora.com
[ Upstream commit 1e61f6ab08786d66a11cfc51e13d6f08a6b06c56 ]
This patch fixes a race condition communication error, which ends up in PD hard resets when losing the race. Some systems, like the Radxa ROCK 5B are powered through USB-C without any backup power source and use a FUSB302 chip to do the PD negotiation. This means it is quite important to avoid hard resets, since that effectively kills the system's power-supply.
I've found the following race condition while debugging unplanned power loss during booting the board every now and then:
1. lots of TCPM/FUSB302/PD initialization stuff 2. TCPM ends up in SNK_WAIT_CAPABILITIES (tcpm_set_pd_rx is enabled here) 3. the remote PD source does not send anything, so TCPM does a SOFT RESET 4. TCPM ends up in SNK_WAIT_CAPABILITIES for the second time (tcpm_set_pd_rx is enabled again, even though it is still on)
At this point I've seen broken CRC good messages being send by the FUSB302 with a logic analyzer sniffing the CC lines. Also it looks like messages are being lost and things generally going haywire with one of the two sides doing a hard reset once a broken CRC good message was send to the bus.
I think the system is running into a race condition, that the FIFOs are being cleared and/or the automatic good CRC message generation flag is being updated while a message is already arriving.
Let's avoid this by caching the PD RX enabled state, as we have already processed anything in the FIFOs and are in a good state. As a side effect that this also optimizes I2C bus usage :)
As far as I can tell the problem theoretically also exists when TCPM enters SNK_WAIT_CAPABILITIES the first time, but I believe this is less critical for the following reason:
On devices like the ROCK 5B, which are powered through a TCPM backed USB-C port, the bootloader must have done some prior PD communication (initial communication must happen within 5 seconds after plugging the USB-C plug). This means the first time the kernel TCPM state machine reaches SNK_WAIT_CAPABILITIES, the remote side is not sending messages actively. On other devices a hard reset simply adds some extra delay and things should be good afterwards.
Fixes: c034a43e72dda ("staging: typec: Fairchild FUSB302 Type-c chip driver") Cc: stable stable@kernel.org Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.com Reviewed-by: Heikki Krogerus heikki.krogerus@linux.intel.com Link: https://lore.kernel.org/r/20250704-fusb302-race-condition-fix-v1-1-239012c0e... Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Sasha Levin sashal@kernel.org --- drivers/usb/typec/tcpm/fusb302.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index f15c63d3a8f4..870a71f953f6 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -104,6 +104,7 @@ struct fusb302_chip { bool vconn_on; bool vbus_on; bool charge_on; + bool pd_rx_on; bool vbus_present; enum typec_cc_polarity cc_polarity; enum typec_cc_status cc1; @@ -841,6 +842,11 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) int ret = 0;
mutex_lock(&chip->lock); + if (chip->pd_rx_on == on) { + fusb302_log(chip, "pd is already %s", str_on_off(on)); + goto done; + } + ret = fusb302_pd_rx_flush(chip); if (ret < 0) { fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); @@ -863,6 +869,8 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) str_on_off(on), ret); goto done; } + + chip->pd_rx_on = on; fusb302_log(chip, "pd := %s", str_on_off(on)); done: mutex_unlock(&chip->lock);
linux-stable-mirror@lists.linaro.org