diff options
| author | David L Stevens <david.stevens@oracle.com> | 2015-01-26 15:54:27 -0500 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2015-01-27 03:25:21 -0500 |
| commit | 8e2b60cd18381a2f102dc6157ea2481a9ddd0001 (patch) | |
| tree | 08a04d624d6b289fa51570c462c7b5ae7a102a6c | |
| parent | 07ac3e7099b5788ee4b78233c96532c6209a304b (diff) | |
sunvnet: improve error handling when a remote crashes
If a remote machine crashes while there are pending transmit buffers, the
sunvnet driver reallocates the ring descriptors giving us enries that have
state VIO_DESC_FREE but also an allocated skb. This results in a BUG_ON()
call when the remote reboots and we reach that point in the ring.
This patch:
1) clears pending tx packets in the ring on port reset
2) changes a BUG_ON() to a pr_warn() when a remote host has given us an invalid
descriptor state
3) collapses multiple active buffer frees in a ring to a single message per
ring and adds the device name and remote MAC address
This fixes the particular problem of not cleaning up pending buffers on a
reset, but also prevents us from crashing if the remote handles descriptors
out of order or sets an unexpected state for a descriptor.
Signed-off-by: David L Stevens <david.stevens@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | drivers/net/ethernet/sun/sunvnet.c | 62 |
1 files changed, 34 insertions, 28 deletions
diff --git a/drivers/net/ethernet/sun/sunvnet.c b/drivers/net/ethernet/sun/sunvnet.c index fe044f31708e..2b719ccd6e7c 100644 --- a/drivers/net/ethernet/sun/sunvnet.c +++ b/drivers/net/ethernet/sun/sunvnet.c | |||
| @@ -50,6 +50,7 @@ MODULE_VERSION(DRV_MODULE_VERSION); | |||
| 50 | #define VNET_MAX_RETRIES 10 | 50 | #define VNET_MAX_RETRIES 10 |
| 51 | 51 | ||
| 52 | static int __vnet_tx_trigger(struct vnet_port *port, u32 start); | 52 | static int __vnet_tx_trigger(struct vnet_port *port, u32 start); |
| 53 | static void vnet_port_reset(struct vnet_port *port); | ||
| 53 | 54 | ||
| 54 | /* Ordered from largest major to lowest */ | 55 | /* Ordered from largest major to lowest */ |
| 55 | static struct vio_version vnet_versions[] = { | 56 | static struct vio_version vnet_versions[] = { |
| @@ -736,9 +737,7 @@ ldc_ctrl: | |||
| 736 | vio_link_state_change(vio, event); | 737 | vio_link_state_change(vio, event); |
| 737 | 738 | ||
| 738 | if (event == LDC_EVENT_RESET) { | 739 | if (event == LDC_EVENT_RESET) { |
| 739 | port->rmtu = 0; | 740 | vnet_port_reset(port); |
| 740 | port->tso = true; | ||
| 741 | port->tsolen = 0; | ||
| 742 | vio_port_up(vio); | 741 | vio_port_up(vio); |
| 743 | } | 742 | } |
| 744 | port->rx_event = 0; | 743 | port->rx_event = 0; |
| @@ -934,36 +933,36 @@ static struct sk_buff *vnet_clean_tx_ring(struct vnet_port *port, | |||
| 934 | 933 | ||
| 935 | *pending = 0; | 934 | *pending = 0; |
| 936 | 935 | ||
| 937 | txi = dr->prod-1; | 936 | txi = dr->prod; |
| 938 | if (txi < 0) | ||
| 939 | txi = VNET_TX_RING_SIZE-1; | ||
| 940 | |||
| 941 | for (i = 0; i < VNET_TX_RING_SIZE; ++i) { | 937 | for (i = 0; i < VNET_TX_RING_SIZE; ++i) { |
| 942 | struct vio_net_desc *d; | 938 | struct vio_net_desc *d; |
| 943 | 939 | ||
| 944 | d = vio_dring_entry(dr, txi); | 940 | --txi; |
| 945 | 941 | if (txi < 0) | |
| 946 | if (d->hdr.state == VIO_DESC_DONE) { | 942 | txi = VNET_TX_RING_SIZE-1; |
| 947 | if (port->tx_bufs[txi].skb) { | ||
| 948 | BUG_ON(port->tx_bufs[txi].skb->next); | ||
| 949 | 943 | ||
| 950 | port->tx_bufs[txi].skb->next = skb; | 944 | d = vio_dring_entry(dr, txi); |
| 951 | skb = port->tx_bufs[txi].skb; | ||
| 952 | port->tx_bufs[txi].skb = NULL; | ||
| 953 | 945 | ||
| 954 | ldc_unmap(port->vio.lp, | 946 | if (d->hdr.state == VIO_DESC_READY) { |
| 955 | port->tx_bufs[txi].cookies, | ||
| 956 | port->tx_bufs[txi].ncookies); | ||
| 957 | } | ||
| 958 | d->hdr.state = VIO_DESC_FREE; | ||
| 959 | } else if (d->hdr.state == VIO_DESC_READY) { | ||
| 960 | (*pending)++; | 947 | (*pending)++; |
| 961 | } else if (d->hdr.state == VIO_DESC_FREE) { | 948 | continue; |
| 962 | break; | ||
| 963 | } | 949 | } |
| 964 | --txi; | 950 | if (port->tx_bufs[txi].skb) { |
| 965 | if (txi < 0) | 951 | if (d->hdr.state != VIO_DESC_DONE) |
| 966 | txi = VNET_TX_RING_SIZE-1; | 952 | pr_notice("invalid ring buffer state %d\n", |
| 953 | d->hdr.state); | ||
| 954 | BUG_ON(port->tx_bufs[txi].skb->next); | ||
| 955 | |||
| 956 | port->tx_bufs[txi].skb->next = skb; | ||
| 957 | skb = port->tx_bufs[txi].skb; | ||
| 958 | port->tx_bufs[txi].skb = NULL; | ||
| 959 | |||
| 960 | ldc_unmap(port->vio.lp, | ||
| 961 | port->tx_bufs[txi].cookies, | ||
| 962 | port->tx_bufs[txi].ncookies); | ||
| 963 | } else if (d->hdr.state == VIO_DESC_FREE) | ||
| 964 | break; | ||
| 965 | d->hdr.state = VIO_DESC_FREE; | ||
| 967 | } | 966 | } |
| 968 | return skb; | 967 | return skb; |
| 969 | } | 968 | } |
| @@ -1649,8 +1648,6 @@ static void vnet_port_free_tx_bufs(struct vnet_port *port) | |||
| 1649 | continue; | 1648 | continue; |
| 1650 | 1649 | ||
| 1651 | d = vio_dring_entry(dr, i); | 1650 | d = vio_dring_entry(dr, i); |
| 1652 | if (d->hdr.state == VIO_DESC_READY) | ||
| 1653 | pr_warn("active transmit buffers freed\n"); | ||
| 1654 | 1651 | ||
| 1655 | ldc_unmap(port->vio.lp, | 1652 | ldc_unmap(port->vio.lp, |
| 1656 | port->tx_bufs[i].cookies, | 1653 | port->tx_bufs[i].cookies, |
| @@ -1669,6 +1666,15 @@ static void vnet_port_free_tx_bufs(struct vnet_port *port) | |||
| 1669 | dr->ncookies = 0; | 1666 | dr->ncookies = 0; |
| 1670 | } | 1667 | } |
| 1671 | 1668 | ||
| 1669 | static void vnet_port_reset(struct vnet_port *port) | ||
| 1670 | { | ||
| 1671 | del_timer(&port->clean_timer); | ||
| 1672 | vnet_port_free_tx_bufs(port); | ||
| 1673 | port->rmtu = 0; | ||
| 1674 | port->tso = true; | ||
| 1675 | port->tsolen = 0; | ||
| 1676 | } | ||
| 1677 | |||
| 1672 | static int vnet_port_alloc_tx_ring(struct vnet_port *port) | 1678 | static int vnet_port_alloc_tx_ring(struct vnet_port *port) |
| 1673 | { | 1679 | { |
| 1674 | struct vio_dring_state *dr; | 1680 | struct vio_dring_state *dr; |
