drivers: eth: phy_mii: Don't block system workqueue

Looping while waiting for auto-negotiation to complete can block the
system workqueue for several seconds.

Signed-off-by: Kevin ORourke <kevin.orourke@ferroamp.se>
This commit is contained in:
Kevin ORourke 2025-03-14 13:13:54 +01:00 committed by Benjamin Cabé
commit 9e2752d9d8

View file

@ -31,9 +31,11 @@ struct phy_mii_dev_data {
phy_callback_t cb; phy_callback_t cb;
void *cb_data; void *cb_data;
struct k_work_delayable monitor_work; struct k_work_delayable monitor_work;
struct k_work_delayable autoneg_work;
struct phy_link_state state; struct phy_link_state state;
struct k_sem sem; struct k_sem sem;
bool gigabit_supported; bool gigabit_supported;
uint32_t autoneg_timeout;
}; };
/* Offset to align capabilities bits of 1000BASE-T Control and Status regs */ /* Offset to align capabilities bits of 1000BASE-T Control and Status regs */
@ -41,6 +43,9 @@ struct phy_mii_dev_data {
#define MII_INVALID_PHY_ID UINT32_MAX #define MII_INVALID_PHY_ID UINT32_MAX
/* How often to poll auto-negotiation status while waiting for it to complete */
#define MII_AUTONEG_POLL_INTERVAL_MS 100
static int phy_mii_get_link_state(const struct device *dev, static int phy_mii_get_link_state(const struct device *dev,
struct phy_link_state *state); struct phy_link_state *state);
@ -155,13 +160,8 @@ static int update_link_state(const struct device *dev)
struct phy_mii_dev_data *const data = dev->data; struct phy_mii_dev_data *const data = dev->data;
bool link_up; bool link_up;
uint16_t anar_reg = 0;
uint16_t bmcr_reg = 0; uint16_t bmcr_reg = 0;
uint16_t bmsr_reg = 0; uint16_t bmsr_reg = 0;
uint16_t anlpar_reg = 0;
uint16_t c1kt_reg = 0;
uint16_t s1kt_reg = 0;
uint32_t timeout = CONFIG_PHY_AUTONEG_TIMEOUT_MS / 100;
if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) {
return -EIO; return -EIO;
@ -188,11 +188,6 @@ static int update_link_state(const struct device *dev)
LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence",
cfg->phy_addr); cfg->phy_addr);
/* Read PHY default advertising parameters */
if (phy_mii_reg_read(dev, MII_ANAR, &anar_reg) < 0) {
return -EIO;
}
/* Configure and start auto-negotiation process */ /* Configure and start auto-negotiation process */
if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) {
return -EIO; return -EIO;
@ -205,15 +200,21 @@ static int update_link_state(const struct device *dev)
return -EIO; return -EIO;
} }
/* Wait for the auto-negotiation process to complete */ /* We have to wait for the auto-negotiation process to complete */
do { data->autoneg_timeout = CONFIG_PHY_AUTONEG_TIMEOUT_MS / MII_AUTONEG_POLL_INTERVAL_MS;
if (timeout-- == 0U) { return -EINPROGRESS;
LOG_DBG("PHY (%d) auto-negotiate timedout", }
cfg->phy_addr);
return -ETIMEDOUT;
}
k_sleep(K_MSEC(100)); static int check_autonegotiation_completion(const struct device *dev)
{
const struct phy_mii_dev_config *const cfg = dev->config;
struct phy_mii_dev_data *const data = dev->data;
uint16_t anar_reg = 0;
uint16_t bmsr_reg = 0;
uint16_t anlpar_reg = 0;
uint16_t c1kt_reg = 0;
uint16_t s1kt_reg = 0;
/* On some PHY chips, the BMSR bits are latched, so the first read may /* On some PHY chips, the BMSR bits are latched, so the first read may
* show incorrect status. A second read ensures correct values. * show incorrect status. A second read ensures correct values.
@ -226,11 +227,24 @@ static int update_link_state(const struct device *dev)
if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) { if (phy_mii_reg_read(dev, MII_BMSR, &bmsr_reg) < 0) {
return -EIO; return -EIO;
} }
} while (!(bmsr_reg & MII_BMSR_AUTONEG_COMPLETE));
if (!(bmsr_reg & MII_BMSR_AUTONEG_COMPLETE)) {
if (data->autoneg_timeout-- == 0U) {
LOG_DBG("PHY (%d) auto-negotiate timedout",
cfg->phy_addr);
return -ETIMEDOUT;
}
return -EINPROGRESS;
}
LOG_DBG("PHY (%d) auto-negotiate sequence completed", LOG_DBG("PHY (%d) auto-negotiate sequence completed",
cfg->phy_addr); cfg->phy_addr);
/* Read PHY default advertising parameters */
if (phy_mii_reg_read(dev, MII_ANAR, &anar_reg) < 0) {
return -EIO;
}
/** Read peer device capability */ /** Read peer device capability */
if (phy_mii_reg_read(dev, MII_ANLPAR, &anlpar_reg) < 0) { if (phy_mii_reg_read(dev, MII_ANLPAR, &anlpar_reg) < 0) {
return -EIO; return -EIO;
@ -293,7 +307,12 @@ static void monitor_work_handler(struct k_work *work)
const struct device *dev = data->dev; const struct device *dev = data->dev;
int rc; int rc;
k_sem_take(&data->sem, K_FOREVER); if (k_sem_take(&data->sem, K_NO_WAIT) != 0) {
/* Try again soon */
k_work_reschedule(&data->monitor_work,
K_MSEC(MII_AUTONEG_POLL_INTERVAL_MS));
return;
}
rc = update_link_state(dev); rc = update_link_state(dev);
@ -304,9 +323,50 @@ static void monitor_work_handler(struct k_work *work)
invoke_link_cb(dev); invoke_link_cb(dev);
} }
if (rc == -EINPROGRESS) {
/* Check for autonegotiation completion */
k_work_reschedule(&data->autoneg_work,
K_MSEC(MII_AUTONEG_POLL_INTERVAL_MS));
} else {
/* Submit delayed work */ /* Submit delayed work */
k_work_reschedule(&data->monitor_work, k_work_reschedule(&data->monitor_work,
K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
}
}
static void autoneg_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct phy_mii_dev_data *const data =
CONTAINER_OF(dwork, struct phy_mii_dev_data, autoneg_work);
const struct device *dev = data->dev;
int rc;
if (k_sem_take(&data->sem, K_NO_WAIT) != 0) {
/* Try again soon */
k_work_reschedule(&data->autoneg_work,
K_MSEC(MII_AUTONEG_POLL_INTERVAL_MS));
return;
}
rc = check_autonegotiation_completion(dev);
k_sem_give(&data->sem);
/* If link state has changed and a callback is set, invoke callback */
if (rc == 0) {
invoke_link_cb(dev);
}
if (rc == -EINPROGRESS) {
/* Check again soon */
k_work_reschedule(&data->autoneg_work,
K_MSEC(MII_AUTONEG_POLL_INTERVAL_MS));
} else {
/* Schedule the next monitoring call */
k_work_reschedule(&data->monitor_work,
K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
}
} }
static int phy_mii_read(const struct device *dev, uint16_t reg_addr, static int phy_mii_read(const struct device *dev, uint16_t reg_addr,
@ -499,6 +559,8 @@ static int phy_mii_initialize(const struct device *dev)
k_work_init_delayable(&data->monitor_work, k_work_init_delayable(&data->monitor_work,
monitor_work_handler); monitor_work_handler);
k_work_init_delayable(&data->autoneg_work,
autoneg_work_handler);
monitor_work_handler(&data->monitor_work.work); monitor_work_handler(&data->monitor_work.work);
} }