Commit ee1e52d1 authored by Julian Wiedmann's avatar Julian Wiedmann Committed by David S. Miller

s390/qeth: add TX IRQ coalescing support for IQD devices

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: default avatarJulian Wiedmann <jwi@linux.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1ab2f8c6
...@@ -459,6 +459,7 @@ struct qeth_out_q_stats { ...@@ -459,6 +459,7 @@ struct qeth_out_q_stats {
u64 packing_mode_switch; u64 packing_mode_switch;
u64 stopped; u64 stopped;
u64 doorbell; u64 doorbell;
u64 coal_frames;
u64 completion_yield; u64 completion_yield;
u64 completion_timer; u64 completion_timer;
...@@ -469,6 +470,8 @@ struct qeth_out_q_stats { ...@@ -469,6 +470,8 @@ struct qeth_out_q_stats {
u64 tx_dropped; u64 tx_dropped;
}; };
#define QETH_TX_MAX_COALESCED_FRAMES 1
#define QETH_TX_COALESCE_USECS 25
#define QETH_TX_TIMER_USECS 500 #define QETH_TX_TIMER_USECS 500
struct qeth_qdio_out_q { struct qeth_qdio_out_q {
...@@ -492,9 +495,13 @@ struct qeth_qdio_out_q { ...@@ -492,9 +495,13 @@ struct qeth_qdio_out_q {
struct napi_struct napi; struct napi_struct napi;
struct timer_list timer; struct timer_list timer;
struct qeth_hdr *prev_hdr; struct qeth_hdr *prev_hdr;
unsigned int coalesced_frames;
u8 bulk_start; u8 bulk_start;
u8 bulk_count; u8 bulk_count;
u8 bulk_max; u8 bulk_max;
unsigned int coalesce_usecs;
unsigned int max_coalesced_frames;
}; };
#define qeth_for_each_output_queue(card, q, i) \ #define qeth_for_each_output_queue(card, q, i) \
...@@ -503,12 +510,10 @@ struct qeth_qdio_out_q { ...@@ -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) #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)) timer_reduce(&queue->timer, usecs_to_jiffies(usecs) + jiffies);
return;
mod_timer(&queue->timer, usecs_to_jiffies(QETH_TX_TIMER_USECS) +
jiffies);
} }
static inline bool qeth_out_queue_is_full(struct qeth_qdio_out_q *queue) static inline bool qeth_out_queue_is_full(struct qeth_qdio_out_q *queue)
......
...@@ -2404,6 +2404,8 @@ static int qeth_alloc_qdio_queues(struct qeth_card *card) ...@@ -2404,6 +2404,8 @@ static int qeth_alloc_qdio_queues(struct qeth_card *card)
queue->card = card; queue->card = card;
queue->queue_no = i; queue->queue_no = i;
timer_setup(&queue->timer, qeth_tx_completion_timer, 0); 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 */ /* give outbound qeth_qdio_buffers their qdio_buffers */
for (j = 0; j < QDIO_MAX_BUFFERS_PER_Q; ++j) { for (j = 0; j < QDIO_MAX_BUFFERS_PER_Q; ++j) {
...@@ -2762,6 +2764,7 @@ static int qeth_init_qdio_queues(struct qeth_card *card) ...@@ -2762,6 +2764,7 @@ static int qeth_init_qdio_queues(struct qeth_card *card)
queue->next_buf_to_fill = 0; queue->next_buf_to_fill = 0;
queue->do_pack = 0; queue->do_pack = 0;
queue->prev_hdr = NULL; queue->prev_hdr = NULL;
queue->coalesced_frames = 0;
queue->bulk_start = 0; queue->bulk_start = 0;
queue->bulk_count = 0; queue->bulk_count = 0;
queue->bulk_max = qeth_tx_select_bulk_max(card, queue); 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, ...@@ -3357,6 +3360,7 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index,
buf = queue->bufs[bidx]; buf = queue->bufs[bidx];
buf->buffer->element[buf->next_element_to_fill - 1].eflags |= buf->buffer->element[buf->next_element_to_fill - 1].eflags |=
SBAL_EFLAGS_LAST_ENTRY; SBAL_EFLAGS_LAST_ENTRY;
queue->coalesced_frames += buf->frames;
if (queue->bufstates) if (queue->bufstates)
queue->bufstates[bidx].user = buf; queue->bufstates[bidx].user = buf;
...@@ -3401,8 +3405,18 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index, ...@@ -3401,8 +3405,18 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index,
queue->queue_no, index, count); queue->queue_no, index, count);
/* Fake the TX completion interrupt: */ /* Fake the TX completion interrupt: */
if (IS_IQD(card)) if (IS_IQD(card)) {
napi_schedule(&queue->napi); 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) { if (rc) {
/* ignore temporary SIGA errors without busy condition */ /* ignore temporary SIGA errors without busy condition */
...@@ -5667,7 +5681,7 @@ static int qeth_tx_poll(struct napi_struct *napi, int budget) ...@@ -5667,7 +5681,7 @@ static int qeth_tx_poll(struct napi_struct *napi, int budget)
if (completed <= 0) { if (completed <= 0) {
/* Ensure we see TX completion for pending work: */ /* Ensure we see TX completion for pending work: */
if (napi_complete_done(napi, 0)) if (napi_complete_done(napi, 0))
qeth_tx_arm_timer(queue); qeth_tx_arm_timer(queue, QETH_TX_TIMER_USECS);
return 0; return 0;
} }
......
...@@ -40,6 +40,7 @@ static const struct qeth_stats txq_stats[] = { ...@@ -40,6 +40,7 @@ static const struct qeth_stats txq_stats[] = {
QETH_TXQ_STAT("Packing mode switches", packing_mode_switch), QETH_TXQ_STAT("Packing mode switches", packing_mode_switch),
QETH_TXQ_STAT("Queue stopped", stopped), QETH_TXQ_STAT("Queue stopped", stopped),
QETH_TXQ_STAT("Doorbell", doorbell), QETH_TXQ_STAT("Doorbell", doorbell),
QETH_TXQ_STAT("IRQ for frames", coal_frames),
QETH_TXQ_STAT("Completion yield", completion_yield), QETH_TXQ_STAT("Completion yield", completion_yield),
QETH_TXQ_STAT("Completion timer", completion_timer), QETH_TXQ_STAT("Completion timer", completion_timer),
}; };
...@@ -109,6 +110,38 @@ static void qeth_get_ethtool_stats(struct net_device *dev, ...@@ -109,6 +110,38 @@ static void qeth_get_ethtool_stats(struct net_device *dev,
txq_stats, TXQ_STATS_LEN); 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, static void qeth_get_ringparam(struct net_device *dev,
struct ethtool_ringparam *param) struct ethtool_ringparam *param)
{ {
...@@ -244,6 +277,43 @@ static int qeth_set_tunable(struct net_device *dev, ...@@ -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. */ /* Helper function to fill 'advertising' and 'supported' which are the same. */
/* Autoneg and full-duplex are supported and advertised unconditionally. */ /* Autoneg and full-duplex are supported and advertised unconditionally. */
/* Always advertise and support all speeds up to specified, and only one */ /* 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, ...@@ -443,7 +513,10 @@ static int qeth_get_link_ksettings(struct net_device *netdev,
} }
const struct ethtool_ops qeth_ethtool_ops = { 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, .get_link = ethtool_op_get_link,
.set_coalesce = qeth_set_coalesce,
.get_ringparam = qeth_get_ringparam, .get_ringparam = qeth_get_ringparam,
.get_strings = qeth_get_strings, .get_strings = qeth_get_strings,
.get_ethtool_stats = qeth_get_ethtool_stats, .get_ethtool_stats = qeth_get_ethtool_stats,
...@@ -454,6 +527,8 @@ const struct ethtool_ops qeth_ethtool_ops = { ...@@ -454,6 +527,8 @@ const struct ethtool_ops qeth_ethtool_ops = {
.get_ts_info = qeth_get_ts_info, .get_ts_info = qeth_get_ts_info,
.get_tunable = qeth_get_tunable, .get_tunable = qeth_get_tunable,
.set_tunable = qeth_set_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, .get_link_ksettings = qeth_get_link_ksettings,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment