Skip to content

Commit

Permalink
net: ethernet: fec: Replace interrupt driven MDIO with polled IO
Browse files Browse the repository at this point in the history
Measurements of the MDIO bus have shown that driving the MDIO bus
using interrupts is slow. Back to back MDIO transactions take about
90us, with 25us spent performing the transaction, and the remainder of
the time the bus is idle.

Replacing the completion interrupt with polled IO results in back to
back transactions of 40us. The polling loop waiting for the hardware
to complete the transaction takes around 28us. Which suggests
interrupt handling has an overhead of 50us, and polled IO nearly
halves this overhead, and doubles the MDIO performance.

Care has to be taken when setting the MII_SPEED register, or it can
trigger an MII event> That then upsets the polling, due to an
unexpected pending event.

Suggested-by: Chris Heally <cphealy@gmail.com>
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Andrew Lunn authored and David S. Miller committed May 2, 2020
1 parent 40b9422 commit f166f89
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 36 deletions.
4 changes: 1 addition & 3 deletions drivers/net/ethernet/freescale/fec.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,7 @@ struct bufdesc_ex {
#define FEC_ENET_TS_AVAIL ((uint)0x00010000)
#define FEC_ENET_TS_TIMER ((uint)0x00008000)

#define FEC_DEFAULT_IMASK (FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII)
#define FEC_NAPI_IMASK FEC_ENET_MII
#define FEC_DEFAULT_IMASK (FEC_ENET_TXF | FEC_ENET_RXF)
#define FEC_RX_DISABLED_IMASK (FEC_DEFAULT_IMASK & (~FEC_ENET_RXF))

/* ENET interrupt coalescing macro define */
Expand Down Expand Up @@ -543,7 +542,6 @@ struct fec_enet_private {
int link;
int full_duplex;
int speed;
struct completion mdio_done;
int irq[FEC_IRQ_NUM];
bool bufdesc_ex;
int pause_flag;
Expand Down
77 changes: 44 additions & 33 deletions drivers/net/ethernet/freescale/fec_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -976,8 +976,8 @@ fec_restart(struct net_device *ndev)
writel((__force u32)cpu_to_be32(temp_mac[1]),
fep->hwp + FEC_ADDR_HIGH);

/* Clear any outstanding interrupt. */
writel(0xffffffff, fep->hwp + FEC_IEVENT);
/* Clear any outstanding interrupt, except MDIO. */
writel((0xffffffff & ~FEC_ENET_MII), fep->hwp + FEC_IEVENT);

fec_enet_bd_init(ndev);

Expand Down Expand Up @@ -1123,7 +1123,7 @@ fec_restart(struct net_device *ndev)
if (fep->link)
writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
else
writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);
writel(0, fep->hwp + FEC_IMASK);

/* Init the interrupt coalescing */
fec_enet_itr_coal_init(ndev);
Expand Down Expand Up @@ -1652,23 +1652,23 @@ fec_enet_interrupt(int irq, void *dev_id)
irqreturn_t ret = IRQ_NONE;

int_events = readl(fep->hwp + FEC_IEVENT);

/* Don't clear MDIO events, we poll for those */
int_events &= ~FEC_ENET_MII;

writel(int_events, fep->hwp + FEC_IEVENT);
fec_enet_collect_events(fep, int_events);

if ((fep->work_tx || fep->work_rx) && fep->link) {
ret = IRQ_HANDLED;

if (napi_schedule_prep(&fep->napi)) {
/* Disable the NAPI interrupts */
writel(FEC_NAPI_IMASK, fep->hwp + FEC_IMASK);
/* Disable interrupts */
writel(0, fep->hwp + FEC_IMASK);
__napi_schedule(&fep->napi);
}
}

if (int_events & FEC_ENET_MII) {
ret = IRQ_HANDLED;
complete(&fep->mdio_done);
}
return ret;
}

Expand Down Expand Up @@ -1818,20 +1818,31 @@ static void fec_enet_adjust_link(struct net_device *ndev)
phy_print_status(phy_dev);
}

static int fec_enet_mdio_wait(struct fec_enet_private *fep)
{
uint ievent;
int ret;

ret = readl_poll_timeout_atomic(fep->hwp + FEC_IEVENT, ievent,
ievent & FEC_ENET_MII, 2, 30000);

if (!ret)
writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);

return ret;
}

static int fec_enet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
struct fec_enet_private *fep = bus->priv;
struct device *dev = &fep->pdev->dev;
unsigned long time_left;
int ret = 0, frame_start, frame_addr, frame_op;
bool is_c45 = !!(regnum & MII_ADDR_C45);

ret = pm_runtime_get_sync(dev);
if (ret < 0)
return ret;

reinit_completion(&fep->mdio_done);

if (is_c45) {
frame_start = FEC_MMFR_ST_C45;

Expand All @@ -1843,11 +1854,9 @@ static int fec_enet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
fep->hwp + FEC_MII_DATA);

/* wait for end of transfer */
time_left = wait_for_completion_timeout(&fep->mdio_done,
usecs_to_jiffies(FEC_MII_TIMEOUT));
if (time_left == 0) {
ret = fec_enet_mdio_wait(fep);
if (ret) {
netdev_err(fep->netdev, "MDIO address write timeout\n");
ret = -ETIMEDOUT;
goto out;
}

Expand All @@ -1866,11 +1875,9 @@ static int fec_enet_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
FEC_MMFR_TA, fep->hwp + FEC_MII_DATA);

/* wait for end of transfer */
time_left = wait_for_completion_timeout(&fep->mdio_done,
usecs_to_jiffies(FEC_MII_TIMEOUT));
if (time_left == 0) {
ret = fec_enet_mdio_wait(fep);
if (ret) {
netdev_err(fep->netdev, "MDIO read timeout\n");
ret = -ETIMEDOUT;
goto out;
}

Expand All @@ -1888,7 +1895,6 @@ static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
{
struct fec_enet_private *fep = bus->priv;
struct device *dev = &fep->pdev->dev;
unsigned long time_left;
int ret, frame_start, frame_addr;
bool is_c45 = !!(regnum & MII_ADDR_C45);

Expand All @@ -1898,8 +1904,6 @@ static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
else
ret = 0;

reinit_completion(&fep->mdio_done);

if (is_c45) {
frame_start = FEC_MMFR_ST_C45;

Expand All @@ -1911,11 +1915,9 @@ static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
fep->hwp + FEC_MII_DATA);

/* wait for end of transfer */
time_left = wait_for_completion_timeout(&fep->mdio_done,
usecs_to_jiffies(FEC_MII_TIMEOUT));
if (time_left == 0) {
ret = fec_enet_mdio_wait(fep);
if (ret) {
netdev_err(fep->netdev, "MDIO address write timeout\n");
ret = -ETIMEDOUT;
goto out;
}
} else {
Expand All @@ -1931,12 +1933,9 @@ static int fec_enet_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
fep->hwp + FEC_MII_DATA);

/* wait for end of transfer */
time_left = wait_for_completion_timeout(&fep->mdio_done,
usecs_to_jiffies(FEC_MII_TIMEOUT));
if (time_left == 0) {
ret = fec_enet_mdio_wait(fep);
if (ret)
netdev_err(fep->netdev, "MDIO write timeout\n");
ret = -ETIMEDOUT;
}

out:
pm_runtime_mark_last_busy(dev);
Expand Down Expand Up @@ -2143,8 +2142,21 @@ static int fec_enet_mii_init(struct platform_device *pdev)
if (suppress_preamble)
fep->phy_speed |= BIT(7);

/* Clear MMFR to avoid to generate MII event by writing MSCR.
* MII event generation condition:
* - writing MSCR:
* - mmfr[31:0]_not_zero & mscr[7:0]_is_zero &
* mscr_reg_data_in[7:0] != 0
* - writing MMFR:
* - mscr[7:0]_not_zero
*/
writel(0, fep->hwp + FEC_MII_DATA);

writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);

/* Clear any pending transaction complete indication */
writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);

fep->mii_bus = mdiobus_alloc();
if (fep->mii_bus == NULL) {
err = -ENOMEM;
Expand Down Expand Up @@ -3686,7 +3698,6 @@ fec_probe(struct platform_device *pdev)
fep->irq[i] = irq;
}

init_completion(&fep->mdio_done);
ret = fec_enet_mii_init(pdev);
if (ret)
goto failed_mii_init;
Expand Down

0 comments on commit f166f89

Please sign in to comment.