Re: [PATCH v5] phy: renesas: rcar-gen3-usb2: Avoid long delay in atomic context
Claudiu Beznea <[email protected]>
| Newsgroups | org.kernel.vger.linux-renesas-soc,org.infradead.lists.linux-phy,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
Hi, Gentle ping. Thank you, Claudiu On 7/16/26 21:32, Claudiu Beznea wrote: > From: Claudiu Beznea <[email protected]> > > The OTG PHY initialization sequence needs to wait for 20 ms at a specific > step, as described in commit 72c0339c115b ("phy: renesas: > rcar-gen3-usb2: follow the hardware manual procedure"). > > Commit 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware > registers and driver data") tried to address various problems in the > rcar-gen3-usb2 driver and converted the mutex protecting HW register > accesses to a spin lock, leaving, however, a long delay in the critical > section protected by the spin lock. This may become a problem, > especially on RT kernels. > > To address this, release the spin lock before sleeping for 20 ms as > required by the HW manual and reacquire it afterwards. To avoid other > threads entering the critical section and configuring the HW while the > software is waiting for the OTG initialization to complete, introduce the > otg_initializing variable alongside the otg_init_done wait queue. Any > other thread trying to configure the HW while the OTG PHY initialization > is in progress waits for the wait queue instead of immediately returning > errors to PHY users. The IRQs were also disabled while waiting for the OTG > PHY initialization to complete, as the interrupt handler may also apply HW > settings. > > The OTG can only be initialized once. It is initialized by the first PHY > that calls struct phy_ops::rcar_gen3_phy_usb2_init(). > > To avoid failures when multiple PHYs call struct > phy_ops::rcar_gen3_phy_usb2_init() simultaneously, and the PHY responsible > for initializing the OTG either fails or deinit quiqly and another PHY > takes over the PHY init role), the code waiting for the > channel->otg_init_done wait queue retries up to NUM_OF_PHYS times. > > Fixes: 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") > Cc: [email protected] > Reported-by: Pavel Machek <[email protected]> > Closes: https://lore.kernel.org/all/[email protected] > Reported-by: Nobuhiro Iwamatsu <[email protected]> > Closes: https://lore.kernel.org/all/[email protected] > Signed-off-by: Claudiu Beznea <[email protected]> > --- > > Changes in v5: > - replaced completion with wait_queue > - on the threads waiting for OTG initialization, checked again the > channel->otg_initializing and return with -EBUSY if it's true; atherwise > reinitialize the ret variable and continue > - used proper register to set obint_enable_bits in > rcar_gen3_phy_usb2_irqs_mask_all() > > Changes in v4: > - increase the timeout to 120 ms and added a macro for it USB2_OTG_INIT_TIMEOUT > - add retry mechanism when waiting for otg_init_done completion > - read the status registers in rcar_gen3_phy_usb2_irq() before returning, > in case an unexpected IRQ is received while waiting for OTG init > - mask channel->phy_data->obint_enable_bits in rcar_gen3_phy_usb2_irqs_mask_all() > - updated the patch description > > Changes in v3: > - initialize ret value in role_store() > - jump to exit label in role_store() to have the same exit path > - dropped scoped_guard() in rcar_gen3_phy_usb2_irq() to avoid mixing > goto statements with scoped_guard() > - take into account USB2_INT_ENABLE_UCOM_INTEN when masking/unmasking IRQs > - increase the completion timeout to 40ms > - in rcar_gen3_phy_usb2_power_on() disable regulator on failure path > > Changes in v2: > - dropped the rcar_gen3_phy_wait_otg_init() and used inline the code; with it > the comment that was previously in rcar_gen3_phy_wait_otg_init() was added > in struct rcar_gen3_chan for above the org_initializing variable > - dropped READ_ONCE()/WRITE_ONCE() arround struct rcar_gen3_chan::otg_initializing > - checked for struct rcar_gen3_chan::otg_initializing before checking for > rcar_gen3_is_any_otg_rphy_initialized() in all code places > - added rcar_gen3_phy_usb2_irqs_mask_all() and rcar_gen3_phy_usb2_irqs_unmask() > to mask/unmask only the interrupts at the controller level since the line is > shared > - along with it dropped disable_irq_nosync()/enable_irq() from v1 along with > struct rcar_gen3_chan::irq > - increased the completion timeout to 30 ms > > > drivers/phy/renesas/phy-rcar-gen3-usb2.c | 310 +++++++++++++++++++---- > 1 file changed, 265 insertions(+), 45 deletions(-) > > diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c > index 9a45d840efeb..ceaac691ec89 100644 > --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c > +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c > @@ -27,6 +27,7 @@ > #include <linux/reset.h> > #include <linux/string.h> > #include <linux/usb/of.h> > +#include <linux/wait.h> > #include <linux/workqueue.h> > > /******* USB2.0 Host registers (original offset is +0x200) *******/ > @@ -106,6 +107,13 @@ > /* RZ/G2L specific */ > #define USB2_LINECTRL1_USB2_IDMON BIT(0) > > +/* > + * The OTG initialization is expected to finish in 20ms. Choose a large enough > + * timeout to avoid waiters exit prematurely the waiting section under heavy > + * CPU load. > + */ > +#define USB2_OTG_INIT_TIMEOUT msecs_to_jiffies(120) > + > #define NUM_OF_PHYS 4 > enum rcar_gen3_phy_index { > PHY_INDEX_BOTH_HC, > @@ -138,12 +146,20 @@ struct rcar_gen3_chan { > struct rcar_gen3_phy rphys[NUM_OF_PHYS]; > struct regulator *vbus; > struct work_struct work; > + wait_queue_head_t otg_init_done; > spinlock_t lock; /* protects access to hardware and driver data structure. */ > enum usb_dr_mode dr_mode; > bool extcon_host; > bool is_otg_channel; > bool uses_otg_pins; > bool otg_internal_reg; > + /* > + * The OTG can be initialized only once and needs to release the spinlock > + * and wait for 20 ms due to hardware constraints. If a thread executes > + * PHY configuration code while the OTG PHY is waiting for the 20 ms, the > + * thread will have to wait for the OTG PHY initialization to complete. > + */ > + bool otg_initializing; > }; > > struct rcar_gen3_phy_drv_data { > @@ -392,26 +408,58 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, > struct rcar_gen3_chan *ch = dev_get_drvdata(dev); > bool is_b_device; > enum phy_mode cur_mode, new_mode; > + int retries = NUM_OF_PHYS; > + unsigned long flags; > + int ret = -EIO; > > - guard(spinlock_irqsave)(&ch->lock); > + spin_lock_irqsave(&ch->lock, flags); > > - if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) > - return -EIO; > + if (!ch->is_otg_channel) > + goto unlock; > + > + while (retries-- && ch->otg_initializing) { > + spin_unlock_irqrestore(&ch->lock, flags); > + > + ret = wait_event_timeout(ch->otg_init_done, !ch->otg_initializing, > + USB2_OTG_INIT_TIMEOUT); > + ret = ret ? 0 : -ETIMEDOUT; > + if (ret && !retries) > + goto exit; > + > + spin_lock_irqsave(&ch->lock, flags); > + } > + > + /* If another thread started a new initialization just return -EBUSY. */ > + if (ch->otg_initializing) { > + ret = -EBUSY; > + goto unlock; > + } else { > + ret = 0; > + } > + > + if (!rcar_gen3_is_any_otg_rphy_initialized(ch)) { > + ret = -EIO; > + goto unlock; > + } > > - if (sysfs_streq(buf, "host")) > + if (sysfs_streq(buf, "host")) { > new_mode = PHY_MODE_USB_HOST; > - else if (sysfs_streq(buf, "peripheral")) > + } else if (sysfs_streq(buf, "peripheral")) { > new_mode = PHY_MODE_USB_DEVICE; > - else > - return -EINVAL; > + } else { > + ret = -EINVAL; > + goto unlock; > + } > > /* is_b_device: true is B-Device. false is A-Device. */ > is_b_device = rcar_gen3_check_id(ch); > cur_mode = rcar_gen3_get_phy_mode(ch); > > /* If current and new mode is the same, this returns the error */ > - if (cur_mode == new_mode) > - return -EINVAL; > + if (cur_mode == new_mode) { > + ret = -EINVAL; > + goto unlock; > + } > > if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ > if (!is_b_device) /* A-Peripheral */ > @@ -425,7 +473,10 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, > rcar_gen3_init_for_peri(ch); > } > > - return count; > +unlock: > + spin_unlock_irqrestore(&ch->lock, flags); > +exit: > + return ret ?: count; > } > > static ssize_t role_show(struct device *dev, struct device_attribute *attr, > @@ -441,14 +492,11 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, > } > static DEVICE_ATTR_RW(role); > > -static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) > +static void rcar_gen3_init_otg_phase0(struct rcar_gen3_chan *ch) > { > void __iomem *usb2_base = ch->base; > u32 val; > > - if (!ch->is_otg_channel || rcar_gen3_is_any_otg_rphy_initialized(ch)) > - return; > - > /* Should not use functions of read-modify-write a register */ > val = readl(usb2_base + USB2_LINECTRL1); > val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | > @@ -471,7 +519,11 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) > writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); > } > } > - mdelay(20); > +} > + > +static void rcar_gen3_init_otg_phase1(struct rcar_gen3_chan *ch) > +{ > + void __iomem *usb2_base = ch->base; > > writel(0xffffffff, usb2_base + USB2_OBINTSTA); > writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTEN); > @@ -502,6 +554,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) > void __iomem *usb2_base = ch->base; > struct device *dev = ch->dev; > irqreturn_t ret = IRQ_NONE; > + unsigned long flags; > u32 status; > > pm_runtime_get_noresume(dev); > @@ -509,33 +562,102 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) > if (pm_runtime_suspended(dev)) > goto rpm_put; > > - scoped_guard(spinlock, &ch->lock) { > - status = readl(usb2_base + USB2_OBINTSTA); > - if (status & ch->phy_data->obint_enable_bits) { > - dev_vdbg(dev, "%s: %08x\n", __func__, status); > - if (ch->phy_data->vblvl_ctrl) > - writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); > - else > - writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); > - rcar_gen3_device_recognition(ch); > - rcar_gen3_configure_vblvl_ctrl(ch); > - ret = IRQ_HANDLED; > - } > + spin_lock_irqsave(&ch->lock, flags); > + > + status = readl(usb2_base + USB2_OBINTSTA); > + if (status & ch->phy_data->obint_enable_bits) { > + dev_vdbg(dev, "%s: %08x\n", __func__, status); > + if (ch->phy_data->vblvl_ctrl) > + writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); > + else > + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); > + > + ret = IRQ_HANDLED; > + > + /* This should not happen! */ > + if (ch->otg_initializing) > + goto unlock; > + > + rcar_gen3_device_recognition(ch); > + rcar_gen3_configure_vblvl_ctrl(ch); > } > > +unlock: > + spin_unlock_irqrestore(&ch->lock, flags); > rpm_put: > pm_runtime_put_noidle(dev); > return ret; > } > > +static void rcar_gen3_phy_usb2_irqs_mask_all(struct rcar_gen3_chan *channel, > + u32 *masked_irqs_bits) > +{ > + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; > + void __iomem *usb2_base = channel->base; > + > + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) > + bitmask |= channel->rphys[i].int_enable_bits; > + > + val = readl(usb2_base + USB2_INT_ENABLE); > + *masked_irqs_bits = val & bitmask; > + val &= ~bitmask; > + writel(val, usb2_base + USB2_INT_ENABLE); > + > + /* > + * Don't report channel->phy_data->obint_enable_bits IRQs. These are > + * unmasked anyway in rcar_gen3_init_otg_phase1(). > + */ > + val = readl(usb2_base + USB2_OBINTEN); > + val &= ~channel->phy_data->obint_enable_bits; > + writel(val, usb2_base + USB2_OBINTEN); > +} > + > +static void rcar_gen3_phy_usb2_irqs_unmask(struct rcar_gen3_chan *channel, > + u32 irqs_bits) > +{ > + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; > + void __iomem *usb2_base = channel->base; > + > + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) > + bitmask |= channel->rphys[i].int_enable_bits; > + > + val = readl(usb2_base + USB2_INT_ENABLE); > + val &= ~bitmask; > + val |= irqs_bits; > + writel(val, usb2_base + USB2_INT_ENABLE); > +} > + > static int rcar_gen3_phy_usb2_init(struct phy *p) > { > struct rcar_gen3_phy *rphy = phy_get_drvdata(p); > struct rcar_gen3_chan *channel = rphy->ch; > void __iomem *usb2_base = channel->base; > + int retries = NUM_OF_PHYS; > + unsigned long flags; > u32 val; > + int ret; > + > + spin_lock_irqsave(&channel->lock, flags); > > - guard(spinlock_irqsave)(&channel->lock); > + while (retries-- && channel->otg_initializing) { > + spin_unlock_irqrestore(&channel->lock, flags); > + > + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, > + USB2_OTG_INIT_TIMEOUT); > + ret = ret ? 0 : -ETIMEDOUT; > + if (ret && !retries) > + return ret; > + > + spin_lock_irqsave(&channel->lock, flags); > + } > + > + /* If another thread started a new initialization just return -EBUSY. */ > + if (channel->otg_initializing) { > + ret = -EBUSY; > + goto unlock; > + } else { > + ret = 0; > + } > > /* Initialize USB2 part */ > val = readl(usb2_base + USB2_INT_ENABLE); > @@ -548,8 +670,23 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) > } > > /* Initialize otg part (only if we initialize a PHY with IRQs). */ > - if (rphy->int_enable_bits) > - rcar_gen3_init_otg(channel); > + if (rphy->int_enable_bits && channel->is_otg_channel && > + !rcar_gen3_is_any_otg_rphy_initialized(channel)) { > + u32 masked_irq_bits = 0; > + > + rcar_gen3_init_otg_phase0(channel); > + rcar_gen3_phy_usb2_irqs_mask_all(channel, &masked_irq_bits); > + channel->otg_initializing = true; > + spin_unlock_irqrestore(&channel->lock, flags); > + > + fsleep(20000); > + > + spin_lock_irqsave(&channel->lock, flags); > + rcar_gen3_phy_usb2_irqs_unmask(channel, masked_irq_bits); > + rcar_gen3_init_otg_phase1(channel); > + channel->otg_initializing = false; > + wake_up_all(&channel->otg_init_done); > + } > > if (channel->phy_data->vblvl_ctrl) { > /* SIDDQ mode release */ > @@ -568,7 +705,10 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) > > rphy->initialized = true; > > - return 0; > +unlock: > + spin_unlock_irqrestore(&channel->lock, flags); > + > + return ret; > } > > static int rcar_gen3_phy_usb2_exit(struct phy *p) > @@ -576,9 +716,32 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) > struct rcar_gen3_phy *rphy = phy_get_drvdata(p); > struct rcar_gen3_chan *channel = rphy->ch; > void __iomem *usb2_base = channel->base; > + int retries = NUM_OF_PHYS; > + unsigned long flags; > u32 val; > + int ret; > + > + spin_lock_irqsave(&channel->lock, flags); > + > + while (retries-- && channel->otg_initializing) { > + spin_unlock_irqrestore(&channel->lock, flags); > + > + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, > + USB2_OTG_INIT_TIMEOUT); > + ret = ret ? 0 : -ETIMEDOUT; > + if (ret && !retries) > + return ret; > + > + spin_lock_irqsave(&channel->lock, flags); > + } > > - guard(spinlock_irqsave)(&channel->lock); > + /* If another thread started a new initialization just return -EBUSY. */ > + if (channel->otg_initializing) { > + ret = -EBUSY; > + goto unlock; > + } else { > + ret = 0; > + } > > rphy->initialized = false; > > @@ -588,7 +751,9 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) > val &= ~USB2_INT_ENABLE_UCOM_INTEN; > writel(val, usb2_base + USB2_INT_ENABLE); > > - return 0; > +unlock: > + spin_unlock_irqrestore(&channel->lock, flags); > + return ret; > } > > static int rcar_gen3_phy_usb2_power_on(struct phy *p) > @@ -596,8 +761,10 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) > struct rcar_gen3_phy *rphy = phy_get_drvdata(p); > struct rcar_gen3_chan *channel = rphy->ch; > void __iomem *usb2_base = channel->base; > + int retries = NUM_OF_PHYS; > + unsigned long flags; > u32 val; > - int ret = 0; > + int ret; > > if (channel->vbus && !channel->otg_internal_reg) { > ret = regulator_enable(channel->vbus); > @@ -605,7 +772,27 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) > return ret; > } > > - guard(spinlock_irqsave)(&channel->lock); > + spin_lock_irqsave(&channel->lock, flags); > + > + while (retries-- && channel->otg_initializing) { > + spin_unlock_irqrestore(&channel->lock, flags); > + > + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, > + USB2_OTG_INIT_TIMEOUT); > + ret = ret ? 0 : -ETIMEDOUT; > + if (ret && !retries) > + goto disable_regulator; > + > + spin_lock_irqsave(&channel->lock, flags); > + } > + > + /* If another thread started a new initialization just return -EBUSY. */ > + if (channel->otg_initializing) { > + ret = -EBUSY; > + goto unlock; > + } else { > + ret = 0; > + } > > if (!rcar_gen3_are_all_rphys_power_off(channel)) > goto out; > @@ -620,27 +807,59 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) > /* The powered flag should be set for any other phys anyway */ > rphy->powered = true; > > - return 0; > +unlock: > + spin_unlock_irqrestore(&channel->lock, flags); > + > +disable_regulator: > + if (ret && channel->vbus && !channel->otg_internal_reg) > + regulator_disable(channel->vbus); > + > + return ret; > } > > static int rcar_gen3_phy_usb2_power_off(struct phy *p) > { > struct rcar_gen3_phy *rphy = phy_get_drvdata(p); > struct rcar_gen3_chan *channel = rphy->ch; > - int ret = 0; > + int retries = NUM_OF_PHYS; > + unsigned long flags; > + int ret; > > - scoped_guard(spinlock_irqsave, &channel->lock) { > - rphy->powered = false; > + spin_lock_irqsave(&channel->lock, flags); > > - if (rcar_gen3_are_all_rphys_power_off(channel)) { > - u32 val = readl(channel->base + USB2_USBCTR); > + while (retries-- && channel->otg_initializing) { > + spin_unlock_irqrestore(&channel->lock, flags); > > - val |= USB2_USBCTR_PLL_RST; > - writel(val, channel->base + USB2_USBCTR); > - } > + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, > + USB2_OTG_INIT_TIMEOUT); > + ret = ret ? 0 : -ETIMEDOUT; > + if (ret && !retries) > + return ret; > + > + spin_lock_irqsave(&channel->lock, flags); > + } > + > + /* If another thread started a new initialization just return -EBUSY. */ > + if (channel->otg_initializing) { > + ret = -EBUSY; > + goto unlock; > + } else { > + ret = 0; > + } > + > + rphy->powered = false; > + > + if (rcar_gen3_are_all_rphys_power_off(channel)) { > + u32 val = readl(channel->base + USB2_USBCTR); > + > + val |= USB2_USBCTR_PLL_RST; > + writel(val, channel->base + USB2_USBCTR); > } > > - if (channel->vbus && !channel->otg_internal_reg) > +unlock: > + spin_unlock_irqrestore(&channel->lock, flags); > + > + if (!ret && channel->vbus && !channel->otg_internal_reg) > ret = regulator_disable(channel->vbus); > > return ret; > @@ -1007,6 +1226,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) > return ret; > > spin_lock_init(&channel->lock); > + init_waitqueue_head(&channel->otg_init_done); > for (i = 0; i < NUM_OF_PHYS; i++) { > channel->rphys[i].phy = devm_phy_create(dev, NULL, > channel->phy_data->phy_usb2_ops);