s390/qeth: add TX IRQ coalescing support for IQD devices
authorJulian Wiedmann <jwi@linux.ibm.com>
Wed, 25 Mar 2020 09:35:04 +0000 (10:35 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 25 Mar 2020 19:07:15 +0000 (12:07 -0700)
Since IQD devices complete (most of) their transmissions synchronously,
they don't offer TX completion IRQs and have no HW coalescing controls.
But we can fake the easy parts in SW, and give the user some control wrt
to how often the TX NAPI code should be triggered to process the TX
completions.

Having per-queue controls can in particular help the dedicated mcast
queue, as it likely benefits from different fine-tuning than what the
ucast queues need.

CC: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/s390/net/qeth_core.h
drivers/s390/net/qeth_core_main.c
drivers/s390/net/qeth_ethtool.c

index f56670b..a6553e7 100644 (file)
@@ -459,6 +459,7 @@ struct qeth_out_q_stats {
        u64 packing_mode_switch;
        u64 stopped;
        u64 doorbell;
+       u64 coal_frames;
        u64 completion_yield;
        u64 completion_timer;
 
@@ -469,6 +470,8 @@ struct qeth_out_q_stats {
        u64 tx_dropped;
 };
 
+#define QETH_TX_MAX_COALESCED_FRAMES   1
+#define QETH_TX_COALESCE_USECS         25
 #define QETH_TX_TIMER_USECS            500
 
 struct qeth_qdio_out_q {
@@ -492,9 +495,13 @@ struct qeth_qdio_out_q {
        struct napi_struct napi;
        struct timer_list timer;
        struct qeth_hdr *prev_hdr;
+       unsigned int coalesced_frames;
        u8 bulk_start;
        u8 bulk_count;
        u8 bulk_max;
+
+       unsigned int coalesce_usecs;
+       unsigned int max_coalesced_frames;
 };
 
 #define qeth_for_each_output_queue(card, q, i)         \
@@ -503,12 +510,10 @@ struct qeth_qdio_out_q {
 
 #define        qeth_napi_to_out_queue(n) container_of(n, struct qeth_qdio_out_q, napi)
 
-static inline void qeth_tx_arm_timer(struct qeth_qdio_out_q *queue)
+static inline void qeth_tx_arm_timer(struct qeth_qdio_out_q *queue,
+                                    unsigned long usecs)
 {
-       if (timer_pending(&queue->timer))
-               return;
-       mod_timer(&queue->timer, usecs_to_jiffies(QETH_TX_TIMER_USECS) +
-                                jiffies);
+       timer_reduce(&queue->timer, usecs_to_jiffies(usecs) + jiffies);
 }
 
 static inline bool qeth_out_queue_is_full(struct qeth_qdio_out_q *queue)
index 108dd9a..0c9f146 100644 (file)
@@ -2404,6 +2404,8 @@ static int qeth_alloc_qdio_queues(struct qeth_card *card)
                queue->card = card;
                queue->queue_no = i;
                timer_setup(&queue->timer, qeth_tx_completion_timer, 0);
+               queue->coalesce_usecs = QETH_TX_COALESCE_USECS;
+               queue->max_coalesced_frames = QETH_TX_MAX_COALESCED_FRAMES;
 
                /* give outbound qeth_qdio_buffers their qdio_buffers */
                for (j = 0; j < QDIO_MAX_BUFFERS_PER_Q; ++j) {
@@ -2762,6 +2764,7 @@ static int qeth_init_qdio_queues(struct qeth_card *card)
                queue->next_buf_to_fill = 0;
                queue->do_pack = 0;
                queue->prev_hdr = NULL;
+               queue->coalesced_frames = 0;
                queue->bulk_start = 0;
                queue->bulk_count = 0;
                queue->bulk_max = qeth_tx_select_bulk_max(card, queue);
@@ -3357,6 +3360,7 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index,
                buf = queue->bufs[bidx];
                buf->buffer->element[buf->next_element_to_fill - 1].eflags |=
                                SBAL_EFLAGS_LAST_ENTRY;
+               queue->coalesced_frames += buf->frames;
 
                if (queue->bufstates)
                        queue->bufstates[bidx].user = buf;
@@ -3401,8 +3405,18 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index,
                     queue->queue_no, index, count);
 
        /* Fake the TX completion interrupt: */
-       if (IS_IQD(card))
-               napi_schedule(&queue->napi);
+       if (IS_IQD(card)) {
+               unsigned int frames = READ_ONCE(queue->max_coalesced_frames);
+               unsigned int usecs = READ_ONCE(queue->coalesce_usecs);
+
+               if (frames && queue->coalesced_frames >= frames) {
+                       napi_schedule(&queue->napi);
+                       queue->coalesced_frames = 0;
+                       QETH_TXQ_STAT_INC(queue, coal_frames);
+               } else if (usecs) {
+                       qeth_tx_arm_timer(queue, usecs);
+               }
+       }
 
        if (rc) {
                /* ignore temporary SIGA errors without busy condition */
@@ -5667,7 +5681,7 @@ static int qeth_tx_poll(struct napi_struct *napi, int budget)
                if (completed <= 0) {
                        /* Ensure we see TX completion for pending work: */
                        if (napi_complete_done(napi, 0))
-                               qeth_tx_arm_timer(queue);
+                               qeth_tx_arm_timer(queue, QETH_TX_TIMER_USECS);
                        return 0;
                }
 
index 6f0cc6f..ebdc032 100644 (file)
@@ -40,6 +40,7 @@ static const struct qeth_stats txq_stats[] = {
        QETH_TXQ_STAT("Packing mode switches", packing_mode_switch),
        QETH_TXQ_STAT("Queue stopped", stopped),
        QETH_TXQ_STAT("Doorbell", doorbell),
+       QETH_TXQ_STAT("IRQ for frames", coal_frames),
        QETH_TXQ_STAT("Completion yield", completion_yield),
        QETH_TXQ_STAT("Completion timer", completion_timer),
 };
@@ -109,6 +110,38 @@ static void qeth_get_ethtool_stats(struct net_device *dev,
                                   txq_stats, TXQ_STATS_LEN);
 }
 
+static void __qeth_set_coalesce(struct net_device *dev,
+                               struct qeth_qdio_out_q *queue,
+                               struct ethtool_coalesce *coal)
+{
+       WRITE_ONCE(queue->coalesce_usecs, coal->tx_coalesce_usecs);
+       WRITE_ONCE(queue->max_coalesced_frames, coal->tx_max_coalesced_frames);
+
+       if (coal->tx_coalesce_usecs &&
+           netif_running(dev) &&
+           !qeth_out_queue_is_empty(queue))
+               qeth_tx_arm_timer(queue, coal->tx_coalesce_usecs);
+}
+
+static int qeth_set_coalesce(struct net_device *dev,
+                            struct ethtool_coalesce *coal)
+{
+       struct qeth_card *card = dev->ml_priv;
+       struct qeth_qdio_out_q *queue;
+       unsigned int i;
+
+       if (!IS_IQD(card))
+               return -EOPNOTSUPP;
+
+       if (!coal->tx_coalesce_usecs && !coal->tx_max_coalesced_frames)
+               return -EINVAL;
+
+       qeth_for_each_output_queue(card, queue, i)
+               __qeth_set_coalesce(dev, queue, coal);
+
+       return 0;
+}
+
 static void qeth_get_ringparam(struct net_device *dev,
                               struct ethtool_ringparam *param)
 {
@@ -244,6 +277,43 @@ static int qeth_set_tunable(struct net_device *dev,
        }
 }
 
+static int qeth_get_per_queue_coalesce(struct net_device *dev, u32 __queue,
+                                      struct ethtool_coalesce *coal)
+{
+       struct qeth_card *card = dev->ml_priv;
+       struct qeth_qdio_out_q *queue;
+
+       if (!IS_IQD(card))
+               return -EOPNOTSUPP;
+
+       if (__queue >= card->qdio.no_out_queues)
+               return -EINVAL;
+
+       queue = card->qdio.out_qs[__queue];
+
+       coal->tx_coalesce_usecs = queue->coalesce_usecs;
+       coal->tx_max_coalesced_frames = queue->max_coalesced_frames;
+       return 0;
+}
+
+static int qeth_set_per_queue_coalesce(struct net_device *dev, u32 queue,
+                                      struct ethtool_coalesce *coal)
+{
+       struct qeth_card *card = dev->ml_priv;
+
+       if (!IS_IQD(card))
+               return -EOPNOTSUPP;
+
+       if (queue >= card->qdio.no_out_queues)
+               return -EINVAL;
+
+       if (!coal->tx_coalesce_usecs && !coal->tx_max_coalesced_frames)
+               return -EINVAL;
+
+       __qeth_set_coalesce(dev, card->qdio.out_qs[queue], coal);
+       return 0;
+}
+
 /* Helper function to fill 'advertising' and 'supported' which are the same. */
 /* Autoneg and full-duplex are supported and advertised unconditionally.     */
 /* Always advertise and support all speeds up to specified, and only one     */
@@ -443,7 +513,10 @@ static int qeth_get_link_ksettings(struct net_device *netdev,
 }
 
 const struct ethtool_ops qeth_ethtool_ops = {
+       .supported_coalesce_params = ETHTOOL_COALESCE_TX_USECS |
+                                    ETHTOOL_COALESCE_TX_MAX_FRAMES,
        .get_link = ethtool_op_get_link,
+       .set_coalesce = qeth_set_coalesce,
        .get_ringparam = qeth_get_ringparam,
        .get_strings = qeth_get_strings,
        .get_ethtool_stats = qeth_get_ethtool_stats,
@@ -454,6 +527,8 @@ const struct ethtool_ops qeth_ethtool_ops = {
        .get_ts_info = qeth_get_ts_info,
        .get_tunable = qeth_get_tunable,
        .set_tunable = qeth_set_tunable,
+       .get_per_queue_coalesce = qeth_get_per_queue_coalesce,
+       .set_per_queue_coalesce = qeth_set_per_queue_coalesce,
        .get_link_ksettings = qeth_get_link_ksettings,
 };