From: sashiko-bot@kernel.org
To: "Gary Yang" <gary.yang@cixtech.com>
Cc: linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v4 2/2] usb: typec: add CIX Type-C PD controller driver
Date: Fri, 13 Nov 2026 14:31:49 +0000 [thread overview]
Message-ID: <sashiko-outbox-212990@kernel.org> (raw)
In-Reply-To: <20261113125304.1693911-3-gary.yang@cixtech.com>
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] usb: typec: cix_pd: HPD_IRQ latched in prev_status in
cix_pd_event_handler()
- [Medium] usb: typec: cix_pd: unacknowledged IRQ on status refresh in
cix_pd_resume()
- [Medium] usb: typec: cix_pd: unconditional port update in
cix_pd_event_handler()
- [Medium] usb: typec: cix_pd: unlocked device_is_bound() in
cix_pd_typec_port_update()
- [Medium] usb: typec: cix_pd: IRQ re-enabled on wake error in
cix_pd_suspend()
--
commit df6c3014329332ed5d36e38c30e57956677e5273
Author: Gary Yang <gary.yang@cixtech.com>
usb: typec: add CIX Type-C PD controller driver
This commit adds an I2C driver for the CIX Type-C PD controller (Realtek
RTS5453H), reporting orientation, USB data role, power role, and DisplayPort
alternate mode state changes to the Type-C framework.
> diff --git a/drivers/usb/typec/cix_pd.c b/drivers/usb/typec/cix_pd.c
> new file mode 100644
> index 0000000000000..b413a125c7e6a
> --- /dev/null
> +++ b/drivers/usb/typec/cix_pd.c
[ ... ]
> +static int cix_pd_typec_port_update(struct cix_pd *typec, bool force)
> +{
[ ... ]
> + if (!connected) {
> + if (typec->partner_altmode) {
> + typec_altmode_update_active(typec->partner_altmode,
> + false);
> + typec_unregister_altmode(typec->partner_altmode);
> + typec->partner_altmode = NULL;
> + }
> + if (typec->partner) {
> + dev_info(typec->dev, "typec port(%d): unregister partner\n",
> + typec->id);
> + typec_unregister_partner(typec->partner);
> + typec->partner = NULL;
> + }
> + } else {
> + if (!typec->partner) {
> + dev_info(typec->dev, "typec port(%d): register partner\n",
> + typec->id);
> + typec->partner = typec_register_partner(typec->port,
> + &typec->desc);
> + if (IS_ERR(typec->partner)) {
> + dev_err(typec->dev,
> + "typec port(%d): failed to register partner: %ld\n",
> + typec->id, PTR_ERR(typec->partner));
> + typec->partner = NULL;
> + }
> + }
> +
> + if (typec->partner_altmode && !dp) {
> + typec_altmode_update_active(typec->partner_altmode,
> + false);
> + typec_unregister_altmode(typec->partner_altmode);
> + typec->partner_altmode = NULL;
> + }
> +
> + if (typec->partner && dp && !typec->partner_altmode) {
> + typec->partner_altmode =
> + typec_partner_register_altmode(typec->partner,
> + &cix_pd_dp_partner_altmode);
> + if (IS_ERR(typec->partner_altmode)) {
> + dev_err(typec->dev,
> + "typec port(%d): failed to register DP altmode: %ld\n",
> + typec->id,
> + PTR_ERR(typec->partner_altmode));
> + typec->partner_altmode = NULL;
> + }
> + }
> +
> + /*
> + * typec_altmode_update_active() takes a reference to the
> + * alternate mode driver's module only when a driver is bound
> + * to the mode device. Reporting the mode as active without a
> + * driver would leave that reference unbalanced, and the later
> + * deactivation would drop a reference that was never taken.
> + * Report the mode only once a driver is bound, and retry on
> + * the next status update so that a driver loaded after the
> + * cable was attached still learns about the active mode.
> + */
> + if (typec->partner_altmode &&
> + device_is_bound(&typec->partner_altmode->dev))
> + typec_altmode_update_active(typec->partner_altmode, true);
> + }
[Severity: Medium]
Does calling device_is_bound(&typec->partner_altmode->dev) and
typec_altmode_update_active() without holding
device_lock(&typec->partner_altmode->dev) race with driver unbind?
device_is_bound() requires the caller to hold device_lock(dev). During driver
unbind, __device_release_driver() runs typec_remove() ->
typec_altmode_update_active(adev, false) and device_unbind_cleanup() (which
sets adev->dev.driver = NULL) before detaching knode_driver:
CPU0 CPU1
cix_pd_typec_port_update() device_release_driver_internal()
__device_release_driver(dev)
typec_remove(dev)
typec_altmode_update_active(..., false)
device_is_bound(dev) == true
typec_altmode_update_active(..., true)
if (adev->dev.driver)
device_unbind_cleanup(dev)
dev->driver = NULL
try_module_get(adev->dev.driver->owner)
<-- NULL pointer dereference
Depending on the exact timing against __device_release_driver(), this can also
leak a module reference, set adev->active = true without taking a module
reference, or double-put the module reference when
typec_altmode_update_active(typec->partner_altmode, false) runs on disconnect
or in cix_pd_unregister_ports().
In addition, when the DisplayPort alternate mode driver is built as a module
and loaded asynchronously by udev after typec_partner_register_altmode(),
device_is_bound() is false during the initial status update; if the hardware
raises no further interrupt after the module binds, partner_altmode remains
inactive in sysfs.
[ ... ]
> +static int cix_pd_event_handler(struct cix_pd *rts, u8 clear_irq, bool force)
> +{
> + int ret, err = 0;
> + bool changed;
> + u8 data_control[3] = {0};
> +
> + mutex_lock(&rts->lock);
> + if (clear_irq) {
> + data_control[0] = 2;
> + data_control[1] = I2C_INT_ACK_BIT;
> + data_control[2] = 0;
> +
> + ret = cix_pd_block_write(rts, RTS_DATA_CONTROL, data_control, 3);
> + if (ret)
> + goto i2c_err;
> + }
> +
> + ret = cix_pd_block_read(rts, RTS_DATA_STATUS, rts->data_status, 6);
> + if (ret)
> + goto i2c_err;
> +
> + dev_dbg(rts->dev,
> + "typec port(%d): data status = 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
> + rts->id, rts->data_status[0], rts->data_status[1],
> + rts->data_status[2], rts->data_status[3], rts->data_status[4],
> + rts->data_status[5]);
> +
> + /*
> + * Only bytes 1..3 are decoded by this driver. Byte 0 and bytes 4..5
> + * have no known meaning, so they are excluded from the comparison: a
> + * change in an undecoded bit must not be reported as a port event.
> + */
> + changed = !rts->status_valid ||
> + memcmp(&rts->data_status[1], &rts->prev_status[1],
> + DATA_STATUS_COUNT - 3);
> + memcpy(rts->prev_status, rts->data_status, DATA_STATUS_COUNT);
> + rts->status_valid = true;
> +
> + err = cix_pd_typec_port_update(rts, force);
[Severity: Medium]
Should cix_pd_event_handler() skip cix_pd_typec_port_update() when neither
changed nor force is true?
Because all controller instances share one interrupt line, an interrupt on one
port runs cix_pd_event_handler() on every sibling instance. Calling
cix_pd_typec_port_update() unconditionally when !changed && !force causes
every unchanged sibling port to call typec_set_orientation() and
typec_mux_set(), which reprogram the orientation switch and mode mux and emit
spurious KOBJ_CHANGE uevents and sysfs notifications on every unaffected port.
> + if (err)
> + dev_err(rts->dev, "typec port(%d): failed to update port: %d\n",
> + rts->id, err);
> +
> + if (rts->data_status[2] & HPD_IRQ) {
> + data_control[0] = 2;
> + data_control[1] = 0;
> + data_control[2] = HPD_IRQ_ACK;
> + ret = cix_pd_block_write(rts, RTS_DATA_CONTROL, data_control, 3);
> + if (ret)
> + goto i2c_err;
> + }
[Severity: High]
Does copying rts->data_status into rts->prev_status with HPD_IRQ still set
break shared-interrupt attribution on subsequent interrupts?
When a DisplayPort sink generates an HPD_IRQ pulse, rts->prev_status[2] saves
HPD_IRQ = 1 (and cix_pd_typec_port_update() saves DP_STATUS_IRQ_HPD in
typec->hpd_status), and then writing HPD_IRQ_ACK clears HPD_IRQ in the
controller hardware. If the sink generates a second HPD_IRQ while the rest of
data_status[1..3] is unchanged, rts->data_status[2] again has HPD_IRQ = 1,
matching the stale HPD_IRQ = 1 in rts->prev_status[2]; memcmp() reports no
change and cix_pd_irq_handle() returns IRQ_NONE for a valid interrupt it just
acknowledged, which can cause the spurious interrupt detector to disable the
shared IRQ line. Conversely, if a sibling port interrupts next while this
port has HPD_IRQ = 0, this port sees a difference against its stale
rts->prev_status[2], returns IRQ_HANDLED for the sibling's interrupt, and logs
a duplicate mode message because dp_data->status no longer matches
typec->hpd_status.
[ ... ]
> +static int __maybe_unused cix_pd_suspend(struct device *dev)
> +{
[ ... ]
> + disable_irq(client->irq);
> + rts->irq_disabled = true;
> +
> + if (device_may_wakeup(dev)) {
> + ret = enable_irq_wake(client->irq);
> + if (ret) {
> + dev_err(dev, "failed to enable IRQ wake: %d\n", ret);
> + enable_irq(client->irq);
> + rts->irq_disabled = false;
> + } else {
> + rts->wake_enabled = true;
> + }
> + }
> + mutex_unlock(&rts->irq_lock);
> +
> + return 0;
> +}
[Severity: Medium]
If enable_irq_wake() fails, doesn't re-enabling client->irq while returning 0
allow the interrupt handler to run before the I2C adapter resumes?
Returning 0 lets system suspend proceed with the IRQ enabled, so
resume_device_irqs() unmasks the line before the I2C adapter is resumed and
cix_pd_irq_handle() can attempt I2C transfers on a suspended bus. Also, when
multiple controller instances share client->irq, calling disable_irq() in each
instance increments the IRQ disable depth to the number of instances, whereas
suspend_device_irq() on IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND chips decrements the
depth only once, leaving the shared wakeup interrupt masked during sleep.
> +
> +static int __maybe_unused cix_pd_resume(struct device *dev)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct cix_pd *rts = i2c_get_clientdata(client);
> + int ret;
> +
> + if (client->irq > 0) {
> + mutex_lock(&rts->irq_lock);
> + if (rts->wake_enabled) {
> + disable_irq_wake(client->irq);
> + rts->wake_enabled = false;
> + }
> + if (rts->irq_disabled) {
> + enable_irq(client->irq);
> + rts->irq_disabled = false;
> + }
> + mutex_unlock(&rts->irq_lock);
> + }
> +
> + /* Force the USB role to be reprogrammed after system resume. */
> + ret = cix_pd_event_handler(rts, 0, true);
[Severity: Medium]
Does calling cix_pd_event_handler(rts, 0, true) after enable_irq() consume
sleep-time status changes without acknowledging the controller interrupt?
If a port status change occurred while the system was suspended, the
controller keeps the shared level-triggered interrupt line asserted until
I2C_INT_ACK_BIT is written. When multiple instances share client->irq,
enable_irq() keeps the line masked until the last instance resumes, while each
earlier instance's cix_pd_event_handler(rts, 0, true) updates rts->prev_status
without clearing the hardware interrupt (and on the last or sole instance,
enable_irq() races the IRQ thread against cix_pd_event_handler()). When
cix_pd_irq_handle() then runs for the still-asserted line, rts->prev_status
already matches rts->data_status, so every instance returns IRQ_NONE.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113125304.1693911-2-gary.yang@cixtech.com?part=2
prev parent reply other threads:[~2026-11-13 14:31 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 12:53 [PATCH v4 0/2] usb: typec: add CIX Type-C PD controller driver Gary Yang
2026-11-13 12:53 ` [PATCH v4 1/2] dt-bindings: usb: add Realtek RTS5453H Type-C port controller Gary Yang
2026-11-13 12:53 ` [PATCH v4 2/2] usb: typec: add CIX Type-C PD controller driver Gary Yang
2026-11-13 14:31 ` sashiko-bot [this message]
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=sashiko-outbox-212990@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=gary.yang@cixtech.com \
--cc=linux-usb@vger.kernel.org \
--cc=robh@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox