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: 5+ 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 14:15 ` sashiko-bot
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 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.