tsnep: Throttle interrupts
Without interrupt throttling, iperf server mode generates a CPU load of 100% (A53 1.2GHz). Also the throughput suffers with less than 900Mbit/s on a 1Gbit/s link. The reason is a high interrupt load with interrupts every ~20us. Reduce interrupt load by throttling of interrupts. Interrupt delay default is 64us. For iperf server mode the CPU load is significantly reduced to ~20% and the throughput reaches the maximum of 941MBit/s. Interrupts are generated every ~140us. RX and TX coalesce can be configured with ethtool. RX coalesce has priority over TX coalesce if the same interrupt is used. Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
4f661ccfca
commit
d3dfe8d6c0
@ -132,6 +132,8 @@ struct tsnep_queue {
|
||||
|
||||
int irq;
|
||||
u32 irq_mask;
|
||||
void __iomem *irq_delay_addr;
|
||||
u8 irq_delay;
|
||||
};
|
||||
|
||||
struct tsnep_adapter {
|
||||
@ -223,5 +225,7 @@ static inline void tsnep_ethtool_self_test(struct net_device *dev,
|
||||
#endif /* CONFIG_TSNEP_SELFTESTS */
|
||||
|
||||
void tsnep_get_system_time(struct tsnep_adapter *adapter, u64 *time);
|
||||
int tsnep_set_irq_coalesce(struct tsnep_queue *queue, u32 usecs);
|
||||
u32 tsnep_get_irq_coalesce(struct tsnep_queue *queue);
|
||||
|
||||
#endif /* _TSNEP_H */
|
||||
|
@ -324,7 +324,137 @@ static int tsnep_ethtool_get_ts_info(struct net_device *netdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct tsnep_queue *tsnep_get_queue_with_tx(struct tsnep_adapter *adapter,
|
||||
int index)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < adapter->num_queues; i++) {
|
||||
if (adapter->queue[i].tx) {
|
||||
if (index == 0)
|
||||
return &adapter->queue[i];
|
||||
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct tsnep_queue *tsnep_get_queue_with_rx(struct tsnep_adapter *adapter,
|
||||
int index)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < adapter->num_queues; i++) {
|
||||
if (adapter->queue[i].rx) {
|
||||
if (index == 0)
|
||||
return &adapter->queue[i];
|
||||
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int tsnep_ethtool_get_coalesce(struct net_device *netdev,
|
||||
struct ethtool_coalesce *ec,
|
||||
struct kernel_ethtool_coalesce *kernel_coal,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct tsnep_adapter *adapter = netdev_priv(netdev);
|
||||
struct tsnep_queue *queue;
|
||||
|
||||
queue = tsnep_get_queue_with_rx(adapter, 0);
|
||||
if (queue)
|
||||
ec->rx_coalesce_usecs = tsnep_get_irq_coalesce(queue);
|
||||
|
||||
queue = tsnep_get_queue_with_tx(adapter, 0);
|
||||
if (queue)
|
||||
ec->tx_coalesce_usecs = tsnep_get_irq_coalesce(queue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsnep_ethtool_set_coalesce(struct net_device *netdev,
|
||||
struct ethtool_coalesce *ec,
|
||||
struct kernel_ethtool_coalesce *kernel_coal,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct tsnep_adapter *adapter = netdev_priv(netdev);
|
||||
int i;
|
||||
int retval;
|
||||
|
||||
for (i = 0; i < adapter->num_queues; i++) {
|
||||
/* RX coalesce has priority for queues with TX and RX */
|
||||
if (adapter->queue[i].rx)
|
||||
retval = tsnep_set_irq_coalesce(&adapter->queue[i],
|
||||
ec->rx_coalesce_usecs);
|
||||
else
|
||||
retval = tsnep_set_irq_coalesce(&adapter->queue[i],
|
||||
ec->tx_coalesce_usecs);
|
||||
if (retval != 0)
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsnep_ethtool_get_per_queue_coalesce(struct net_device *netdev,
|
||||
u32 queue,
|
||||
struct ethtool_coalesce *ec)
|
||||
{
|
||||
struct tsnep_adapter *adapter = netdev_priv(netdev);
|
||||
struct tsnep_queue *queue_with_rx;
|
||||
struct tsnep_queue *queue_with_tx;
|
||||
|
||||
if (queue >= max(adapter->num_tx_queues, adapter->num_rx_queues))
|
||||
return -EINVAL;
|
||||
|
||||
queue_with_rx = tsnep_get_queue_with_rx(adapter, queue);
|
||||
if (queue_with_rx)
|
||||
ec->rx_coalesce_usecs = tsnep_get_irq_coalesce(queue_with_rx);
|
||||
|
||||
queue_with_tx = tsnep_get_queue_with_tx(adapter, queue);
|
||||
if (queue_with_tx)
|
||||
ec->tx_coalesce_usecs = tsnep_get_irq_coalesce(queue_with_tx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsnep_ethtool_set_per_queue_coalesce(struct net_device *netdev,
|
||||
u32 queue,
|
||||
struct ethtool_coalesce *ec)
|
||||
{
|
||||
struct tsnep_adapter *adapter = netdev_priv(netdev);
|
||||
struct tsnep_queue *queue_with_rx;
|
||||
struct tsnep_queue *queue_with_tx;
|
||||
int retval;
|
||||
|
||||
if (queue >= max(adapter->num_tx_queues, adapter->num_rx_queues))
|
||||
return -EINVAL;
|
||||
|
||||
queue_with_rx = tsnep_get_queue_with_rx(adapter, queue);
|
||||
if (queue_with_rx) {
|
||||
retval = tsnep_set_irq_coalesce(queue_with_rx, ec->rx_coalesce_usecs);
|
||||
if (retval != 0)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* RX coalesce has priority for queues with TX and RX */
|
||||
queue_with_tx = tsnep_get_queue_with_tx(adapter, queue);
|
||||
if (queue_with_tx && !queue_with_tx->rx) {
|
||||
retval = tsnep_set_irq_coalesce(queue_with_tx, ec->tx_coalesce_usecs);
|
||||
if (retval != 0)
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct ethtool_ops tsnep_ethtool_ops = {
|
||||
.supported_coalesce_params = ETHTOOL_COALESCE_USECS,
|
||||
.get_drvinfo = tsnep_ethtool_get_drvinfo,
|
||||
.get_regs_len = tsnep_ethtool_get_regs_len,
|
||||
.get_regs = tsnep_ethtool_get_regs,
|
||||
@ -340,6 +470,10 @@ const struct ethtool_ops tsnep_ethtool_ops = {
|
||||
.set_rxnfc = tsnep_ethtool_set_rxnfc,
|
||||
.get_channels = tsnep_ethtool_get_channels,
|
||||
.get_ts_info = tsnep_ethtool_get_ts_info,
|
||||
.get_coalesce = tsnep_ethtool_get_coalesce,
|
||||
.set_coalesce = tsnep_ethtool_set_coalesce,
|
||||
.get_per_queue_coalesce = tsnep_ethtool_get_per_queue_coalesce,
|
||||
.set_per_queue_coalesce = tsnep_ethtool_set_per_queue_coalesce,
|
||||
.get_link_ksettings = phy_ethtool_get_link_ksettings,
|
||||
.set_link_ksettings = phy_ethtool_set_link_ksettings,
|
||||
};
|
||||
|
@ -48,6 +48,13 @@
|
||||
#define ECM_COUNTER_LOW 0x0028
|
||||
#define ECM_COUNTER_HIGH 0x002C
|
||||
|
||||
/* interrupt delay */
|
||||
#define ECM_INT_DELAY 0x0030
|
||||
#define ECM_INT_DELAY_MASK 0xF0
|
||||
#define ECM_INT_DELAY_SHIFT 4
|
||||
#define ECM_INT_DELAY_BASE_US 16
|
||||
#define ECM_INT_DELAY_OFFSET 1
|
||||
|
||||
/* control and status */
|
||||
#define ECM_STATUS 0x0080
|
||||
#define ECM_LINK_MODE_OFF 0x01000000
|
||||
|
@ -39,6 +39,10 @@
|
||||
#endif
|
||||
#define DMA_ADDR_LOW(dma_addr) ((u32)((dma_addr) & 0xFFFFFFFF))
|
||||
|
||||
#define TSNEP_COALESCE_USECS_DEFAULT 64
|
||||
#define TSNEP_COALESCE_USECS_MAX ((ECM_INT_DELAY_MASK >> ECM_INT_DELAY_SHIFT) * \
|
||||
ECM_INT_DELAY_BASE_US + ECM_INT_DELAY_BASE_US - 1)
|
||||
|
||||
static void tsnep_enable_irq(struct tsnep_adapter *adapter, u32 mask)
|
||||
{
|
||||
iowrite32(mask, adapter->addr + ECM_INT_ENABLE);
|
||||
@ -83,6 +87,33 @@ static irqreturn_t tsnep_irq_txrx(int irq, void *arg)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int tsnep_set_irq_coalesce(struct tsnep_queue *queue, u32 usecs)
|
||||
{
|
||||
if (usecs > TSNEP_COALESCE_USECS_MAX)
|
||||
return -ERANGE;
|
||||
|
||||
usecs /= ECM_INT_DELAY_BASE_US;
|
||||
usecs <<= ECM_INT_DELAY_SHIFT;
|
||||
usecs &= ECM_INT_DELAY_MASK;
|
||||
|
||||
queue->irq_delay &= ~ECM_INT_DELAY_MASK;
|
||||
queue->irq_delay |= usecs;
|
||||
iowrite8(queue->irq_delay, queue->irq_delay_addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 tsnep_get_irq_coalesce(struct tsnep_queue *queue)
|
||||
{
|
||||
u32 usecs;
|
||||
|
||||
usecs = (queue->irq_delay & ECM_INT_DELAY_MASK);
|
||||
usecs >>= ECM_INT_DELAY_SHIFT;
|
||||
usecs *= ECM_INT_DELAY_BASE_US;
|
||||
|
||||
return usecs;
|
||||
}
|
||||
|
||||
static int tsnep_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
|
||||
{
|
||||
struct tsnep_adapter *adapter = bus->priv;
|
||||
@ -1371,6 +1402,11 @@ static int tsnep_queue_init(struct tsnep_adapter *adapter, int queue_count)
|
||||
adapter->queue[0].tx = &adapter->tx[0];
|
||||
adapter->queue[0].rx = &adapter->rx[0];
|
||||
adapter->queue[0].irq_mask = irq_mask;
|
||||
adapter->queue[0].irq_delay_addr = adapter->addr + ECM_INT_DELAY;
|
||||
retval = tsnep_set_irq_coalesce(&adapter->queue[0],
|
||||
TSNEP_COALESCE_USECS_DEFAULT);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
adapter->netdev->irq = adapter->queue[0].irq;
|
||||
|
||||
@ -1391,6 +1427,12 @@ static int tsnep_queue_init(struct tsnep_adapter *adapter, int queue_count)
|
||||
adapter->queue[i].rx = &adapter->rx[i];
|
||||
adapter->queue[i].irq_mask =
|
||||
irq_mask << (ECM_INT_TXRX_SHIFT * i);
|
||||
adapter->queue[i].irq_delay_addr =
|
||||
adapter->addr + ECM_INT_DELAY + ECM_INT_DELAY_OFFSET * i;
|
||||
retval = tsnep_set_irq_coalesce(&adapter->queue[i],
|
||||
TSNEP_COALESCE_USECS_DEFAULT);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user