aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJohn Efstathiades2021-08-24 19:56:10 +0100
committerDavid S. Miller2021-08-25 10:55:43 +0100
commite1210fe63bf8b080edd0805240e90b81b6b069c1 (patch)
treed5924ebbd4fe772919dfc1a5bb51f9ffb353ef88 /drivers
parentb1f6696daafebea243ed59ed18a8b10cfd33b474 (diff)
lan78xx: Fix partial packet errors on suspend/resume
The MAC can get out of step with the internal packet FIFOs if the system goes to sleep when the link is active, especially at high data rates. This can result in partial frames in the packet FIFOs that in result in malformed frames being delivered to the host. This occurs because the driver does not enable/disable the internal packet FIFOs in step with the corresponding MAC data path. The following changes fix this problem. Update code that enables/disables the MAC receiver and transmitter to the more general Rx and Tx data path, where the data path in each direction consists of both the MAC function (Tx or Rx) and the corresponding packet FIFO. In the receive path the packet FIFO must be enabled before the MAC receiver but disabled after the MAC receiver. In the transmit path the opposite is true: the packet FIFO must be enabled after the MAC transmitter but disabled before the MAC transmitter. The packet FIFOs can be flushed safely once the corresponding data path is stopped. Signed-off-by: John Efstathiades <john.efstathiades@pebblebay.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/usb/lan78xx.c303
1 files changed, 197 insertions, 106 deletions
diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c
index 2eb853b13c2a..9170a786a24c 100644
--- a/drivers/net/usb/lan78xx.c
+++ b/drivers/net/usb/lan78xx.c
@@ -100,6 +100,12 @@
/* statistic update interval (mSec) */
#define STAT_UPDATE_TIMER (1 * 1000)
+/* time to wait for MAC or FCT to stop (jiffies) */
+#define HW_DISABLE_TIMEOUT (HZ / 10)
+
+/* time to wait between polling MAC or FCT state (ms) */
+#define HW_DISABLE_DELAY_MS 1
+
/* defines interrupts from interrupt EP */
#define MAX_INT_EP (32)
#define INT_EP_INTEP (31)
@@ -487,6 +493,26 @@ static int lan78xx_write_reg(struct lan78xx_net *dev, u32 index, u32 data)
return ret;
}
+static int lan78xx_update_reg(struct lan78xx_net *dev, u32 reg, u32 mask,
+ u32 data)
+{
+ int ret;
+ u32 buf;
+
+ ret = lan78xx_read_reg(dev, reg, &buf);
+ if (ret < 0)
+ return ret;
+
+ buf &= ~mask;
+ buf |= (mask & data);
+
+ ret = lan78xx_write_reg(dev, reg, buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int lan78xx_read_stats(struct lan78xx_net *dev,
struct lan78xx_statstage *data)
{
@@ -2513,6 +2539,156 @@ static void lan78xx_init_ltm(struct lan78xx_net *dev)
lan78xx_write_reg(dev, LTM_INACTIVE1, regs[5]);
}
+static int lan78xx_start_hw(struct lan78xx_net *dev, u32 reg, u32 hw_enable)
+{
+ return lan78xx_update_reg(dev, reg, hw_enable, hw_enable);
+}
+
+static int lan78xx_stop_hw(struct lan78xx_net *dev, u32 reg, u32 hw_enabled,
+ u32 hw_disabled)
+{
+ unsigned long timeout;
+ bool stopped = true;
+ int ret;
+ u32 buf;
+
+ /* Stop the h/w block (if not already stopped) */
+
+ ret = lan78xx_read_reg(dev, reg, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (buf & hw_enabled) {
+ buf &= ~hw_enabled;
+
+ ret = lan78xx_write_reg(dev, reg, buf);
+ if (ret < 0)
+ return ret;
+
+ stopped = false;
+ timeout = jiffies + HW_DISABLE_TIMEOUT;
+ do {
+ ret = lan78xx_read_reg(dev, reg, &buf);
+ if (ret < 0)
+ return ret;
+
+ if (buf & hw_disabled)
+ stopped = true;
+ else
+ msleep(HW_DISABLE_DELAY_MS);
+ } while (!stopped && !time_after(jiffies, timeout));
+ }
+
+ ret = stopped ? 0 : -ETIME;
+
+ return ret;
+}
+
+static int lan78xx_flush_fifo(struct lan78xx_net *dev, u32 reg, u32 fifo_flush)
+{
+ return lan78xx_update_reg(dev, reg, fifo_flush, fifo_flush);
+}
+
+static int lan78xx_start_tx_path(struct lan78xx_net *dev)
+{
+ int ret;
+
+ netif_dbg(dev, drv, dev->net, "start tx path");
+
+ /* Start the MAC transmitter */
+
+ ret = lan78xx_start_hw(dev, MAC_TX, MAC_TX_TXEN_);
+ if (ret < 0)
+ return ret;
+
+ /* Start the Tx FIFO */
+
+ ret = lan78xx_start_hw(dev, FCT_TX_CTL, FCT_TX_CTL_EN_);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int lan78xx_stop_tx_path(struct lan78xx_net *dev)
+{
+ int ret;
+
+ netif_dbg(dev, drv, dev->net, "stop tx path");
+
+ /* Stop the Tx FIFO */
+
+ ret = lan78xx_stop_hw(dev, FCT_TX_CTL, FCT_TX_CTL_EN_, FCT_TX_CTL_DIS_);
+ if (ret < 0)
+ return ret;
+
+ /* Stop the MAC transmitter */
+
+ ret = lan78xx_stop_hw(dev, MAC_TX, MAC_TX_TXEN_, MAC_TX_TXD_);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* The caller must ensure the Tx path is stopped before calling
+ * lan78xx_flush_tx_fifo().
+ */
+static int lan78xx_flush_tx_fifo(struct lan78xx_net *dev)
+{
+ return lan78xx_flush_fifo(dev, FCT_TX_CTL, FCT_TX_CTL_RST_);
+}
+
+static int lan78xx_start_rx_path(struct lan78xx_net *dev)
+{
+ int ret;
+
+ netif_dbg(dev, drv, dev->net, "start rx path");
+
+ /* Start the Rx FIFO */
+
+ ret = lan78xx_start_hw(dev, FCT_RX_CTL, FCT_RX_CTL_EN_);
+ if (ret < 0)
+ return ret;
+
+ /* Start the MAC receiver*/
+
+ ret = lan78xx_start_hw(dev, MAC_RX, MAC_RX_RXEN_);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int lan78xx_stop_rx_path(struct lan78xx_net *dev)
+{
+ int ret;
+
+ netif_dbg(dev, drv, dev->net, "stop rx path");
+
+ /* Stop the MAC receiver */
+
+ ret = lan78xx_stop_hw(dev, MAC_RX, MAC_RX_RXEN_, MAC_RX_RXD_);
+ if (ret < 0)
+ return ret;
+
+ /* Stop the Rx FIFO */
+
+ ret = lan78xx_stop_hw(dev, FCT_RX_CTL, FCT_RX_CTL_EN_, FCT_RX_CTL_DIS_);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* The caller must ensure the Rx path is stopped before calling
+ * lan78xx_flush_rx_fifo().
+ */
+static int lan78xx_flush_rx_fifo(struct lan78xx_net *dev)
+{
+ return lan78xx_flush_fifo(dev, FCT_RX_CTL, FCT_RX_CTL_RST_);
+}
+
static int lan78xx_reset(struct lan78xx_net *dev)
{
struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]);
@@ -2703,23 +2879,7 @@ static int lan78xx_reset(struct lan78xx_net *dev)
if (ret < 0)
return ret;
- ret = lan78xx_read_reg(dev, MAC_TX, &buf);
- if (ret < 0)
- return ret;
-
- buf |= MAC_TX_TXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_TX, buf);
- if (ret < 0)
- return ret;
-
- ret = lan78xx_read_reg(dev, FCT_TX_CTL, &buf);
- if (ret < 0)
- return ret;
-
- buf |= FCT_TX_CTL_EN_;
-
- ret = lan78xx_write_reg(dev, FCT_TX_CTL, buf);
+ ret = lan78xx_start_tx_path(dev);
if (ret < 0)
return ret;
@@ -2728,27 +2888,9 @@ static int lan78xx_reset(struct lan78xx_net *dev)
if (ret < 0)
return ret;
- ret = lan78xx_read_reg(dev, MAC_RX, &buf);
- if (ret < 0)
- return ret;
-
- buf |= MAC_RX_RXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_RX, buf);
- if (ret < 0)
- return ret;
+ ret = lan78xx_start_rx_path(dev);
- ret = lan78xx_read_reg(dev, FCT_RX_CTL, &buf);
- if (ret < 0)
- return ret;
-
- buf |= FCT_RX_CTL_EN_;
-
- ret = lan78xx_write_reg(dev, FCT_RX_CTL, buf);
- if (ret < 0)
- return ret;
-
- return 0;
+ return ret;
}
static void lan78xx_init_stats(struct lan78xx_net *dev)
@@ -3970,23 +4112,10 @@ static int lan78xx_set_suspend(struct lan78xx_net *dev, u32 wol)
u16 crc;
int ret;
- ret = lan78xx_read_reg(dev, MAC_TX, &buf);
+ ret = lan78xx_stop_tx_path(dev);
if (ret < 0)
return ret;
-
- buf &= ~MAC_TX_TXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_TX, buf);
- if (ret < 0)
- return ret;
-
- ret = lan78xx_read_reg(dev, MAC_RX, &buf);
- if (ret < 0)
- return ret;
-
- buf &= ~MAC_RX_RXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_RX, buf);
+ ret = lan78xx_stop_rx_path(dev);
if (ret < 0)
return ret;
@@ -4163,17 +4292,9 @@ static int lan78xx_set_suspend(struct lan78xx_net *dev, u32 wol)
if (ret < 0)
return ret;
- lan78xx_read_reg(dev, MAC_RX, &buf);
- if (ret < 0)
- return ret;
+ ret = lan78xx_start_rx_path(dev);
- buf |= MAC_RX_RXEN_;
-
- lan78xx_write_reg(dev, MAC_RX, buf);
- if (ret < 0)
- return ret;
-
- return 0;
+ return ret;
}
static int lan78xx_suspend(struct usb_interface *intf, pm_message_t message)
@@ -4196,24 +4317,17 @@ static int lan78xx_suspend(struct usb_interface *intf, pm_message_t message)
spin_unlock_irq(&dev->txq.lock);
}
- /* stop TX & RX */
- ret = lan78xx_read_reg(dev, MAC_TX, &buf);
+ /* stop RX */
+ ret = lan78xx_stop_rx_path(dev);
if (ret < 0)
return ret;
- buf &= ~MAC_TX_TXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_TX, buf);
+ ret = lan78xx_flush_rx_fifo(dev);
if (ret < 0)
return ret;
- ret = lan78xx_read_reg(dev, MAC_RX, &buf);
- if (ret < 0)
- return ret;
-
- buf &= ~MAC_RX_RXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_RX, buf);
+ /* stop Tx */
+ ret = lan78xx_stop_tx_path(dev);
if (ret < 0)
return ret;
@@ -4231,23 +4345,11 @@ static int lan78xx_suspend(struct usb_interface *intf, pm_message_t message)
if (PMSG_IS_AUTO(message)) {
/* auto suspend (selective suspend) */
- ret = lan78xx_read_reg(dev, MAC_TX, &buf);
- if (ret < 0)
- return ret;
-
- buf &= ~MAC_TX_TXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_TX, buf);
- if (ret < 0)
- return ret;
-
- ret = lan78xx_read_reg(dev, MAC_RX, &buf);
+ ret = lan78xx_stop_tx_path(dev);
if (ret < 0)
return ret;
- buf &= ~MAC_RX_RXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_RX, buf);
+ ret = lan78xx_stop_rx_path(dev);
if (ret < 0)
return ret;
@@ -4299,13 +4401,7 @@ static int lan78xx_suspend(struct usb_interface *intf, pm_message_t message)
if (ret < 0)
return ret;
- ret = lan78xx_read_reg(dev, MAC_RX, &buf);
- if (ret < 0)
- return ret;
-
- buf |= MAC_RX_RXEN_;
-
- ret = lan78xx_write_reg(dev, MAC_RX, buf);
+ ret = lan78xx_start_rx_path(dev);
if (ret < 0)
return ret;
} else {
@@ -4330,7 +4426,6 @@ static int lan78xx_resume(struct usb_interface *intf)
struct sk_buff *skb;
struct urb *res;
int ret;
- u32 buf;
if (!timer_pending(&dev->stat_monitor)) {
dev->delta = 1;
@@ -4338,6 +4433,10 @@ static int lan78xx_resume(struct usb_interface *intf)
jiffies + STAT_UPDATE_TIMER);
}
+ ret = lan78xx_flush_tx_fifo(dev);
+ if (ret < 0)
+ return ret;
+
if (!--dev->suspend_count) {
/* resume interrupt URBs */
if (dev->urb_intr && test_bit(EVENT_DEV_OPEN, &dev->flags)) {
@@ -4397,17 +4496,9 @@ static int lan78xx_resume(struct usb_interface *intf)
if (ret < 0)
return ret;
- ret = lan78xx_read_reg(dev, MAC_TX, &buf);
- if (ret < 0)
- return ret;
-
- buf |= MAC_TX_TXEN_;
+ ret = lan78xx_start_tx_path(dev);
- ret = lan78xx_write_reg(dev, MAC_TX, buf);
- if (ret < 0)
- return ret;
-
- return 0;
+ return ret;
}
static int lan78xx_reset_resume(struct usb_interface *intf)