[PATCH v1 3/4] usb: cdns3: Add CIX Sky1 glue driver
sashiko-bot at kernel.org
sashiko-bot at kernel.org
Mon Aug 31 01:41:07 PDT 2026
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [High] IOMEM, clock, and reset leaks on probe error paths.
- [High] Missing get_device() causes unsafe device pointer storage in match function, leading to Use-After-Free.
- [High] Global state corruption of shared static cdns_sky1_auxdata array in probe.
- [High] Flawed platform_data memory management causes leaks or double-frees when multiple or zero children exist.
- [High] Unbalanced Runtime PM state during driver removal.
- [High] Incorrect hardware reset logic during driver removal.
- [High] Signed integer overflow in clock frequency calculations.
- [High] Writing inverted AXI_HALT bitmask to the capability register inadvertently sets all other bits to 1.
- [Medium] Device node reference leak in sky1_of_get_addr_by_name.
- [Medium] Clock error recovery paths destructively nullify devres managed clock pointers.
- [Low] Signed integer overflow when defining CFG_3XPORT_MODE_DIS_SSP.
--
commit f5b41d8b4e76c0521bdb8557f0e54b6023714500
Author: Hongliang Yang <hongliang.yang at cixtech.com>
usb: cdns3: Add CIX Sky1 glue driver
Add glue layer for the Cadence USBSSP DRD controller on CIX Sky1 SoCs.
The glue driver handles platform-specific initialization including clocks,
resets, and PHY configuration.
> diff --git a/drivers/usb/cdns3/cdnsp-sky1.c b/drivers/usb/cdns3/cdnsp-sky1.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/usb/cdns3/cdnsp-sky1.c
> @@ -0,0 +1,682 @@
[ ... ]
> +static int cdnsp_sky1_clk_enable_resume(struct device *dev)
> +{
[ ... ]
> +err_usb_clks:
> + cix_usb_clks[i] = NULL;
> + while (--i >= 0) {
> + clk_disable_unprepare(cix_usb_clks[i]);
> + cix_usb_clks[i] = NULL;
[Severity: Medium]
Is it safe to nullify these clock pointers?
Because the clocks are devres-managed, devres will still properly free them,
but nullifying the pointers destroys the driver's ability to ever reference
them again.
If a transient error occurs during a resume attempt, any subsequent retry or
cleanup will fail because the driver lost its internal pointers.
> + }
> + return ret;
> +};
[ ... ]
> +static void cdnsp_sky1_configure_controller(struct cdnsp_sky1 *data)
> +{
[ ... ]
> + writel(AXI_HALT, data->device_base + D_XEC_AXI_CAP);
> + writel(AXI_HALT, data->xhci_base + D_XEC_AXI_CAP);
> + writel(data->axi_bmax_value, data->device_base + D_XEC_AXI_CTRL0);
> + writel(data->axi_bmax_value, data->xhci_base + D_XEC_AXI_CTRL0);
> + writel((~(u32)(AXI_HALT)), data->device_base + D_XEC_AXI_CAP);
> + writel((~(u32)(AXI_HALT)), data->xhci_base + D_XEC_AXI_CAP);
[Severity: High]
Does this inadvertently overwrite other capability bits?
AXI_HALT is defined as BIT(31). Blindly writing ~(u32)(AXI_HALT) yields
0x7FFFFFFF, which sets bits 0 through 30 to 1 rather than preserving their
existing state with a read-modify-write or clearing them with 0.
Overwriting 31 capability bits with 1s can enable unintended modes or cause
unpredictable hardware behavior.
> + clk = data->sof_clk_freq;
> + v0 = 25 * clk / 100000000;
> + v1 = clk / 10000;
> + v2 = clk / 10;
> + writel(((v0 > 1) ? v0 - 1 : 1), data->device_base
> + + D_XEC_PRE_REG_250NS);
> + writel((unsigned int)((v1 / 100 > 1) > 0 ? (v1 / 100) - 1 : 1),
> + data->device_base + D_XEC_PRE_REG_1US);
> + writel((unsigned int)((v1 / 10 > 1) > 0 ? (v1 / 10) - 1 : 1),
> + data->device_base + D_XEC_PRE_REG_10US);
> + writel(((v1) > 1 ? v1 - 1 : 1), data->device_base
> + + D_XEC_PRE_REG_100US);
> + writel((unsigned int)((125 * clk / 1000000) > 1 ? (125 * clk / 1000000) : 1),
> + data->device_base + D_XEC_PRE_REG_125US);
[Severity: High]
Will this calculation overflow for frequencies above 17.1 MHz?
Because clk is typed as a signed 32-bit int, 125 * clk will exceed INT_MAX
(2,147,483,647) and overflow for values above approximately 17.1 MHz. This
yields a negative value that improperly configures the hardware pre-scaler
registers.
Could this be mitigated by using an unsigned 64-bit cast, such as 125ULL * clk?
[ ... ]
> +static void *sky1_of_get_addr_by_name(struct device_node *parent, char *name)
> +{
> + struct device_node *node;
> + int index;
> +
> + node = of_get_next_child(parent, NULL);
> + if (node) {
> + index = of_property_match_string(node, "reg-names", name);
> + if (index >= 0)
> + return of_iomap(node, index);
> + }
> + return NULL;
> +}
[Severity: Medium]
Can this leak the device node reference?
of_get_next_child() acquires and returns a device node with an incremented
reference count, but the reference is never released with of_node_put() on
either the success or failure paths.
[ ... ]
> +static struct of_dev_auxdata cdns_sky1_auxdata[] = {
> + {
> + .compatible = "cdns,usb3",
> + },
> + {},
> +};
> +
> +static int cdnsp_sky1_probe(struct platform_device *pdev)
> +{
[ ... ]
> + data->device_base = sky1_get_addr_by_name(dev, "dev");
> + if (!data->device_base)
> + return -ENODEV;
> + ret = cdnsp_sky1_drd_init(data);
> + if (ret == -ETIMEDOUT)
> + return -EPROBE_DEFER;
> + if (ret)
> + return ret;
[Severity: High]
Do these error paths leak the IOMEM regions, hardware clocks, and resets?
cdnsp_sky1_probe() manually maps IOMEM regions using sky1_get_addr_by_name()
and enables hardware clocks in cdnsp_sky1_drd_init(). If subsequent
initialization steps fail, the function returns directly or jumps to the err
label without unmapping the IOMEM with iounmap(), disabling the clocks, or
re-asserting the hardware resets.
Since probe can be retried multiple times, these leaked resources will
accumulate.
> + data->oc_gpio = devm_gpiod_get_optional(data->dev, "oc", GPIOD_IN);
> + if (IS_ERR(data->oc_gpio)) {
> + dev_err(data->dev, "can not get oc_gpio\n");
> + ret = PTR_ERR(data->oc_gpio);
> + return ret;
> + }
> + if (data->oc_gpio) {
> + ret = gpiod_direction_input(data->oc_gpio);
> + if (ret < 0)
> + dev_err(data->dev, "set oc_gpio input failed:%d\n", ret);
> + }
> + /* release by platform_device_release */
> + cdns_sky1_pdata = kzalloc(sizeof(*cdns_sky1_pdata), GFP_KERNEL);
[Severity: High]
Can this cause a memory leak or double-free of cdns_sky1_pdata?
The instance-specific structure cdns_sky1_pdata is allocated here and passed
to child devices via of_platform_populate(). If of_platform_populate() matches
zero children, the pointer is never attached to a device and permanently leaks.
If it matches multiple children, all children receive the exact same pointer,
resulting in a double-free kernel panic when each child's release function
attempts to free the same pointer upon unbind.
> + if (!cdns_sky1_pdata)
> + return -ENOMEM;
> + cdns_sky1_pdata->platform_suspend = cdns_sky1_platform_suspend;
> + cdns_sky1_pdata->quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW;
> + cdns_sky1_auxdata->platform_data = cdns_sky1_pdata;
> + ret = of_platform_populate(node, NULL, cdns_sky1_auxdata, dev);
[Severity: High]
Does this corrupt the global cdns_sky1_auxdata array?
cdns_sky1_auxdata is defined as a statically shared array for the driver, but
a dynamically allocated, instance-specific pointer (cdns_sky1_pdata) is
assigned to cdns_sky1_auxdata->platform_data.
Because the device tree contains multiple instances of this driver, probing
them concurrently or sequentially will race to overwrite this shared global
state, causing cross-device memory corruption and dangling pointers.
[ ... ]
> +static void cdnsp_sky1_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct cdnsp_sky1 *data = dev_get_drvdata(dev);
> +
> + pm_runtime_get_sync(dev);
> + of_platform_depopulate(dev);
> + sky1_put_addr(data->xhci_base);
> + sky1_put_addr(data->device_base);
> + reset_control_deassert(data->reset);
> + reset_control_deassert(data->preset);
[Severity: High]
Should these resets be asserted instead of deasserted?
The driver attempts to tear down the hardware but incorrectly calls
reset_control_deassert() again instead of reset_control_assert(). This fails
to reset the hardware and leaves it active while its clocks are subsequently
disabled, which can hang the system or cause undefined hardware behavior.
> + cdnsp_sky1_clk_disable_all(dev);
> + platform_set_drvdata(pdev, NULL);
> +}
[Severity: High]
Does this leave the device's runtime PM subsystem in an unbalanced state?
cdnsp_sky1_probe() enables runtime PM, but cdnsp_sky1_remove() omits a call
to pm_runtime_disable() before exiting.
Additionally, cdnsp_sky1_remove() calls pm_runtime_get_sync() at the start
of the function but omits a balancing pm_runtime_put() call, permanently
leaking the device's usage counter.
[ ... ]
> +static int cdnsp_sky1_find_gadget_match(struct device *dev, void *data)
> +{
[ ... ]
> + if (name && dev->bus && !strcmp(dev->bus->name, "gadget") &&
> + !strncmp(name, gadget_prefix, sizeof(gadget_prefix) - 1)) {
> + *gadget_dev = dev;
> + return 1;
> + }
> + return 0;
> +}
> +
> +static void cdnsp_sky1_shutdown(struct platform_device *pdev)
> +{
[ ... ]
> + if (!device_may_wakeup(dev)) {
> + /*
> + * Find the cdns3 child device, then find its gadget device
> + * and release the function driver before disabling clocks.
> + * This ensures all register accesses in gadget_unbind_driver
> + * complete before clocks are turned off.
> + */
> + device_for_each_child(dev, &cdns, cdnsp_sky1_find_cdns);
> + if (cdns)
> + device_for_each_child(cdns->dev, &gadget_dev,
> + cdnsp_sky1_find_gadget_match);
> + if (gadget_dev)
> + device_release_driver(gadget_dev);
[Severity: High]
Can this result in a use-after-free condition?
In cdnsp_sky1_find_gadget_match(), a pointer to the matched device is stored
without acquiring a persistent reference via get_device(). The iterator
device_for_each_child() immediately drops its reference when advancing or
returning, leaving a bare pointer.
If the gadget device is removed concurrently immediately before
device_release_driver() is called, the pointer becomes dangling.
[ ... ]
> diff --git a/drivers/usb/cdns3/cdnsp-sky1.h b/drivers/usb/cdns3/cdnsp-sky1.h
> new file mode 100644
> --- /dev/null
> +++ b/drivers/usb/cdns3/cdnsp-sky1.h
> @@ -0,0 +1,127 @@
[ ... ]
> +#define D_XEC_CFG_3XPORT_MODE_VALUE 0xa0031e07
> +#define XEC_CFG_3XPORT_MODE_VALUE 0xa0031e07
> +#define CFG_3XPORT_MODE_DIS_SSP (~(1 << 31))
[Severity: Low]
Does this trigger undefined behavior?
1 is treated as a signed 32-bit integer, and shifting it left by 31 bits
overflows the sign bit. Should this be explicitly unsigned, such as
(~(1U << 31)) or ~BIT(31)?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831082309.3854507-1-hongliang.yang@cixtech.com?part=3
More information about the linux-phy
mailing list