Polling be gone on LAN95xx
Subsystems (7)
cover Polling be gone on LAN95xx
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
The meat of the series is in patch [5/7]. The preceding and following patches are various cleanups to prepare for and adjust to interrupt-driven link state detection.
Please review and test. Thanks!
Lukas Wunner (7):
usbnet: Run unregister_netdev() before unbind() again
usbnet: smsc95xx: Don't clear read-only PHY interrupt
usbnet: smsc95xx: Don't reset PHY behind PHY driver's back
usbnet: smsc95xx: Avoid link settings race on interrupt reception
usbnet: smsc95xx: Forward PHY interrupts to PHY driver to avoid
polling
net: phy: smsc: Cache interrupt mask
net: phy: smsc: Cope with hot-removal in interrupt handler
drivers/net/phy/smsc.c | 28 +++--- drivers/net/usb/asix_devices.c | 6 +- drivers/net/usb/smsc95xx.c | 155 ++++++++++++++++----------------- drivers/net/usb/usbnet.c | 6 +- 4 files changed, 91 insertions(+), 104 deletions(-)
Signature · 1 line
2.35.2
PATCH 1/7 usbnet: Run unregister_netdev() before unbind() again
Commit 2c9d6c2b871d ("usbnet: run unbind() before unregister_netdev()") sought to fix a use-after-free on disconnect of USB Ethernet adapters.
It turns out that a different fix is necessary to address the issue:
https://lore.kernel.org/netdev/18b3541e5372bc9b9fc733d422f4e698c089077c.1650177997.git.lukas@wunner.de/
So the commit was not necessary.
The commit made binding and unbinding of USB Ethernet asymmetrical:
Before, usbnet_probe() first invoked the ->bind() callback and then register_netdev(). usbnet_disconnect() mirrored that by first invoking unregister_netdev() and then ->unbind().
Since the commit, the order in usbnet_disconnect() is reversed and no longer mirrors usbnet_probe().
One consequence is that a PHY disconnected (and stopped) in ->unbind() is afterwards stopped once more by unregister_netdev() as it closes the netdev before unregistering. That necessitates a contortion in ->stop() because the PHY may only be stopped if it hasn't already been disconnected.
Reverting the commit allows making the call to phy_stop() unconditional in ->stop().
--- drivers/net/usb/asix_devices.c | 6 +----- drivers/net/usb/smsc95xx.c | 3 +-- drivers/net/usb/usbnet.c | 6 +++--- 3 files changed, 5 insertions(+), 10 deletions(-)
drivers/net/usb/asix_devices.c +1 -5
@@ -795,11 +795,7 @@ static int ax88772_stop(struct usbnet *dev) {struct asix_common_private *priv = dev->driver_priv; -/* On unplugged USB, we will get MDIO communication errors and the -* PHY will be set in to PHY_HALTED state. -*/ -if (priv->phydev->state !=PHY_HALTED ) -phy_stop (priv->phydev); +phy_stop (priv->phydev);return 0 ; }
drivers/net/usb/smsc95xx.c +1 -2
@@ -1243,8 +1243,7 @@ static int smsc95xx_start_phy(struct usbnet *dev)static int smsc95xx_stop (struct usbnet *dev) { -if (dev->net->phydev) -phy_stop (dev->net->phydev); +phy_stop (dev->net->phydev);return 0 ; }
drivers/net/usb/usbnet.c +3 -3
@@ -1616,9 +1616,6 @@ void usbnet_disconnect (struct usb_interface *intf) xdev->bus->bus_name, xdev->devpath, dev->driver_info->description); -if (dev->driver_info->unbind) - dev->driver_info->unbind (dev, intf); - net = dev->net;unregister_netdev (net);
@@ -1626,6 +1623,9 @@ void usbnet_disconnect (struct usb_interface *intf)usb_scuttle_anchored_urbs (&dev->deferred); +if (dev->driver_info->unbind) + dev->driver_info->unbind (dev, intf); +usb_kill_urb (dev->interrupt);usb_free_urb (dev->interrupt);kfree (dev->padding_pkt);
Signature · 1 line
2.35.2
Re: [PATCH net-next 1/7] usbnet: Run unregister_netdev() before unbind() again
Quoted 22 lines · Lukas Wunner
Commit
2c9d6c2b871d("usbnet: rununbind()beforeunregister_netdev()") sought to fix a use-after-free on disconnect of USB Ethernet adapters.It turns out that a different fix is necessary to address the issue:
https://lore.kernel.org/netdev/18b3541e5372bc9b9fc733d422f4e698c089077c.1650177997.git.lukas@wunner.de/So the commit was not necessary.
The commit made binding and unbinding of USB Ethernet asymmetrical:
Before,usbnet_probe()first invoked the ->bind() callback and thenregister_netdev().usbnet_disconnect()mirrored that by first invokingunregister_netdev()and then ->unbind().Since the commit, the order in
usbnet_disconnect()is reversed and no longer mirrorsusbnet_probe().One consequence is that a PHY disconnected (and stopped) in ->unbind() is afterwards stopped once more by
unregister_netdev()as it closes the netdev before unregistering. That necessitates a contortion in ->stop() because the PHY may only be stopped if it hasn't already been disconnected.
Quoted 8 lines
Reverting the commit allows making the call to
phy_stop()unconditional in ->stop().Signed-off-by Lukas Wunner <[email protected]>Cc Oleksij Rempel <[email protected]>Cc Martyn Welch <[email protected]>Cc Andrew Lunn <[email protected]>
Oliver Neukum
maintainerRe: [PATCH net-next 1/7] usbnet: Run unregister_netdev() before unbind() again
In reply to Lukas WunnerQuoted 4 lines · Lukas Wunner
Commit
2c9d6c2b871d("usbnet: rununbind()beforeunregister_netdev()") sought to fix a use-after-free on disconnect of USB Ethernet adapters.
Hi,
thank you for doing this. We have finally come at least to a partial solution for this issue.
Regards
Oliver
PATCH 2/7 usbnet: smsc95xx: Don't clear read-only PHY interrupt
In reply to Lukas WunnerUpon receiving data from the Interrupt Endpoint, the SMSC LAN95xx driver attempts to clear the signaled interrupts by writing "all ones" to the Interrupt Status Register.
However the driver only ever enables a single type of interrupt, namely the PHY Interrupt. And according to page 119 of the LAN950x datasheet, its bit in the Interrupt Status Register is read-only. There's no other way to clear it than in a separate PHY register:
Consequently, writing "all ones" to the Interrupt Status Register is pointless and can be dropped.
--- drivers/net/usb/smsc95xx.c | 4 ---- 1 file changed, 4 deletions(-)
drivers/net/usb/smsc95xx.c +0 -4
@@ -572,10 +572,6 @@ static int smsc95xx_link_reset(struct usbnet *dev)unsigned long flags;int ret; - ret =smsc95xx_write_reg (dev,INT_STS ,INT_STS_CLEAR_ALL_ ); -if (ret <0 ) -return ret; -spin_lock_irqsave (&pdata->mac_cr_lock, flags);if (pdata->phydev->duplex !=DUPLEX_FULL ) { pdata->mac_cr &= ~MAC_CR_FDPX_ ;
Signature · 1 line
2.35.2
PATCH 3/7 usbnet: smsc95xx: Don't reset PHY behind PHY driver's back
In reply to Lukas Wunnersmsc95xx_reset() resets the PHY behind the PHY driver's back, which seems like a bad idea generally. Remove that portion of the function.
We're about to use PHY interrupts instead of polling to detect link changes on SMSC LAN95xx chips. Because smsc95xx_reset() is called from usbnet_open(), PHY interrupt settings are lost whenever the net_device is brought up.
There are two other callers of smsc95xx_reset(), namely smsc95xx_bind() and smsc95xx_reset_resume(), and both may indeed benefit from a PHY reset. However they already perform one through their calls to phy_connect_direct() and phy_init_hw().
--- drivers/net/usb/smsc95xx.c | 18 ------------------ 1 file changed, 18 deletions(-)
drivers/net/usb/smsc95xx.c +0 -18
@@ -887,24 +887,6 @@ static int smsc95xx_reset(struct usbnet *dev)return ret; } - ret =smsc95xx_write_reg (dev,PM_CTRL ,PM_CTL_PHY_RST_ ); -if (ret <0 ) -return ret; - - timeout =0 ; -do { -msleep (10 ); - ret =smsc95xx_read_reg (dev,PM_CTRL , &read_buf); -if (ret <0 ) -return ret; - timeout++; - }while ((read_buf &PM_CTL_PHY_RST_ ) && (timeout <100 )); - -if (timeout >=100 ) { -netdev_warn (dev->net,"timeout waiting for PHY Reset\n" ); -return ret; - } - ret =smsc95xx_set_mac_address (dev);if (ret <0 )return ret;
Signature · 1 line
2.35.2
PATCH 4/7 usbnet: smsc95xx: Avoid link settings race on interrupt reception
In reply to Lukas WunnerWhen a PHY interrupt is signaled, the SMSC LAN95xx driver updates the MAC full duplex mode and PHY flow control registers based on cached data in struct phy_device:
smsc95xx_status() # raises EVENT_LINK_RESET
usbnet_deferred_kevent()
smsc95xx_link_reset() # uses cached data in phydev
Simultaneously, phylib polls link status once per second and updates that cached data:
phy_state_machine()
phy_check_link_status()
phy_read_status()
lan87xx_read_status()
genphy_read_status() # updates cached data in phydev
If smsc95xx_link_reset() wins the race against genphy_read_status(), the registers may be updated based on stale data.
E.g. if the link was previously down, phydev->duplex is set to DUPLEX_UNKNOWN and that's what smsc95xx_link_reset() will use, even though genphy_read_status() may update it to DUPLEX_FULL afterwards.
PHY interrupts are currently only enabled on suspend to trigger wakeup, so the impact of the race is limited, but we're about to enable them perpetually.
Avoid the race by delaying execution of smsc95xx_link_reset() until phy_state_machine() has done its job and calls back via smsc95xx_handle_link_change().
Signaling EVENT_LINK_RESET on wakeup is not necessary because phylib picks up link status changes through polling. So drop the declaration of a ->link_reset() callback.
--- drivers/net/usb/smsc95xx.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/net/usb/smsc95xx.c +7 -7
@@ -566,7 +566,7 @@ static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev)return smsc95xx_write_reg (dev,AFC_CFG , afc_cfg); } -static int smsc95xx_link_reset (struct usbnet *dev) +static void smsc95xx_mac_update_fullduplex (struct usbnet *dev) {struct smsc95xx_priv *pdata = dev->driver_priv;unsigned long flags;
@@ -583,14 +583,14 @@ static int smsc95xx_link_reset(struct usbnet *dev)spin_unlock_irqrestore (&pdata->mac_cr_lock, flags); ret =smsc95xx_write_reg (dev,MAC_CR , pdata->mac_cr); -if (ret <0 ) -return ret; +if (ret <0 ) { +netdev_warn (dev->net,"Error updating MAC full duplex mode\n" ); +return ; + } ret =smsc95xx_phy_update_flowcontrol (dev);if (ret <0 )netdev_warn (dev->net,"Error updating PHY flow control\n" ); - -return ret; }static void smsc95xx_status (struct usbnet *dev,struct urb *urb)
@@ -607,7 +607,7 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb)netif_dbg (dev, link, dev->net,"intdata: 0x%08X\n" , intdata);if (intdata &INT_ENP_PHY_INT_ ) -usbnet_defer_kevent (dev,EVENT_LINK_RESET ); + ;else netdev_warn (dev->net,"unexpected interrupt, intdata=0x%08X\n" , intdata);
@@ -1070,6 +1070,7 @@ static void smsc95xx_handle_link_change(struct net_device *net)struct usbnet *dev =netdev_priv (net);phy_print_status (net->phydev); +smsc95xx_mac_update_fullduplex (dev);usbnet_defer_kevent (dev,EVENT_LINK_CHANGE ); }
@@ -1975,7 +1976,6 @@ static const struct driver_info smsc95xx_info = { .description = "smsc95xx USB 2.0 Ethernet", .bind = smsc95xx_bind, .unbind = smsc95xx_unbind, - .link_reset = smsc95xx_link_reset, .reset = smsc95xx_reset, .check_connect = smsc95xx_start_phy, .stop = smsc95xx_stop,
Signature · 1 line
2.35.2
Andrew Lunn
maintainerRe: [PATCH net-next 4/7] usbnet: smsc95xx: Avoid link settings race on interrupt reception
drivers/net/usb/smsc95xx.c static void smsc95xx_status(struct usbnet *dev, struct urb *urb) · line 613
Quoted 10 lines
static void
smsc95xx_status(struct usbnet *dev, struct urb *urb)drivers/net/usb/smsc95xx.c +1
-1@@ -607,7 +607,7 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb)netif_dbg (dev, link, dev->net,"intdata: 0x%08X\n" , intdata);if (intdata &INT_ENP_PHY_INT_ ) -usbnet_defer_kevent (dev,EVENT_LINK_RESET ); + ;else netdev_warn (dev->net,"unexpected interrupt, intdata=0x%08X\n" , intdata);
Hi Lukas
It would probably make sense to invert the condition and remove the ;
Andrew
PATCH 5/7 usbnet: smsc95xx: Forward PHY interrupts to PHY driver to avoid polling
In reply to Lukas WunnerLink status of SMSC LAN95xx chips is polled once per second, even though they're capable of signaling PHY interrupts through the MAC layer.
Forward those interrupts to the PHY driver to avoid polling. Benefits are reduced bus traffic, reduced CPU overhead and quicker interface bringup.
Polling was introduced in 2016 by commit d69d16949346 ("usbnet:
smsc95xx: fix link detection for disabled autonegotiation"). Back then, the LAN95xx driver neglected to enable the ENERGYON interrupt, hence couldn't detect link-up events when auto-negotiation was disabled. The proper solution would have been to enable the ENERGYON interrupt instead of polling.
Since then, PHY handling was moved from the LAN95xx driver to the SMSC PHY driver with commit 05b35e7eb9a1 ("smsc95xx: add phylib support"). That PHY driver is capable of link detection with auto-negotiation disabled because it enables the ENERGYON interrupt.
Note that signaling interrupts through the MAC layer not only works with the integrated PHY, but also with an external PHY, provided its interrupt pin is attached to LAN95xx's nPHY_INT pin.
In the unlikely event that the interrupt pin of an external PHY is attached to a GPIO of the SoC (or not connected at all), the driver can be amended to retrieve the irq from the PHY's of_node.
To forward PHY interrupts to phylib, it is not sufficient to call phy_mac_interrupt(). Instead, the PHY's interrupt handler needs to run so that PHY interrupts are cleared. That's because according to page 119 of the LAN950x datasheet, "The source of this interrupt is a level. The interrupt persists until it is cleared in the PHY."
Therefore, create an IRQ domain with a single IRQ for the PHY. In the future, the IRQ domain may be extended to support the 11 GPIOs on the LAN95xx.
Normally the PHY interrupt should be masked until the PHY driver has cleared it. However masking requires a (sleeping) USB transaction and interrupts are received in (non-sleepable) softirq context. I decided not to mask the interrupt at all (by using the dummy_irq_chip's noop ->irq_mask() callback): The USB interrupt endpoint is polled in 1 msec intervals and normally that's sufficient to wake the PHY driver's IRQ thread and have it clear the interrupt. If it does take longer, worst thing that can happen is the IRQ thread is woken again. No big deal.
Because PHY interrupts are now perpetually enabled, there's no need to selectively enable them on suspend. So remove all invocations of smsc95xx_enable_phy_wakeup_interrupts().
--- drivers/net/usb/smsc95xx.c | 118 +++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 52 deletions(-)
drivers/net/usb/smsc95xx.c +66 -52
@@ -18,6 +18,8 @@#include <linux/usb/usbnet.h> #include <linux/slab.h> #include <linux/of_net.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> #include <linux/mdio.h> #include <linux/phy.h> #include <net/selftests.h>
@@ -53,6 +55,9 @@#define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) +#define SMSC95XX_NR_IRQS (1)/* raise to 12 for GPIOs */ +#define PHY_HWIRQ (SMSC95XX_NR_IRQS - 1) +struct smsc95xx_priv {u32 mac_cr;u32 hash_hi;
@@ -61,6 +66,9 @@ struct smsc95xx_priv {spinlock_t mac_cr_lock;u8 features;u8 suspend_flags; +struct irq_chip irqchip; +struct irq_domain *irqdomain; +struct fwnode_handle *irqfwnode;struct mii_bus *mdiobus;struct phy_device *phydev; };
@@ -595,6 +603,8 @@ static void smsc95xx_mac_update_fullduplex(struct usbnet *dev)static void smsc95xx_status (struct usbnet *dev,struct urb *urb) { +struct smsc95xx_priv *pdata = dev->driver_priv; +unsigned long flags;u32 intdata;if (urb->actual_length !=4 ) {
@@ -606,11 +616,20 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb) intdata =get_unaligned_le32 (urb->transfer_buffer);netif_dbg (dev, link, dev->net,"intdata: 0x%08X\n" , intdata); +/* USB interrupts are received in softirq (tasklet) context. +* Switch to hardirq context to make genirq code happy. +*/ +local_irq_save (flags); +__irq_enter_raw (); +if (intdata &INT_ENP_PHY_INT_ ) - ; +generic_handle_domain_irq (pdata->irqdomain,PHY_HWIRQ );else netdev_warn (dev->net,"unexpected interrupt, intdata=0x%08X\n" , intdata); + +__irq_exit_raw (); +local_irq_restore (flags); }/* Enable or disable Tx & Rx checksum offload engines */
@@ -1078,8 +1097,9 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) {struct smsc95xx_priv *pdata;bool is_internal_phy; +char usb_path[64 ]; +int ret, phy_irq;u32 val; -int ret; printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n");
@@ -1119,10 +1139,38 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)if (ret) goto free_pdata; +/* create irq domain for use by PHY driver and GPIO consumers */ +usb_make_path (dev->udev, usb_path,sizeof (usb_path)); + pdata->irqfwnode =irq_domain_alloc_named_fwnode (usb_path); +if (!pdata->irqfwnode) { + ret = -ENOMEM ; + goto free_pdata; + } + + pdata->irqdomain =irq_domain_create_linear (pdata->irqfwnode, +SMSC95XX_NR_IRQS , + &irq_domain_simple_ops, + pdata); +if (!pdata->irqdomain) { + ret = -ENOMEM ; + goto free_irqfwnode; + } + + phy_irq =irq_create_mapping (pdata->irqdomain,PHY_HWIRQ ); +if (!phy_irq) { + ret = -ENOENT ; + goto remove_irqdomain; + } + + pdata->irqchip = dummy_irq_chip; + pdata->irqchip.name =SMSC_CHIPNAME ; +irq_set_chip_and_handler_name (phy_irq, &pdata->irqchip, + handle_simple_irq,"phy" ); + pdata->mdiobus =mdiobus_alloc ();if (!pdata->mdiobus) { ret = -ENOMEM ; - goto free_pdata; + goto dispose_irq; } ret =smsc95xx_read_reg (dev,HW_CFG , &val);
@@ -1155,6 +1203,7 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) goto unregister_mdio; } + pdata->phydev->irq = phy_irq; pdata->phydev->is_internal = is_internal_phy;/* detect device revision as different features may be available */
@@ -1197,6 +1246,15 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) free_mdio:mdiobus_free (pdata->mdiobus); +dispose_irq: +irq_dispose_mapping (phy_irq); + +remove_irqdomain: +irq_domain_remove (pdata->irqdomain); + +free_irqfwnode: +irq_domain_free_fwnode (pdata->irqfwnode); + free_pdata:kfree (pdata);return ret;
@@ -1209,6 +1267,9 @@ static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf)phy_disconnect (dev->net->phydev);mdiobus_unregister (pdata->mdiobus);mdiobus_free (pdata->mdiobus); +irq_dispose_mapping (irq_find_mapping (pdata->irqdomain,PHY_HWIRQ )); +irq_domain_remove (pdata->irqdomain); +irq_domain_free_fwnode (pdata->irqfwnode);netif_dbg (dev, ifdown, dev->net,"free pdata\n" );kfree (pdata); }
@@ -1233,29 +1294,6 @@ static u32 smsc_crc(const u8 *buffer, size_t len, int filter)return crc << ((filter %2 ) *16 ); } -static int smsc95xx_enable_phy_wakeup_interrupts (struct usbnet *dev,u16 mask) -{ -int ret; - -netdev_dbg (dev->net,"enabling PHY wakeup interrupts\n" ); - -/* read to clear */ - ret =smsc95xx_mdio_read_nopm (dev,PHY_INT_SRC ); -if (ret <0 ) -return ret; - -/* enable interrupt source */ - ret =smsc95xx_mdio_read_nopm (dev,PHY_INT_MASK ); -if (ret <0 ) -return ret; - - ret |= mask; - -smsc95xx_mdio_write_nopm (dev,PHY_INT_MASK , ret); - -return 0 ; -} -static int smsc95xx_link_ok_nopm (struct usbnet *dev) {int ret;
@@ -1422,7 +1460,6 @@ static int smsc95xx_enter_suspend3(struct usbnet *dev)static int smsc95xx_autosuspend (struct usbnet *dev,u32 link_up) {struct smsc95xx_priv *pdata = dev->driver_priv; -int ret;if (!netif_running (dev->net)) {/* interface is ifconfig down so fully power down hw */
@@ -1441,27 +1478,10 @@ static int smsc95xx_autosuspend(struct usbnet *dev, u32 link_up) }netdev_dbg (dev->net,"autosuspend entering SUSPEND1\n" ); - -/* enable PHY wakeup events for if cable is attached */ - ret =smsc95xx_enable_phy_wakeup_interrupts (dev, -PHY_INT_MASK_ANEG_COMP_ ); -if (ret <0 ) { -netdev_warn (dev->net,"error enabling PHY wakeup ints\n" ); -return ret; - } -netdev_info (dev->net,"entering SUSPEND1 mode\n" );return smsc95xx_enter_suspend1 (dev); } -/* enable PHY wakeup events so we remote wakeup if cable is pulled */ - ret =smsc95xx_enable_phy_wakeup_interrupts (dev, -PHY_INT_MASK_LINK_DOWN_ ); -if (ret <0 ) { -netdev_warn (dev->net,"error enabling PHY wakeup ints\n" ); -return ret; - } -netdev_dbg (dev->net,"autosuspend entering SUSPEND3\n" );return smsc95xx_enter_suspend3 (dev); }
@@ -1527,13 +1547,6 @@ static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) }if (pdata->wolopts &WAKE_PHY ) { - ret =smsc95xx_enable_phy_wakeup_interrupts (dev, - (PHY_INT_MASK_ANEG_COMP_ |PHY_INT_MASK_LINK_DOWN_ )); -if (ret <0 ) { -netdev_warn (dev->net,"error enabling PHY wakeup ints\n" ); - goto done; - } -/* if link is down then configure EDPD and enter SUSPEND1, * otherwise enter SUSPEND0 below */
@@ -1767,11 +1780,12 @@ static int smsc95xx_resume(struct usb_interface *intf)return ret; } +phy_init_hw (pdata->phydev); + ret =usbnet_resume (intf);if (ret <0 )netdev_warn (dev->net,"usbnet_resume error\n" ); -phy_init_hw (pdata->phydev);return ret; }
Signature · 1 line
2.35.2
Andrew Lunn
maintainerRe: [PATCH net-next 5/7] usbnet: smsc95xx: Forward PHY interrupts to PHY driver to avoid polling
Quoted 6 lines
drivers/net/usb/smsc95xx.c +2
-0@@ -606,11 +616,20 @@ static void smsc95xx_status(struct usbnet *dev, struct urb *urb) intdata =get_unaligned_le32 (urb->transfer_buffer);netif_dbg (dev, link, dev->net,"intdata: 0x%08X\n" , intdata); +/* USB interrupts are received in softirq (tasklet) context. +* Switch to hardirq context to make genirq code happy.
drivers/net/usb/smsc95xx.c static void smsc95xx_status(struct usbnet *dev, struct urb *urb) · line 629
Quoted 10 lines
drivers/net/usb/smsc95xx.c +5
-1+*/ +local_irq_save (flags); +__irq_enter_raw (); +if (intdata &INT_ENP_PHY_INT_ ) - ; +generic_handle_domain_irq (pdata->irqdomain,PHY_HWIRQ );else netdev_warn (dev->net,"unexpected interrupt, intdata=0x%08X\n" , intdata);
Ah, O.K, forget my previous comment. Maybe add something to the commit message that the ; will soon be replaced by a call to actually handle the interrupt.
Andrew
Re: [PATCH net-next 5/7] usbnet: smsc95xx: Forward PHY interrupts to PHY driver to avoid polling
In reply to Lukas WunnerQuoted 48 lines · Lukas Wunner
Link status of SMSC LAN95xx chips is polled once per second, even though they're capable of signaling PHY interrupts through the MAC layer.
Forward those interrupts to the PHY driver to avoid polling. Benefits are reduced bus traffic, reduced CPU overhead and quicker interface bringup.
Polling was introduced in 2016 by commit
d69d16949346("usbnet:
smsc95xx: fix link detection for disabled autonegotiation"). Back then, the LAN95xx driver neglected to enable the ENERGYON interrupt, hence couldn't detect link-up events when auto-negotiation was disabled. The proper solution would have been to enable the ENERGYON interrupt instead of polling.Since then, PHY handling was moved from the LAN95xx driver to the SMSC PHY driver with commit
05b35e7eb9a1("smsc95xx: add phylib support"). That PHY driver is capable of link detection with auto-negotiation disabled because it enables the ENERGYON interrupt.Note that signaling interrupts through the MAC layer not only works with the integrated PHY, but also with an external PHY, provided its interrupt pin is attached to LAN95xx's
nPHY_INTpin.In the unlikely event that the interrupt pin of an external PHY is attached to a GPIO of the SoC (or not connected at all), the driver can be amended to retrieve the irq from the PHY's
of_node.To forward PHY interrupts to phylib, it is not sufficient to call
phy_mac_interrupt(). Instead, the PHY's interrupt handler needs to run so that PHY interrupts are cleared. That's because according to page 119 of the LAN950x datasheet, "The source of this interrupt is a level. The interrupt persists until it is cleared in the PHY."Therefore, create an IRQ domain with a single IRQ for the PHY. In the future, the IRQ domain may be extended to support the 11 GPIOs on the LAN95xx.
Normally the PHY interrupt should be masked until the PHY driver has cleared it. However masking requires a (sleeping) USB transaction and interrupts are received in (non-sleepable) softirq context. I decided not to mask the interrupt at all (by using the dummy_irq_chip's noop ->irq_mask() callback): The USB interrupt endpoint is polled in 1 msec intervals and normally that's sufficient to wake the PHY driver's IRQ thread and have it clear the interrupt. If it does take longer, worst thing that can happen is the IRQ thread is woken again. No big deal.
Quoted 6 lines
Because PHY interrupts are now perpetually enabled, there's no need to selectively enable them on suspend. So remove all invocations of
smsc95xx_enable_phy_wakeup_interrupts().Signed-off-by Lukas Wunner <[email protected]>Cc Andre Edich <[email protected]>
This looks reasonable from a PHY perspective. I cannot say much about USB though.
Andrew
PATCH 6/7 net: phy: smsc: Cache interrupt mask
In reply to Lukas WunnerCache the interrupt mask to avoid re-reading it from the PHY upon every interrupt. The PHY may be located on a USB device, so the additional read may unnecessarily increase interrupt overhead and latency.
--- drivers/net/phy/smsc.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-)
drivers/net/phy/smsc.c +11 -13
@@ -44,6 +44,7 @@ static struct smsc_hw_stat smsc_hw_stats[] = { };struct smsc_phy_priv { +u16 intmask;bool energy_enable;struct clk *refclk; };
@@ -58,7 +59,6 @@ static int smsc_phy_ack_interrupt(struct phy_device *phydev)static int smsc_phy_config_intr (struct phy_device *phydev) {struct smsc_phy_priv *priv = phydev->priv; -u16 intmask =0 ;int rc;if (phydev->interrupts ==PHY_INTERRUPT_ENABLED ) {
@@ -66,12 +66,15 @@ static int smsc_phy_config_intr(struct phy_device *phydev)if (rc)return rc; - intmask =MII_LAN83C185_ISF_INT4 |MII_LAN83C185_ISF_INT6 ; + priv->intmask =MII_LAN83C185_ISF_INT4 |MII_LAN83C185_ISF_INT6 ;if (priv->energy_enable) - intmask |=MII_LAN83C185_ISF_INT7 ; - rc =phy_write (phydev,MII_LAN83C185_IM , intmask); + priv->intmask |=MII_LAN83C185_ISF_INT7 ; + + rc =phy_write (phydev,MII_LAN83C185_IM , priv->intmask); }else { - rc =phy_write (phydev,MII_LAN83C185_IM , intmask); + priv->intmask =0 ; + + rc =phy_write (phydev,MII_LAN83C185_IM ,0 );if (rc)return rc;
@@ -83,13 +86,8 @@ static int smsc_phy_config_intr(struct phy_device *phydev)static irqreturn_t smsc_phy_handle_interrupt (struct phy_device *phydev) { -int irq_status, irq_enabled; - - irq_enabled =phy_read (phydev,MII_LAN83C185_IM ); -if (irq_enabled <0 ) { -phy_error (phydev); -return IRQ_NONE ; - } +struct smsc_phy_priv *priv = phydev->priv; +int irq_status; irq_status =phy_read (phydev,MII_LAN83C185_ISF );if (irq_status <0 ) {
@@ -97,7 +95,7 @@ static irqreturn_t smsc_phy_handle_interrupt(struct phy_device *phydev)return IRQ_NONE ; } -if (!(irq_status & irq_enabled)) +if (!(irq_status & priv->intmask))return IRQ_NONE ;phy_trigger_machine (phydev);
Signature · 1 line
2.35.2
Andrew Lunn
maintainerRe: [PATCH net-next 6/7] net: phy: smsc: Cache interrupt mask
Quoted 3 lines · Lukas Wunner
Cache the interrupt mask to avoid re-reading it from the PHY upon every interrupt. The PHY may be located on a USB device, so the additional read may unnecessarily increase interrupt overhead and latency.
I don't think your justification is valid. The MDIO bus is clocked at 2.5MHz. So even if you are using USB 1.1 at 12MHz, the USB overheads are not particularly large. At 480Mbps they are pretty insignificant.
In general, we consider PHYs as slow devices, they take over 1 second to negotiate a link and declare it up. So we don't do this sort of micro optimization.
What i think is relevant here is that you could have an interrupt storm going on because you don't mask interrupts? It is not a true storm, due to the way USB works, more of a light shower. Do you have any statistics to show this code actually reduces the amount of rain in a significant way?
Andrew
Re: [PATCH net-next 6/7] net: phy: smsc: Cache interrupt mask
Quoted 13 lines · Andrew Lunn
Quoted 3 lines · Lukas Wunner
Cache the interrupt mask to avoid re-reading it from the PHY upon every interrupt. The PHY may be located on a USB device, so the additional read may unnecessarily increase interrupt overhead and latency.
I don't think your justification is valid. The MDIO bus is clocked at 2.5MHz. So even if you are using USB 1.1 at 12MHz, the USB overheads are not particularly large. At 480Mbps they are pretty insignificant.
In general, we consider PHYs as slow devices, they take over 1 second to negotiate a link and declare it up. So we don't do this sort of micro optimization.
Quoted 5 lines
What i think is relevant here is that you could have an interrupt storm going on because you don't mask interrupts? It is not a true storm, due to the way USB works, more of a light shower. Do you have any statistics to show this code actually reduces the amount of rain in a significant way?
TBH the primary motivation for this change is that it simplifies the succeeding commit ("Cope with hot-removal in interrupt handler").
Additionally it seemed silly to me to re-read the interrupt mask every time for no reason at all. To test and debug this series I logged every MDIO read/write and these nonsensical transactions are very visible and very annoying in the log output.
So yeah, maybe the latency argument isn't very strong, but there are other arguments which I didn't deem necessary mentioning in the commit message as they seemed somewhat egotistical. :)
Thanks,
Lukas
PATCH 7/7 net: phy: smsc: Cope with hot-removal in interrupt handler
In reply to Lukas WunnerIf reading the Interrupt Source Flag register fails with -ENODEV, then the PHY has been hot-removed and the correct response is to bail out instead of throwing a WARN splat and attempting to suspend the PHY. The PHY should be stopped in due course anyway as the kernel asynchronously tears down the device.
--- drivers/net/phy/smsc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/net/phy/smsc.c +3 -1
@@ -91,7 +91,9 @@ static irqreturn_t smsc_phy_handle_interrupt(struct phy_device *phydev) irq_status =phy_read (phydev,MII_LAN83C185_ISF );if (irq_status <0 ) { -phy_error (phydev); +if (irq_status != -ENODEV ) +phy_error (phydev); +return IRQ_NONE ; }
Signature · 1 line
2.35.2
Oliver Neukum
maintainerRe: [PATCH net-next 7/7] net: phy: smsc: Cope with hot-removal in interrupt handler
Quoted 16 lines · Lukas Wunner
If reading the Interrupt Source Flag register fails with
-ENODEV, then the PHY has been hot-removed and the correct response is to bail out instead of throwing a WARN splat and attempting to suspend the PHY. The PHY should be stopped in due course anyway as the kernel asynchronously tears down the device.Signed-off-by Lukas Wunner <[email protected]>--- drivers/net/phy/smsc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)drivers/net/phy/smsc.c +0
-0
drivers/net/phy/smsc.c static irqreturn_t smsc_phy_handle_interrupt(struct phy_device *phydev) · line 99
Quoted 10 lines
drivers/net/phy/smsc.c +3
-1@@ -91,7 +91,9 @@ static irqreturn_t smsc_phy_handle_interrupt(struct phy_device *phydev) irq_status =phy_read (phydev,MII_LAN83C185_ISF );if (irq_status <0 ) { -phy_error (phydev); +if (irq_status != -ENODEV ) +phy_error (phydev); +return IRQ_NONE ; }
Hi,
picking a small nit, if you get ENODEV, you have no idea whether the irq was caused
by the phy. Strictly speaking you should return IRQ_HANDLED in that case.
Regards
Oliver
Quoted 9 lines · Lukas Wunner
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
The meat of the series is in patch [5/7]. The preceding and following patches are various cleanups to prepare for and adjust to interrupt-driven link state detection.
Please review and test. Thanks!
Tested on:
- LAN9514 (RPi2)
- LAN9512 (EVB)
- LAN9500 (EVB)
On USB unplug i get some not usable kernel message, but it is not the show stopper for me:
smsc95xx 1-1.4.1:1.0 eth1: Error updating MAC full duplex mode smsc95xx 1-1.4.1:1.0 eth1: hardware isn't capable of remote wakeup
Thank you!
Regards,
Oleksij
Signature · 4 lines
Pengutronix e.K. | | Steuerwalder Str. 21 | http://www.pengutronix.de/ | 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Quoted 9 lines · Oleksij Rempel
Quoted 7 lines · Lukas Wunner
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
The meat of the series is in patch [5/7]. The preceding and following patches are various cleanups to prepare for and adjust to interrupt-driven link state detection.
Quoted 6 lines
Tested-by Oleksij Rempel <[email protected]>Tested on:
- LAN9514 (RPi2)
- LAN9512 (EVB)
- LAN9500 (EVB)
Thanks a lot for testing, this helps greatly.
Quoted 4 lines
On USB unplug i get some not usable kernel message, but it is not the show stopper for me:
smsc95xx 1-1.4.1:1.0 eth1: Error updating MAC full duplex mode smsc95xx 1-1.4.1:1.0 eth1: hardware isn't capable of remote wakeup
Okay, I've amended patch [4/7] to silence the first of these messages if the error is caused by hot-removal.
The second message isn't related to this series, I'll address that separately at a later point in time.
Thanks,
Lukas
Hi,
Op 27-04-2022 om 07:48 schreef Lukas Wunner:
Quoted 20 lines
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
The meat of the series is in patch [5/7]. The preceding and following patches are various cleanups to prepare for and adjust to interrupt-driven link state detection.
Please review and test. Thanks!
Lukas Wunner (7):
usbnet: Rununregister_netdev()beforeunbind()again
usbnet: smsc95xx: Don't clear read-only PHY interrupt
usbnet: smsc95xx: Don't reset PHY behind PHY driver's back
usbnet: smsc95xx: Avoid link settings race on interrupt reception
usbnet: smsc95xx: Forward PHY interrupts to PHY driver to avoid
polling
net: phy: smsc: Cache interrupt mask
net: phy: smsc: Cope with hot-removal in interrupt handler
Quoted 6 lines
drivers/net/phy/smsc.c | 28 +++--- drivers/net/usb/asix_devices.c | 6 +- drivers/net/usb/smsc95xx.c | 155 ++++++++++++++++----------------- drivers/net/usb/usbnet.c | 6 +- 4 files changed, 91 insertions(+), 104 deletions(-)
(linux v5.17 + the series + usbnet: smsc95xx: Fix deadlock on runtime resume)
While testing I noted another problem. I have "DMA-API: debugging enabled by kernel config" and this (I guess) shows me before and after the patches:
------------[ cut here ]------------ DMA-API: xhci-hcd xhci-hcd.1.auto: cacheline tracking EEXIST, overlapping mappings aren't supported WARNING: CPU: 0 PID: 24 at kernel/dma/debug.c:570 add_dma_entry+0x1d9/0x270 Modules linked in: mmc_block extcon_intel_mrfld sdhci_pci cqhci sdhci led_class mmc_core intel_soc_pmic_mrfld btrfs libcrc32c xor zlib_deflate raid6_pq zstd_c> CPU: 0 PID: 24 Comm: kworker/0:1 Not tainted 5.17.0-edison-acpi-standard #1 Hardware name: Intel Corporation Merrifield/BODEGA BAY, BIOS 542 2015.01.21:18.19.48 Workqueue: usb_hub_wq hub_event RIP: 0010:add_dma_entry+0x1d9/0x270 Code: ff 0f 84 97 00 00 00 4c 8b 67 50 4d 85 e4 75 03 4c 8b 27 e8 29 a4 52 00 48 89 c6 4c 89 e2 48 c7 c7 78 9d 7e 98 e8 b3 f7 b6 00 <0f> 0b 48 85 ed 0f 85 d3 > RSP: 0018:ffffb2ce000d7ad0 EFLAGS: 00010286 RAX: 0000000000000000 RBX: 00000000ffffffff RCX: 0000000000000000 RDX: 0000000000000001 RSI: 00000000ffffffea RDI: 00000000ffffffff RBP: ffff9dea81239a00 R08: ffffffff98b356c8 R09: 00000000ffffdfff R10: ffffffff98a556e0 R11: ffffffff98a556e0 R12: ffff9dea87397180 R13: 0000000000000001 R14: 0000000000000206 R15: 0000000000046c59 FS: 0000000000000000(0000) GS:ffff9deabe200000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00005595807522e0 CR3: 0000000006be2000 CR4: 00000000001006f0 Call Trace: <TASK> dma_map_page_attrs+0xfc/0x240 ? usb_hcd_link_urb_to_ep+0x6e/0xa0 ? preempt_count_add+0x63/0x90 usb_hcd_map_urb_for_dma+0x3f3/0x580 usb_hcd_submit_urb+0x93/0xb90 ? _raw_spin_lock+0xe/0x30 ? _raw_spin_unlock+0xd/0x20 ? usb_hcd_link_urb_to_ep+0x6e/0xa0 ? prepare_transfer+0xff/0x140 usb_start_wait_urb+0x60/0x160 usb_control_msg+0xdc/0x140 hub_ext_port_status+0x82/0x100 hub_event+0x1b2/0x1880 ? hub_activate+0x59c/0x880 process_one_work+0x1d3/0x3a0 worker_thread+0x48/0x3c0 ? rescuer_thread+0x380/0x380 kthread+0xe2/0x110 ? kthread_complete_and_exit+0x20/0x20 ret_from_fork+0x22/0x30 </TASK> ---[ end trace 0000000000000000 ]--- DMA-API: Mapped at: debug_dma_map_page+0x60/0xf0 dma_map_page_attrs+0xfc/0x240 usb_hcd_map_urb_for_dma+0x3f3/0x580 usb_hcd_submit_urb+0x93/0xb90 usb_start_wait_urb+0x60/0x160
usb 1-1.1: new high-speed USB device number 3 using xhci-hcd
usb 1-1.1: New USB device found, idVendor=0424, idProduct=ec00, bcdDevice= 2.00 usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1.1: Product: LAN9514
usb 1-1.1: Manufacturer: SMSC
usb 1-1.1: SerialNumber: 00951d0d
Any ideas on this?
Quoted 6 lines · Ferry Toth
Op 27-04-2022 om 07:48 schreef Lukas Wunner:
Quoted 3 lines
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
Thank you!
Quoted 6 lines
While testing I noted another problem. I have "DMA-API: debugging enabled by kernel config" and this (I guess) shows me before and after the patches:
------------[ cut here ]------------ DMA-API: xhci-hcd xhci-hcd.1.auto: cacheline tracking EEXIST, overlappingmappings aren't supported
That is under investigation here:
https://bugzilla.kernel.org/show_bug.cgi?id=215740
It's apparently a long-standing bug in the USB core which was exposed by a new WARN() check introduced in 5.14.
Thanks,
Lukas
Hi
Quoted 11 lines · Lukas Wunner
Quoted 5 lines · Ferry Toth
Op 27-04-2022 om 07:48 schreef Lukas Wunner:
Quoted 3 lines
Do away with link status polling on LAN95XX USB Ethernet and rely on interrupts instead, thereby reducing bus traffic, CPU overhead and improving interface bringup latency.
Thank you!
Quoted 3 lines
While testing I noted another problem. I have "DMA-API: debugging enabled by kernel config" and this (I guess) shows me before and after the patches:
Quoted 8 lines
Quoted 3 lines
------------[ cut here ]------------ DMA-API: xhci-hcd xhci-hcd.1.auto: cacheline tracking EEXIST, overlappingmappings aren't supported
That is under investigation here:
https://bugzilla.kernel.org/show_bug.cgi?id=215740It's apparently a long-standing bug in the USB core which was exposed by a new
WARN()check introduced in 5.14.
I'm not sure this is correct. The issue happens for me only when connecting the SMSC9414.
Other usb devices I have (memory sticks, wifi, bluetooth) do not trigger this.
I think we need to consider it might be a valid warning. It seems to be originating from the same "Workqueue: usb_hub_wq hub_event" though.
Quoted 3 lines
Thanks,
Lukas
Quoted 12 lines · Ferry Toth
Quoted 11 lines · Lukas Wunner
Quoted 6 lines · Ferry Toth
I have "DMA-API: debugging enabled by kernel config" and this (I guess) shows me before and after the patches:
------------[ cut here ]------------ DMA-API: xhci-hcd xhci-hcd.1.auto: cacheline tracking EEXIST, overlappingmappings aren't supported
That is under investigation here:
https://bugzilla.kernel.org/show_bug.cgi?id=215740
Quoted 8 lines
Quoted 2 lines
It's apparently a long-standing bug in the USB core which was exposed by a new
WARN()check introduced in 5.14.I'm not sure this is correct. The issue happens for me only when connecting the SMSC9414.
Other usb devices I have (memory sticks, wifi, bluetooth) do not trigger this.
Hm, I've taken the liberty to add that information to the bugzilla.
Thanks,
Lukas