From: sashiko-bot@kernel.org
To: "Konrad Dybcio" <konradybcio@kernel.org>
Cc: robh@kernel.org, olteanv@gmail.com,
linux-phy@lists.infradead.org, conor+dt@kernel.org,
devicetree@vger.kernel.org, vkoul@kernel.org,
neil.armstrong@linaro.org
Subject: Re: [PATCH v4 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support
Date: Thu, 20 Aug 2026 14:40:14 +0000 [thread overview]
Message-ID: <20260820144015.32F2E1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260820-topic-usb4phy-v4-3-aec9d2cb31f6@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
New issues:
- [Critical] Unclocked hardware register access in `qmp_combo_com_init()` during USB4 mode selection.
- [High] Double initialization of the USB4 PHY hardware in `qmp_combo_usb4_init()`.
- [High] Lockless check of `qmpphy_mode` in `qmp_combo_typec_switch_set()` leads to a TOCTOU race and uncoordinated PHY resets.
- [High] Wiping Type-C orientation state during USB4 mode permanently breaks subsequent fallback to USB3.
- [High] Silently dropping Type-C mux events during USB4 mode permanently strands the PHY.
- [High] PM runtime leak on `usb4_phy` creation failure in `qmp_combo_probe()`.
- [High] Inconsistent PCS selection between `power_on` and `power_off` leaves the USB4 PCS permanently running on error paths.
Pre-existing issues:
- [High] Unbalanced `init_count` decrement on error path when `force == true` in `qmp_combo_com_init()`.
- [High] Missing `phy_mutex` lock in `qmp_combo_typec_mux_set()` causes unsafe concurrent hardware access.
- [Medium] Double-disable of `pipe_clk` on `qmp_combo_usb_power_on()` timeout error.
--
--- Patch [3]: [PATCH v4 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support ---
commit 1ed9c8485ed697b644025bad10e28d1de6fda740
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
phy: qualcomm: qmp-combo: Add preliminary USB4 support
Some Combo PHYs (so far only on SC8280XP, X1E80100 and Glymur), come in
a flavor called USB43DP, which as the name implies, features USB4, USB3
and DP signal processing capabilities. In that architecture, USB3 and
USB4 PHYs share the same USB_PLL while featuring separate logic spaces.
> diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> index c39ced168d035..dfdeec3ee4f5f 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
[ ... ]
> @@ -4019,6 +4052,16 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force)
> else
> aon_toggle = qmp->pcs_aon;
>
> + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) {
> + pcs = qmp->usb4_pcs;
> +
> + qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_USB4_CLAMP_ENABLE], CLAMP_EN);
> +
> + /* Do not disturb the DP PLL in case there's an active DP tunnel */
> + if (readl(com + QPHY_V3_DP_COM_RESET_OVRD_CTRL) & DP_TUNNELING_CLOCK_GEN_EN)
> + dp_reset_val &= ~SW_DPPHY_RESET_MUX;
> + }
> +
> ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
> if (ret) {
> dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
[Severity: Critical]
Does this code execute memory-mapped I/O reads/writes before the
clk_bulk_prepare_enable() call?
When qmp_combo_reconfigure_phy() switches to QMPPHY_MODE_USB4, it calls
qmp_combo_com_exit() to turn off clocks and regulators, then calls
qmp_combo_com_init(). These register accesses happen before the clocks
and regulators are re-enabled, which might cause a synchronous external
abort or bus error.
[ ... ]
> @@ -4219,6 +4276,10 @@ static int qmp_combo_usb_power_on(struct phy *phy)
> struct qmp_combo *qmp = phy_get_drvdata(phy);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
> void __iomem *serdes = qmp->serdes;
> + const struct qmp_phy_init_tbl *pcs_tbl;
> + const struct qmp_phy_init_tbl *rx_tbl;
> + const struct qmp_phy_init_tbl *serdes_tbl;
> + const struct qmp_phy_init_tbl *tx_tbl;
> void __iomem *tx = qmp->tx;
[Severity: Medium]
This is a pre-existing issue, but does a timeout error here cause a
double-disable of pipe_clk?
If PHY initialization times out in qmp_combo_usb_power_on(), it jumps to
err_disable_pipe_clk:
drivers/phy/qualcomm/phy-qcom-qmp-combo.c:qmp_combo_usb_power_on() {
...
err_disable_pipe_clk:
clk_disable_unprepare(qmp->pipe_clk);
return ret;
}
It then returns the error to qmp_combo_usb_init(), which calls
qmp_combo_com_exit(). The exit function unconditionally disables
pipe_clk again, potentially leading to a clock reference count underflow.
[ ... ]
> @@ -4275,16 +4367,20 @@ static int qmp_combo_usb_power_off(struct phy *phy)
> {
> struct qmp_combo *qmp = phy_get_drvdata(phy);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
> + void __iomem *pcs = qmp->pcs;
> +
> + if (qmp->usb4_init_count)
> + pcs = qmp->usb4_pcs;
>
> /* PHY reset */
> - qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
[Severity: High]
Can this asymmetry leave the USB4 PCS running on error paths?
In qmp_combo_usb_power_on(), the code uses qmpphy_mode to select usb4_pcs.
Here, qmp_combo_usb_power_off() uses usb4_init_count to select it.
If qmp_combo_usb4_init() encounters a failure and errors out, it does not
increment usb4_init_count. However, the hardware was already started in USB4
mode. A subsequent call to qmp_combo_usb_exit() will see usb4_init_count == 0
and might power off the USB3 PCS instead of the active USB4 PCS.
[ ... ]
> +static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new_mode)
> +{
> + dev_dbg(qmp->dev, "qmp_combo_reconfigure_phy: switching from qmpphy mode %d to %d\n",
> + qmp->qmpphy_mode, new_mode);
> +
> + if (qmp->usb_init_count || qmp->usb4_init_count)
> + qmp_combo_usb_power_off(qmp->usb_phy);
> +
> + if (qmp->dp_init_count)
> + writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL);
> +
> + qmp_combo_com_exit(qmp, true);
> +
> + qmp->qmpphy_mode = new_mode;
> +
> + /* Now everything's powered down, power up the right PHYs */
> + qmp_combo_com_init(qmp, true);
[Severity: High]
This is a pre-existing issue, but does the error path in qmp_combo_com_init()
cause an unbalanced init_count decrement when force == true?
If force == true, the code skips incrementing qmp->init_count. However, if any
initialization step fails, the function jumps to err_decrement_count:
drivers/phy/qualcomm/phy-qcom-qmp-combo.c:qmp_combo_com_init() {
if (!force && qmp->init_count++)
return 0;
...
ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
if (ret) {
...
goto err_decrement_count;
}
...
err_decrement_count:
qmp->init_count--;
}
This could push the counter to -1, which evaluates to true in subsequent
if (qmp->init_count++) checks, potentially bypassing hardware initialization.
[ ... ]
> + if ((qmp->usb_init_count || qmp->usb4_init_count) &&
> + new_mode != QMPPHY_MODE_DP_ONLY)
> + qmp_combo_usb_power_on(qmp->usb_phy);
> +
> + if ((new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_DP_ONLY) &&
> + qmp->dp_init_count)
> + qmp->cfg->dp_aux_init(qmp);
> +
> + return 0;
> +}
> +
> +static int qmp_combo_usb4_init(struct phy *phy)
> +{
> + struct qmp_combo *qmp = phy_get_drvdata(phy);
> + int ret;
[ ... ]
> + /* USB4 mode takes precedence to USB3(+DP), force reconfigure the PHY */
> + ret = qmp_combo_reconfigure_phy(qmp, QMPPHY_MODE_USB4);
> + if (ret) {
> + clk_disable_unprepare(qmp->p2rr2p_pipe_clk);
> + qmp_combo_com_exit(qmp, false);
> + return ret;
> + }
> +
> + ret = qmp_combo_usb_power_on(phy);
[Severity: High]
Does this code force a redundant hardware power-on sequence on the same
physical block?
When qmp_combo_reconfigure_phy() is called, it already calls
qmp_combo_usb_power_on(qmp->usb_phy) if the USB PHY was active. When execution
returns to qmp_combo_usb4_init(), it unconditionally calls
qmp_combo_usb_power_on(phy) again. This writes initialization sequences and
START bits twice to a running PHY without a power cycle.
[ ... ]
> @@ -4779,24 +5019,29 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
> struct qmp_combo *qmp = typec_switch_get_drvdata(sw);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
>
> + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) {
> + /* QMPPHY has no orientation handling in USB4 mode, don't cache the setting */
> + qmp->orientation = TYPEC_ORIENTATION_NONE;
> + return 0;
> + }
> +
[Severity: High]
Could this destructively clear the Type-C orientation state, causing state
loss upon fallback to USB3?
If qmp_combo_typec_switch_set() forces qmp->orientation = TYPEC_ORIENTATION_NONE,
and later the port falls back to USB3, the TCPM issues a switch_set (which
may drop the orientation due to stale mode) followed by mux_set(USB3). The PHY
might then configure for USB3 with NORMAL orientation, causing the link to
fail if the cable is physically REVERSE.
[Severity: High]
Is qmpphy_mode unsafely accessed outside the lock here?
The check for qmpphy_mode is done before acquiring qmp->phy_mutex. If
qmp_combo_usb4_init() is executing concurrently, it can hold the mutex and
change the mode to QMPPHY_MODE_USB4. If switch_set() evaluates the mode before
this happens, it will block on the mutex and execute a full hardware power
cycle while the host router driver expects exclusive manual control over the
USB4 PHY.
> if (orientation == qmp->orientation || orientation == TYPEC_ORIENTATION_NONE)
> return 0;
>
> - mutex_lock(&qmp->phy_mutex);
> + guard(mutex)(&qmp->phy_mutex);
[ ... ]
> @@ -4804,7 +5049,6 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
> static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
> {
> struct qmp_combo *qmp = typec_mux_get_drvdata(mux);
> - const struct qmp_phy_cfg *cfg = qmp->cfg;
> enum qmpphy_mode new_mode;
> unsigned int svid;
>
> @@ -4815,6 +5059,29 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s
> else
> svid = 0;
>
> + /*
> + * The USB4 router driver must excercise fine-grained control over the timing of
> + * USB4 QMPPHY mode entry/exit, which is difficult to otherwise ensure within Linux's
> + * Type-C framework if the PHY acts as a self-decisive mode mux in parallel.
> + *
> + * Keep the hardware in QMPPHY_MODE_USB4 at all times between .init and .exit of the
> + * (Linux) USB4 PHY and wait for the router driver to turn it off at its discretion.
> + * Once that happens, fall back to the usual USB3/DP/Combo mode logic.
> + *
> + * After the QMPPHY has been turned off through phy_exit(usb4_phy), the next mux_set
> + * will initialize it in the right mode.
> + */
> + if (qmp->usb4_init_count)
> + return 0;
[Severity: High]
Will returning 0 here silently drop Type-C mux events and permanently strand
the PHY in USB4 mode?
If TCPM issues mux_set(USB3) before the USB4 router driver has finished its
teardown (while usb4_init_count > 0), the event is silently ignored. When
usb4_exit() eventually runs, it leaves the PHY powered off, but the TCPM
believes the USB3 transition already succeeded and might not resend it.
[ ... ]
> - if (new_mode == QMPPHY_MODE_DP_ONLY || new_mode == QMPPHY_MODE_USB3DP) {
> - if (qmp->dp_init_count)
> - cfg->dp_aux_init(qmp);
> - }
> - }
> + /* The mux still receives Type-C events, even if all PHYs are uninitialized */
> + if (!qmp->init_count)
> + return 0;
>
> - return 0;
> + return qmp_combo_reconfigure_phy(qmp, new_mode);
> }
[Severity: High]
This is a pre-existing issue, but does missing a lock here cause unsafe
concurrent hardware access?
qmp_combo_typec_mux_set() directly invokes qmp_combo_reconfigure_phy(),
which modifies shared state (qmpphy_mode, usb_init_count) and performs
full hardware power cycles. This function does not hold qmp->phy_mutex,
meaning concurrent execution with usb4_init or usb_init could cause
simultaneous uncoordinated PHY register writes.
[ ... ]
> @@ -5325,6 +5573,17 @@ static int qmp_combo_probe(struct platform_device *pdev)
>
> phy_set_drvdata(qmp->dp_phy, qmp);
>
> + if (qmp->cfg->usb4_serdes_tbl) {
> + qmp->usb4_phy = devm_phy_create(dev, dev->of_node, &qmp_combo_usb4_phy_ops);
> + if (IS_ERR(qmp->usb4_phy)) {
> + ret = PTR_ERR(qmp->usb4_phy);
> + dev_err(dev, "failed to create USB4 PHY: %d\n", ret);
> + goto err_node_put;
> + }
[Severity: High]
Does this jump bypass the pm_runtime_put_noidle() cleanup?
In qmp_combo_probe(), the device acquires a PM runtime reference. If
devm_phy_create() for usb4_phy fails, the code executes goto err_node_put.
This bypasses the err_pm_put label, leaking the PM runtime reference and
preventing the device from suspending.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260820-topic-usb4phy-v4-0-aec9d2cb31f6@oss.qualcomm.com?part=3
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
WARNING: multiple messages have this Message-ID (diff)
From: sashiko-bot@kernel.org
To: "Konrad Dybcio" <konradybcio@kernel.org>
Cc: robh@kernel.org, olteanv@gmail.com,
linux-phy@lists.infradead.org, conor+dt@kernel.org,
devicetree@vger.kernel.org, vkoul@kernel.org,
neil.armstrong@linaro.org
Subject: Re: [PATCH v4 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support
Date: Thu, 20 Aug 2026 14:40:14 +0000 [thread overview]
Message-ID: <20260820144015.32F2E1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260820-topic-usb4phy-v4-3-aec9d2cb31f6@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
New issues:
- [Critical] Unclocked hardware register access in `qmp_combo_com_init()` during USB4 mode selection.
- [High] Double initialization of the USB4 PHY hardware in `qmp_combo_usb4_init()`.
- [High] Lockless check of `qmpphy_mode` in `qmp_combo_typec_switch_set()` leads to a TOCTOU race and uncoordinated PHY resets.
- [High] Wiping Type-C orientation state during USB4 mode permanently breaks subsequent fallback to USB3.
- [High] Silently dropping Type-C mux events during USB4 mode permanently strands the PHY.
- [High] PM runtime leak on `usb4_phy` creation failure in `qmp_combo_probe()`.
- [High] Inconsistent PCS selection between `power_on` and `power_off` leaves the USB4 PCS permanently running on error paths.
Pre-existing issues:
- [High] Unbalanced `init_count` decrement on error path when `force == true` in `qmp_combo_com_init()`.
- [High] Missing `phy_mutex` lock in `qmp_combo_typec_mux_set()` causes unsafe concurrent hardware access.
- [Medium] Double-disable of `pipe_clk` on `qmp_combo_usb_power_on()` timeout error.
--
--- Patch [3]: [PATCH v4 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support ---
commit 1ed9c8485ed697b644025bad10e28d1de6fda740
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
phy: qualcomm: qmp-combo: Add preliminary USB4 support
Some Combo PHYs (so far only on SC8280XP, X1E80100 and Glymur), come in
a flavor called USB43DP, which as the name implies, features USB4, USB3
and DP signal processing capabilities. In that architecture, USB3 and
USB4 PHYs share the same USB_PLL while featuring separate logic spaces.
> diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> index c39ced168d035..dfdeec3ee4f5f 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c
[ ... ]
> @@ -4019,6 +4052,16 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force)
> else
> aon_toggle = qmp->pcs_aon;
>
> + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) {
> + pcs = qmp->usb4_pcs;
> +
> + qphy_setbits(pcs_aon, cfg->regs[QPHY_PCS_USB4_CLAMP_ENABLE], CLAMP_EN);
> +
> + /* Do not disturb the DP PLL in case there's an active DP tunnel */
> + if (readl(com + QPHY_V3_DP_COM_RESET_OVRD_CTRL) & DP_TUNNELING_CLOCK_GEN_EN)
> + dp_reset_val &= ~SW_DPPHY_RESET_MUX;
> + }
> +
> ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
> if (ret) {
> dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
[Severity: Critical]
Does this code execute memory-mapped I/O reads/writes before the
clk_bulk_prepare_enable() call?
When qmp_combo_reconfigure_phy() switches to QMPPHY_MODE_USB4, it calls
qmp_combo_com_exit() to turn off clocks and regulators, then calls
qmp_combo_com_init(). These register accesses happen before the clocks
and regulators are re-enabled, which might cause a synchronous external
abort or bus error.
[ ... ]
> @@ -4219,6 +4276,10 @@ static int qmp_combo_usb_power_on(struct phy *phy)
> struct qmp_combo *qmp = phy_get_drvdata(phy);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
> void __iomem *serdes = qmp->serdes;
> + const struct qmp_phy_init_tbl *pcs_tbl;
> + const struct qmp_phy_init_tbl *rx_tbl;
> + const struct qmp_phy_init_tbl *serdes_tbl;
> + const struct qmp_phy_init_tbl *tx_tbl;
> void __iomem *tx = qmp->tx;
[Severity: Medium]
This is a pre-existing issue, but does a timeout error here cause a
double-disable of pipe_clk?
If PHY initialization times out in qmp_combo_usb_power_on(), it jumps to
err_disable_pipe_clk:
drivers/phy/qualcomm/phy-qcom-qmp-combo.c:qmp_combo_usb_power_on() {
...
err_disable_pipe_clk:
clk_disable_unprepare(qmp->pipe_clk);
return ret;
}
It then returns the error to qmp_combo_usb_init(), which calls
qmp_combo_com_exit(). The exit function unconditionally disables
pipe_clk again, potentially leading to a clock reference count underflow.
[ ... ]
> @@ -4275,16 +4367,20 @@ static int qmp_combo_usb_power_off(struct phy *phy)
> {
> struct qmp_combo *qmp = phy_get_drvdata(phy);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
> + void __iomem *pcs = qmp->pcs;
> +
> + if (qmp->usb4_init_count)
> + pcs = qmp->usb4_pcs;
>
> /* PHY reset */
> - qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
[Severity: High]
Can this asymmetry leave the USB4 PCS running on error paths?
In qmp_combo_usb_power_on(), the code uses qmpphy_mode to select usb4_pcs.
Here, qmp_combo_usb_power_off() uses usb4_init_count to select it.
If qmp_combo_usb4_init() encounters a failure and errors out, it does not
increment usb4_init_count. However, the hardware was already started in USB4
mode. A subsequent call to qmp_combo_usb_exit() will see usb4_init_count == 0
and might power off the USB3 PCS instead of the active USB4 PCS.
[ ... ]
> +static int qmp_combo_reconfigure_phy(struct qmp_combo *qmp, enum qmpphy_mode new_mode)
> +{
> + dev_dbg(qmp->dev, "qmp_combo_reconfigure_phy: switching from qmpphy mode %d to %d\n",
> + qmp->qmpphy_mode, new_mode);
> +
> + if (qmp->usb_init_count || qmp->usb4_init_count)
> + qmp_combo_usb_power_off(qmp->usb_phy);
> +
> + if (qmp->dp_init_count)
> + writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL);
> +
> + qmp_combo_com_exit(qmp, true);
> +
> + qmp->qmpphy_mode = new_mode;
> +
> + /* Now everything's powered down, power up the right PHYs */
> + qmp_combo_com_init(qmp, true);
[Severity: High]
This is a pre-existing issue, but does the error path in qmp_combo_com_init()
cause an unbalanced init_count decrement when force == true?
If force == true, the code skips incrementing qmp->init_count. However, if any
initialization step fails, the function jumps to err_decrement_count:
drivers/phy/qualcomm/phy-qcom-qmp-combo.c:qmp_combo_com_init() {
if (!force && qmp->init_count++)
return 0;
...
ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
if (ret) {
...
goto err_decrement_count;
}
...
err_decrement_count:
qmp->init_count--;
}
This could push the counter to -1, which evaluates to true in subsequent
if (qmp->init_count++) checks, potentially bypassing hardware initialization.
[ ... ]
> + if ((qmp->usb_init_count || qmp->usb4_init_count) &&
> + new_mode != QMPPHY_MODE_DP_ONLY)
> + qmp_combo_usb_power_on(qmp->usb_phy);
> +
> + if ((new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_DP_ONLY) &&
> + qmp->dp_init_count)
> + qmp->cfg->dp_aux_init(qmp);
> +
> + return 0;
> +}
> +
> +static int qmp_combo_usb4_init(struct phy *phy)
> +{
> + struct qmp_combo *qmp = phy_get_drvdata(phy);
> + int ret;
[ ... ]
> + /* USB4 mode takes precedence to USB3(+DP), force reconfigure the PHY */
> + ret = qmp_combo_reconfigure_phy(qmp, QMPPHY_MODE_USB4);
> + if (ret) {
> + clk_disable_unprepare(qmp->p2rr2p_pipe_clk);
> + qmp_combo_com_exit(qmp, false);
> + return ret;
> + }
> +
> + ret = qmp_combo_usb_power_on(phy);
[Severity: High]
Does this code force a redundant hardware power-on sequence on the same
physical block?
When qmp_combo_reconfigure_phy() is called, it already calls
qmp_combo_usb_power_on(qmp->usb_phy) if the USB PHY was active. When execution
returns to qmp_combo_usb4_init(), it unconditionally calls
qmp_combo_usb_power_on(phy) again. This writes initialization sequences and
START bits twice to a running PHY without a power cycle.
[ ... ]
> @@ -4779,24 +5019,29 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
> struct qmp_combo *qmp = typec_switch_get_drvdata(sw);
> const struct qmp_phy_cfg *cfg = qmp->cfg;
>
> + if (qmp->qmpphy_mode == QMPPHY_MODE_USB4) {
> + /* QMPPHY has no orientation handling in USB4 mode, don't cache the setting */
> + qmp->orientation = TYPEC_ORIENTATION_NONE;
> + return 0;
> + }
> +
[Severity: High]
Could this destructively clear the Type-C orientation state, causing state
loss upon fallback to USB3?
If qmp_combo_typec_switch_set() forces qmp->orientation = TYPEC_ORIENTATION_NONE,
and later the port falls back to USB3, the TCPM issues a switch_set (which
may drop the orientation due to stale mode) followed by mux_set(USB3). The PHY
might then configure for USB3 with NORMAL orientation, causing the link to
fail if the cable is physically REVERSE.
[Severity: High]
Is qmpphy_mode unsafely accessed outside the lock here?
The check for qmpphy_mode is done before acquiring qmp->phy_mutex. If
qmp_combo_usb4_init() is executing concurrently, it can hold the mutex and
change the mode to QMPPHY_MODE_USB4. If switch_set() evaluates the mode before
this happens, it will block on the mutex and execute a full hardware power
cycle while the host router driver expects exclusive manual control over the
USB4 PHY.
> if (orientation == qmp->orientation || orientation == TYPEC_ORIENTATION_NONE)
> return 0;
>
> - mutex_lock(&qmp->phy_mutex);
> + guard(mutex)(&qmp->phy_mutex);
[ ... ]
> @@ -4804,7 +5049,6 @@ static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw,
> static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
> {
> struct qmp_combo *qmp = typec_mux_get_drvdata(mux);
> - const struct qmp_phy_cfg *cfg = qmp->cfg;
> enum qmpphy_mode new_mode;
> unsigned int svid;
>
> @@ -4815,6 +5059,29 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s
> else
> svid = 0;
>
> + /*
> + * The USB4 router driver must excercise fine-grained control over the timing of
> + * USB4 QMPPHY mode entry/exit, which is difficult to otherwise ensure within Linux's
> + * Type-C framework if the PHY acts as a self-decisive mode mux in parallel.
> + *
> + * Keep the hardware in QMPPHY_MODE_USB4 at all times between .init and .exit of the
> + * (Linux) USB4 PHY and wait for the router driver to turn it off at its discretion.
> + * Once that happens, fall back to the usual USB3/DP/Combo mode logic.
> + *
> + * After the QMPPHY has been turned off through phy_exit(usb4_phy), the next mux_set
> + * will initialize it in the right mode.
> + */
> + if (qmp->usb4_init_count)
> + return 0;
[Severity: High]
Will returning 0 here silently drop Type-C mux events and permanently strand
the PHY in USB4 mode?
If TCPM issues mux_set(USB3) before the USB4 router driver has finished its
teardown (while usb4_init_count > 0), the event is silently ignored. When
usb4_exit() eventually runs, it leaves the PHY powered off, but the TCPM
believes the USB3 transition already succeeded and might not resend it.
[ ... ]
> - if (new_mode == QMPPHY_MODE_DP_ONLY || new_mode == QMPPHY_MODE_USB3DP) {
> - if (qmp->dp_init_count)
> - cfg->dp_aux_init(qmp);
> - }
> - }
> + /* The mux still receives Type-C events, even if all PHYs are uninitialized */
> + if (!qmp->init_count)
> + return 0;
>
> - return 0;
> + return qmp_combo_reconfigure_phy(qmp, new_mode);
> }
[Severity: High]
This is a pre-existing issue, but does missing a lock here cause unsafe
concurrent hardware access?
qmp_combo_typec_mux_set() directly invokes qmp_combo_reconfigure_phy(),
which modifies shared state (qmpphy_mode, usb_init_count) and performs
full hardware power cycles. This function does not hold qmp->phy_mutex,
meaning concurrent execution with usb4_init or usb_init could cause
simultaneous uncoordinated PHY register writes.
[ ... ]
> @@ -5325,6 +5573,17 @@ static int qmp_combo_probe(struct platform_device *pdev)
>
> phy_set_drvdata(qmp->dp_phy, qmp);
>
> + if (qmp->cfg->usb4_serdes_tbl) {
> + qmp->usb4_phy = devm_phy_create(dev, dev->of_node, &qmp_combo_usb4_phy_ops);
> + if (IS_ERR(qmp->usb4_phy)) {
> + ret = PTR_ERR(qmp->usb4_phy);
> + dev_err(dev, "failed to create USB4 PHY: %d\n", ret);
> + goto err_node_put;
> + }
[Severity: High]
Does this jump bypass the pm_runtime_put_noidle() cleanup?
In qmp_combo_probe(), the device acquires a PM runtime reference. If
devm_phy_create() for usb4_phy fails, the code executes goto err_node_put.
This bypasses the err_pm_put label, leaking the PM runtime reference and
preventing the device from suspending.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260820-topic-usb4phy-v4-0-aec9d2cb31f6@oss.qualcomm.com?part=3
next prev parent reply other threads:[~2026-08-20 14:40 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 14:24 [PATCH v4 0/5] USB4 mode programming for QMMPHY on X1E Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
2026-08-20 14:24 ` [PATCH v4 1/5] dt-bindings: phy: qcom,qmp-usb3-dp: Extend X1E description for USB4 Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
2026-08-20 14:36 ` sashiko-bot
2026-08-20 14:36 ` sashiko-bot
2026-08-20 14:24 ` [PATCH v4 2/5] phy: core: Define TBT phy_mode Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
2026-08-20 14:24 ` [PATCH v4 3/5] phy: qualcomm: qmp-combo: Add preliminary USB4 support Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
2026-08-20 14:40 ` sashiko-bot [this message]
2026-08-20 14:40 ` sashiko-bot
2026-08-20 14:24 ` [PATCH v4 4/5] phy: qualcomm: qmp-combo: Add USB4/TBT3 configuration data for Hamoa Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
2026-08-20 14:44 ` sashiko-bot
2026-08-20 14:44 ` sashiko-bot
2026-08-20 14:24 ` [PATCH v4 5/5] arm64: dts: qcom: hamoa: Extend QMPPHY description for USB4 Konrad Dybcio
2026-08-20 14:24 ` Konrad Dybcio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260820144015.32F2E1F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=konradybcio@kernel.org \
--cc=linux-phy@lists.infradead.org \
--cc=neil.armstrong@linaro.org \
--cc=olteanv@gmail.com \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vkoul@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.