Merge branch 'prevent-nullptr-exceptions-in-isr'

Andre Werner says:

====================
Prevent nullptr exceptions in ISR

In case phydev->irq is modified unconditionally to a valid IRQ, handling
the IRQ may lead to a nullptr exception if no interrupt handler is
registered to the phy driver. phy_interrupt calls a
phy_device->handle_interrupt unconditionally. And interrupts are enabled
in phy_connect_direct if phydev->irq is not equal to PHY_POLL or
PHY_MAC_INTERRUPT, so it does not check for a phy driver providing an ISR.

Adding an additonal check for a valid interrupt handler in phy_attach_direct
function, and falling back to polling mode if not, should prevent for
such nullptr exceptions.

Moreover, the ADIN1100 phy driver is extended with an interrupt handler
for changes in the link status.
====================

Link: https://lore.kernel.org/r/20240129135734.18975-1-andre.werner@systec-electronic.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2024-01-31 16:22:31 -08:00
commit 3a78983d26
2 changed files with 63 additions and 5 deletions

View File

@ -18,6 +18,12 @@
#define PHY_ID_ADIN1110 0x0283bc91
#define PHY_ID_ADIN2111 0x0283bca1
#define ADIN_PHY_SUBSYS_IRQ_MASK 0x0021
#define ADIN_LINK_STAT_CHNG_IRQ_EN BIT(1)
#define ADIN_PHY_SUBSYS_IRQ_STATUS 0x0011
#define ADIN_LINK_STAT_CHNG BIT(1)
#define ADIN_FORCED_MODE 0x8000
#define ADIN_FORCED_MODE_EN BIT(0)
@ -136,6 +142,53 @@ static int adin_config_aneg(struct phy_device *phydev)
return genphy_c45_config_aneg(phydev);
}
static int adin_phy_ack_intr(struct phy_device *phydev)
{
/* Clear pending interrupts */
int rc = phy_read_mmd(phydev, MDIO_MMD_VEND2,
ADIN_PHY_SUBSYS_IRQ_STATUS);
return rc < 0 ? rc : 0;
}
static int adin_config_intr(struct phy_device *phydev)
{
u16 irq_mask;
int ret;
ret = adin_phy_ack_intr(phydev);
if (ret)
return ret;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
irq_mask = ADIN_LINK_STAT_CHNG_IRQ_EN;
else
irq_mask = 0;
return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
ADIN_PHY_SUBSYS_IRQ_MASK,
ADIN_LINK_STAT_CHNG_IRQ_EN, irq_mask);
}
static irqreturn_t adin_phy_handle_interrupt(struct phy_device *phydev)
{
int irq_status;
irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2,
ADIN_PHY_SUBSYS_IRQ_STATUS);
if (irq_status < 0) {
phy_error(phydev);
return IRQ_NONE;
}
if (!(irq_status & ADIN_LINK_STAT_CHNG))
return IRQ_NONE;
phy_trigger_machine(phydev);
return IRQ_HANDLED;
}
static int adin_set_powerdown_mode(struct phy_device *phydev, bool en)
{
int ret;
@ -275,6 +328,8 @@ static struct phy_driver adin_driver[] = {
.probe = adin_probe,
.config_aneg = adin_config_aneg,
.read_status = adin_read_status,
.config_intr = adin_config_intr,
.handle_interrupt = adin_phy_handle_interrupt,
.set_loopback = adin_set_loopback,
.suspend = adin_suspend,
.resume = adin_resume,

View File

@ -1413,6 +1413,11 @@ int phy_sfp_probe(struct phy_device *phydev,
}
EXPORT_SYMBOL(phy_sfp_probe);
static bool phy_drv_supports_irq(struct phy_driver *phydrv)
{
return phydrv->config_intr && phydrv->handle_interrupt;
}
/**
* phy_attach_direct - attach a network device to a given PHY device pointer
* @dev: network device to attach
@ -1527,6 +1532,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
if (phydev->dev_flags & PHY_F_NO_IRQ)
phydev->irq = PHY_POLL;
if (!phy_drv_supports_irq(phydev->drv) && phy_interrupt_is_valid(phydev))
phydev->irq = PHY_POLL;
/* Port is set to PORT_TP by default and the actual PHY driver will set
* it to different value depending on the PHY configuration. If we have
* the generic PHY driver we can't figure it out, thus set the old
@ -2992,11 +3000,6 @@ s32 phy_get_internal_delay(struct phy_device *phydev, struct device *dev,
}
EXPORT_SYMBOL(phy_get_internal_delay);
static bool phy_drv_supports_irq(struct phy_driver *phydrv)
{
return phydrv->config_intr && phydrv->handle_interrupt;
}
static int phy_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{