#include <linux/mv643xx_eth.h>
#include <linux/io.h>
#include <linux/types.h>
+#include <linux/inet_lro.h>
#include <asm/system.h>
static char mv643xx_eth_driver_name[] = "mv643xx_eth";
#define RX_ENABLE_INTERRUPT 0x20000000
#define RX_FIRST_DESC 0x08000000
#define RX_LAST_DESC 0x04000000
+#define RX_IP_HDR_OK 0x02000000
+#define RX_PKT_IS_IPV4 0x01000000
+#define RX_PKT_IS_ETHERNETV2 0x00800000
+#define RX_PKT_LAYER4_TYPE_MASK 0x00600000
+#define RX_PKT_LAYER4_TYPE_TCP_IPV4 0x00000000
+#define RX_PKT_IS_VLAN_TAGGED 0x00080000
/* TX descriptor command */
#define TX_ENABLE_INTERRUPT 0x00800000
u32 late_collision;
};
+struct lro_counters {
+ u32 lro_aggregated;
+ u32 lro_flushed;
+ u32 lro_no_desc;
+};
+
struct rx_queue {
int index;
dma_addr_t rx_desc_dma;
int rx_desc_area_size;
struct sk_buff **rx_skb;
+
+ struct net_lro_mgr lro_mgr;
+ struct net_lro_desc lro_arr[8];
};
struct tx_queue {
spinlock_t mib_counters_lock;
struct mib_counters mib_counters;
+ struct lro_counters lro_counters;
+
struct work_struct tx_timeout_task;
struct napi_struct napi;
/* rx napi ******************************************************************/
+static int
+mv643xx_get_skb_header(struct sk_buff *skb, void **iphdr, void **tcph,
+ u64 *hdr_flags, void *priv)
+{
+ unsigned long cmd_sts = (unsigned long)priv;
+
+ /*
+ * Make sure that this packet is Ethernet II, is not VLAN
+ * tagged, is IPv4, has a valid IP header, and is TCP.
+ */
+ if ((cmd_sts & (RX_IP_HDR_OK | RX_PKT_IS_IPV4 |
+ RX_PKT_IS_ETHERNETV2 | RX_PKT_LAYER4_TYPE_MASK |
+ RX_PKT_IS_VLAN_TAGGED)) !=
+ (RX_IP_HDR_OK | RX_PKT_IS_IPV4 |
+ RX_PKT_IS_ETHERNETV2 | RX_PKT_LAYER4_TYPE_TCP_IPV4))
+ return -1;
+
+ skb_reset_network_header(skb);
+ skb_set_transport_header(skb, ip_hdrlen(skb));
+ *iphdr = ip_hdr(skb);
+ *tcph = tcp_hdr(skb);
+ *hdr_flags = LRO_IPV4 | LRO_TCP;
+
+ return 0;
+}
+
static int rxq_process(struct rx_queue *rxq, int budget)
{
struct mv643xx_eth_private *mp = rxq_to_mp(rxq);
struct net_device_stats *stats = &mp->dev->stats;
+ int lro_flush_needed;
int rx;
+ lro_flush_needed = 0;
rx = 0;
while (rx < budget && rxq->rx_desc_count) {
struct rx_desc *rx_desc;
if (cmd_sts & LAYER_4_CHECKSUM_OK)
skb->ip_summed = CHECKSUM_UNNECESSARY;
skb->protocol = eth_type_trans(skb, mp->dev);
- netif_receive_skb(skb);
+
+ if (skb->dev->features & NETIF_F_LRO &&
+ skb->ip_summed == CHECKSUM_UNNECESSARY) {
+ lro_receive_skb(&rxq->lro_mgr, skb, (void *)cmd_sts);
+ lro_flush_needed = 1;
+ } else
+ netif_receive_skb(skb);
continue;
dev_kfree_skb(skb);
}
+ if (lro_flush_needed)
+ lro_flush_all(&rxq->lro_mgr);
+
if (rx < budget)
mp->work_rx &= ~(1 << rxq->index);
return stats;
}
+static void mv643xx_eth_grab_lro_stats(struct mv643xx_eth_private *mp)
+{
+ u32 lro_aggregated = 0;
+ u32 lro_flushed = 0;
+ u32 lro_no_desc = 0;
+ int i;
+
+ for (i = 0; i < mp->rxq_count; i++) {
+ struct rx_queue *rxq = mp->rxq + i;
+
+ lro_aggregated += rxq->lro_mgr.stats.aggregated;
+ lro_flushed += rxq->lro_mgr.stats.flushed;
+ lro_no_desc += rxq->lro_mgr.stats.no_desc;
+ }
+
+ mp->lro_counters.lro_aggregated = lro_aggregated;
+ mp->lro_counters.lro_flushed = lro_flushed;
+ mp->lro_counters.lro_no_desc = lro_no_desc;
+}
+
static inline u32 mib_read(struct mv643xx_eth_private *mp, int offset)
{
return rdl(mp, MIB_COUNTERS(mp->port_num) + offset);
{
struct mib_counters *p = &mp->mib_counters;
- spin_lock(&mp->mib_counters_lock);
+ spin_lock_bh(&mp->mib_counters_lock);
p->good_octets_received += mib_read(mp, 0x00);
p->good_octets_received += (u64)mib_read(mp, 0x04) << 32;
p->bad_octets_received += mib_read(mp, 0x08);
p->bad_crc_event += mib_read(mp, 0x74);
p->collision += mib_read(mp, 0x78);
p->late_collision += mib_read(mp, 0x7c);
- spin_unlock(&mp->mib_counters_lock);
+ spin_unlock_bh(&mp->mib_counters_lock);
mod_timer(&mp->mib_counters_timer, jiffies + 30 * HZ);
}
{ #m, FIELD_SIZEOF(struct mib_counters, m), \
-1, offsetof(struct mv643xx_eth_private, mib_counters.m) }
+#define LROSTAT(m) \
+ { #m, FIELD_SIZEOF(struct lro_counters, m), \
+ -1, offsetof(struct mv643xx_eth_private, lro_counters.m) }
+
static const struct mv643xx_eth_stats mv643xx_eth_stats[] = {
SSTAT(rx_packets),
SSTAT(tx_packets),
MIBSTAT(bad_crc_event),
MIBSTAT(collision),
MIBSTAT(late_collision),
+ LROSTAT(lro_aggregated),
+ LROSTAT(lro_flushed),
+ LROSTAT(lro_no_desc),
};
static int
mv643xx_eth_get_stats(dev);
mib_counters_update(mp);
+ mv643xx_eth_grab_lro_stats(mp);
for (i = 0; i < ARRAY_SIZE(mv643xx_eth_stats); i++) {
const struct mv643xx_eth_stats *stat;
.set_ringparam = mv643xx_eth_set_ringparam,
.get_rx_csum = mv643xx_eth_get_rx_csum,
.set_rx_csum = mv643xx_eth_set_rx_csum,
+ .set_tx_csum = ethtool_op_set_tx_csum,
.set_sg = ethtool_op_set_sg,
.get_strings = mv643xx_eth_get_strings,
.get_ethtool_stats = mv643xx_eth_get_ethtool_stats,
+ .get_flags = ethtool_op_get_flags,
+ .set_flags = ethtool_op_set_flags,
.get_sset_count = mv643xx_eth_get_sset_count,
};
return;
}
- mc_spec = kmalloc(0x200, GFP_KERNEL);
+ mc_spec = kmalloc(0x200, GFP_ATOMIC);
if (mc_spec == NULL)
goto oom;
mc_other = mc_spec + (0x100 >> 2);
nexti * sizeof(struct rx_desc);
}
+ rxq->lro_mgr.dev = mp->dev;
+ memset(&rxq->lro_mgr.stats, 0, sizeof(rxq->lro_mgr.stats));
+ rxq->lro_mgr.features = LRO_F_NAPI;
+ rxq->lro_mgr.ip_summed = CHECKSUM_UNNECESSARY;
+ rxq->lro_mgr.ip_summed_aggr = CHECKSUM_UNNECESSARY;
+ rxq->lro_mgr.max_desc = ARRAY_SIZE(rxq->lro_arr);
+ rxq->lro_mgr.max_aggr = 32;
+ rxq->lro_mgr.frag_align_pad = 0;
+ rxq->lro_mgr.lro_arr = rxq->lro_arr;
+ rxq->lro_mgr.get_skb_header = mv643xx_get_skb_header;
+
+ memset(&rxq->lro_arr, 0, sizeof(rxq->lro_arr));
+
return 0;
}
/*
- * Add configured unicast address to address filter table.
- */
- mv643xx_eth_program_unicast_filter(mp->dev);
-
- /*
* Receive all unmatched unicast, TCP, UDP, BPDU and broadcast
* frames to RX queue #0, and include the pseudo-header when
* calculating receive checksums.
wrlp(mp, PORT_CONFIG_EXT, 0x00000000);
/*
+ * Add configured unicast addresses to address filter table.
+ */
+ mv643xx_eth_program_unicast_filter(mp->dev);
+
+ /*
* Enable the receive queues.
*/
for (i = 0; i < mp->rxq_count; i++) {
}
}
- netif_carrier_off(dev);
-
port_start(mp);
- set_rx_coal(mp, 0);
- set_tx_coal(mp, 0);
-
wrlp(mp, INT_MASK_EXT, INT_EXT_LINK_PHY | INT_EXT_TX);
wrlp(mp, INT_MASK, INT_TX_END | INT_RX | INT_EXT);
wrlp(mp, INT_MASK, 0x00000000);
rdlp(mp, INT_MASK);
- del_timer_sync(&mp->mib_counters_timer);
-
napi_disable(&mp->napi);
del_timer_sync(&mp->rx_oom);
port_reset(mp);
mv643xx_eth_get_stats(dev);
mib_counters_update(mp);
+ del_timer_sync(&mp->mib_counters_timer);
skb_queue_purge(&mp->rx_recycle);
wrlp(mp, PORT_SERIAL_CONTROL, pscr);
}
+static const struct net_device_ops mv643xx_eth_netdev_ops = {
+ .ndo_open = mv643xx_eth_open,
+ .ndo_stop = mv643xx_eth_stop,
+ .ndo_start_xmit = mv643xx_eth_xmit,
+ .ndo_set_rx_mode = mv643xx_eth_set_rx_mode,
+ .ndo_set_mac_address = mv643xx_eth_set_mac_address,
+ .ndo_do_ioctl = mv643xx_eth_ioctl,
+ .ndo_change_mtu = mv643xx_eth_change_mtu,
+ .ndo_tx_timeout = mv643xx_eth_tx_timeout,
+ .ndo_get_stats = mv643xx_eth_get_stats,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = mv643xx_eth_netpoll,
+#endif
+};
+
static int mv643xx_eth_probe(struct platform_device *pdev)
{
struct mv643xx_eth_platform_data *pd;
BUG_ON(!res);
dev->irq = res->start;
- dev->get_stats = mv643xx_eth_get_stats;
- dev->hard_start_xmit = mv643xx_eth_xmit;
- dev->open = mv643xx_eth_open;
- dev->stop = mv643xx_eth_stop;
- dev->set_rx_mode = mv643xx_eth_set_rx_mode;
- dev->set_mac_address = mv643xx_eth_set_mac_address;
- dev->do_ioctl = mv643xx_eth_ioctl;
- dev->change_mtu = mv643xx_eth_change_mtu;
- dev->tx_timeout = mv643xx_eth_tx_timeout;
-#ifdef CONFIG_NET_POLL_CONTROLLER
- dev->poll_controller = mv643xx_eth_netpoll;
-#endif
+ dev->netdev_ops = &mv643xx_eth_netdev_ops;
+
dev->watchdog_timeo = 2 * HZ;
dev->base_addr = 0;
if (mp->shared->win_protect)
wrl(mp, WINDOW_PROTECT(mp->port_num), mp->shared->win_protect);
+ netif_carrier_off(dev);
+
+ set_rx_coal(mp, 250);
+ set_tx_coal(mp, 0);
+
err = register_netdev(dev);
if (err)
goto out;