Commit 8e8b6c63 authored by David S. Miller's avatar David S. Miller

Merge branch 'rmnet-tx-pkt-aggregation'

Daniele Palmas says:

====================
net: add tx packets aggregation to ethtool and rmnet

Hello maintainers and all,

this patchset implements tx qmap packets aggregation in rmnet and generic
ethtool support for that.

Some low-cat Thread-x based modems are not capable of properly reaching the maximum
allowed throughput both in tx and rx during a bidirectional test if tx packets
aggregation is not enabled.

I verified this problem with rmnet + qmi_wwan by using a MDM9207 Cat. 4 based modem
(50Mbps/150Mbps max throughput). What is actually happening is pictured at
https://drive.google.com/file/d/1gSbozrtd9h0X63i6vdkNpN68d-9sg8f9/view

Testing with iperf TCP, when rx and tx flows are tested singularly there's no issue
in tx and minor issues in rx (not able to reach max throughput). When there are concurrent
tx and rx flows, tx throughput has an huge drop. rx a minor one, but still present.

The same scenario with tx aggregation enabled is pictured at
https://drive.google.com/file/d/1jcVIKNZD7K3lHtwKE5W02mpaloudYYih/view
showing a regular graph.

This issue does not happen with high-cat modems (e.g. SDX20), or at least it
does not happen at the throughputs I'm able to test currently: maybe the same
could happen when moving close to the maximum rates supported by those modems.
Anyway, having the tx aggregation enabled should not hurt.

The first attempt to solve this issue was in qmi_wwan qmap implementation,
see the discussion at https://lore.kernel.org/netdev/20221019132503.6783-1-dnlplm@gmail.com/

However, it turned out that rmnet was a better candidate for the implementation.

Moreover, Greg and Jakub suggested also to use ethtool for the configuration:
not sure if I got their advice right, but this patchset add also generic ethtool
support for tx aggregation.

The patches have been tested mainly against an MDM9207 based modem through USB
and SDX55 through PCI (MHI).

v2 should address the comments highlighted in the review: the implementation is
still in rmnet, due to Subash's request of keeping tx aggregation there.

v3 fixes ethtool-netlink.rst content out of table bounds and a W=1 build warning
for patch 2.

v4 solves a race related to egress_agg_params.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9a06cce6 db8a563a
......@@ -1004,6 +1004,9 @@ Kernel response contents:
``ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL`` u32 rate sampling interval
``ETHTOOL_A_COALESCE_USE_CQE_TX`` bool timer reset mode, Tx
``ETHTOOL_A_COALESCE_USE_CQE_RX`` bool timer reset mode, Rx
``ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES`` u32 max aggr size, Tx
``ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES`` u32 max aggr packets, Tx
``ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS`` u32 time (us), aggr, Tx
=========================================== ====== =======================
Attributes are only included in reply if their value is not zero or the
......@@ -1022,6 +1025,17 @@ each packet event resets the timer. In this mode timer is used to force
the interrupt if queue goes idle, while busy queues depend on the packet
limit to trigger interrupts.
Tx aggregation consists of copying frames into a contiguous buffer so that they
can be submitted as a single IO operation. ``ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES``
describes the maximum size in bytes for the submitted buffer.
``ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES`` describes the maximum number of frames
that can be aggregated into a single buffer.
``ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS`` describes the amount of time in usecs,
counted since the first packet arrival in an aggregated block, after which the
block should be sent.
This feature is mainly of interest for specific USB devices which does not cope
well with frequent small-sized URBs transmissions.
COALESCE_SET
============
......@@ -1055,6 +1069,9 @@ Request contents:
``ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL`` u32 rate sampling interval
``ETHTOOL_A_COALESCE_USE_CQE_TX`` bool timer reset mode, Tx
``ETHTOOL_A_COALESCE_USE_CQE_RX`` bool timer reset mode, Rx
``ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES`` u32 max aggr size, Tx
``ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES`` u32 max aggr packets, Tx
``ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS`` u32 time (us), aggr, Tx
=========================================== ====== =======================
Request is rejected if it attributes declared as unsupported by driver (i.e.
......
......@@ -12,6 +12,7 @@
#include "rmnet_handlers.h"
#include "rmnet_vnd.h"
#include "rmnet_private.h"
#include "rmnet_map.h"
/* Local Definitions and Declarations */
......@@ -39,6 +40,8 @@ static int rmnet_unregister_real_device(struct net_device *real_dev)
if (port->nr_rmnet_devs)
return -EINVAL;
rmnet_map_tx_aggregate_exit(port);
netdev_rx_handler_unregister(real_dev);
kfree(port);
......@@ -79,6 +82,8 @@ static int rmnet_register_real_device(struct net_device *real_dev,
for (entry = 0; entry < RMNET_MAX_LOGICAL_EP; entry++)
INIT_HLIST_HEAD(&port->muxed_ep[entry]);
rmnet_map_tx_aggregate_init(port);
netdev_dbg(real_dev, "registered with rmnet\n");
return 0;
}
......
......@@ -6,6 +6,7 @@
*/
#include <linux/skbuff.h>
#include <linux/time.h>
#include <net/gro_cells.h>
#ifndef _RMNET_CONFIG_H_
......@@ -19,6 +20,12 @@ struct rmnet_endpoint {
struct hlist_node hlnode;
};
struct rmnet_egress_agg_params {
u32 bytes;
u32 count;
u64 time_nsec;
};
/* One instance of this structure is instantiated for each real_dev associated
* with rmnet.
*/
......@@ -30,6 +37,19 @@ struct rmnet_port {
struct hlist_head muxed_ep[RMNET_MAX_LOGICAL_EP];
struct net_device *bridge_ep;
struct net_device *rmnet_dev;
/* Egress aggregation information */
struct rmnet_egress_agg_params egress_agg_params;
/* Protect aggregation related elements */
spinlock_t agg_lock;
struct sk_buff *skbagg_head;
struct sk_buff *skbagg_tail;
int agg_state;
u8 agg_count;
struct timespec64 agg_time;
struct timespec64 agg_last;
struct hrtimer hrtimer;
struct work_struct agg_wq;
};
extern struct rtnl_link_ops rmnet_link_ops;
......
......@@ -164,8 +164,18 @@ static int rmnet_map_egress_handler(struct sk_buff *skb,
map_header->mux_id = mux_id;
skb->protocol = htons(ETH_P_MAP);
if (READ_ONCE(port->egress_agg_params.count) > 1) {
unsigned int len;
len = rmnet_map_tx_aggregate(skb, port, orig_dev);
if (likely(len)) {
rmnet_vnd_tx_fixup_len(len, orig_dev);
return -EINPROGRESS;
}
return -ENOMEM;
}
skb->protocol = htons(ETH_P_MAP);
return 0;
}
......@@ -235,6 +245,7 @@ void rmnet_egress_handler(struct sk_buff *skb)
struct rmnet_port *port;
struct rmnet_priv *priv;
u8 mux_id;
int err;
sk_pacing_shift_update(skb->sk, 8);
......@@ -247,8 +258,11 @@ void rmnet_egress_handler(struct sk_buff *skb)
if (!port)
goto drop;
if (rmnet_map_egress_handler(skb, port, mux_id, orig_dev))
err = rmnet_map_egress_handler(skb, port, mux_id, orig_dev);
if (err == -ENOMEM)
goto drop;
else if (err == -EINPROGRESS)
return;
rmnet_vnd_tx_fixup(skb, orig_dev);
......
......@@ -53,5 +53,11 @@ void rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
struct net_device *orig_dev,
int csum_type);
int rmnet_map_process_next_hdr_packet(struct sk_buff *skb, u16 len);
unsigned int rmnet_map_tx_aggregate(struct sk_buff *skb, struct rmnet_port *port,
struct net_device *orig_dev);
void rmnet_map_tx_aggregate_init(struct rmnet_port *port);
void rmnet_map_tx_aggregate_exit(struct rmnet_port *port);
void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
u32 count, u32 time);
#endif /* _RMNET_MAP_H_ */
......@@ -12,6 +12,7 @@
#include "rmnet_config.h"
#include "rmnet_map.h"
#include "rmnet_private.h"
#include "rmnet_vnd.h"
#define RMNET_MAP_DEAGGR_SPACING 64
#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)
......@@ -518,3 +519,193 @@ int rmnet_map_process_next_hdr_packet(struct sk_buff *skb,
return 0;
}
#define RMNET_AGG_BYPASS_TIME_NSEC 10000000L
static void reset_aggr_params(struct rmnet_port *port)
{
port->skbagg_head = NULL;
port->agg_count = 0;
port->agg_state = 0;
memset(&port->agg_time, 0, sizeof(struct timespec64));
}
static void rmnet_send_skb(struct rmnet_port *port, struct sk_buff *skb)
{
if (skb_needs_linearize(skb, port->dev->features)) {
if (unlikely(__skb_linearize(skb))) {
struct rmnet_priv *priv;
priv = netdev_priv(port->rmnet_dev);
this_cpu_inc(priv->pcpu_stats->stats.tx_drops);
dev_kfree_skb_any(skb);
return;
}
}
dev_queue_xmit(skb);
}
static void rmnet_map_flush_tx_packet_work(struct work_struct *work)
{
struct sk_buff *skb = NULL;
struct rmnet_port *port;
port = container_of(work, struct rmnet_port, agg_wq);
spin_lock_bh(&port->agg_lock);
if (likely(port->agg_state == -EINPROGRESS)) {
/* Buffer may have already been shipped out */
if (likely(port->skbagg_head)) {
skb = port->skbagg_head;
reset_aggr_params(port);
}
port->agg_state = 0;
}
spin_unlock_bh(&port->agg_lock);
if (skb)
rmnet_send_skb(port, skb);
}
static enum hrtimer_restart rmnet_map_flush_tx_packet_queue(struct hrtimer *t)
{
struct rmnet_port *port;
port = container_of(t, struct rmnet_port, hrtimer);
schedule_work(&port->agg_wq);
return HRTIMER_NORESTART;
}
unsigned int rmnet_map_tx_aggregate(struct sk_buff *skb, struct rmnet_port *port,
struct net_device *orig_dev)
{
struct timespec64 diff, last;
unsigned int len = skb->len;
struct sk_buff *agg_skb;
int size;
spin_lock_bh(&port->agg_lock);
memcpy(&last, &port->agg_last, sizeof(struct timespec64));
ktime_get_real_ts64(&port->agg_last);
if (!port->skbagg_head) {
/* Check to see if we should agg first. If the traffic is very
* sparse, don't aggregate.
*/
new_packet:
diff = timespec64_sub(port->agg_last, last);
size = port->egress_agg_params.bytes - skb->len;
if (size < 0) {
/* dropped */
spin_unlock_bh(&port->agg_lock);
return 0;
}
if (diff.tv_sec > 0 || diff.tv_nsec > RMNET_AGG_BYPASS_TIME_NSEC ||
size == 0)
goto no_aggr;
port->skbagg_head = skb_copy_expand(skb, 0, size, GFP_ATOMIC);
if (!port->skbagg_head)
goto no_aggr;
dev_kfree_skb_any(skb);
port->skbagg_head->protocol = htons(ETH_P_MAP);
port->agg_count = 1;
ktime_get_real_ts64(&port->agg_time);
skb_frag_list_init(port->skbagg_head);
goto schedule;
}
diff = timespec64_sub(port->agg_last, port->agg_time);
size = port->egress_agg_params.bytes - port->skbagg_head->len;
if (skb->len > size) {
agg_skb = port->skbagg_head;
reset_aggr_params(port);
spin_unlock_bh(&port->agg_lock);
hrtimer_cancel(&port->hrtimer);
rmnet_send_skb(port, agg_skb);
spin_lock_bh(&port->agg_lock);
goto new_packet;
}
if (skb_has_frag_list(port->skbagg_head))
port->skbagg_tail->next = skb;
else
skb_shinfo(port->skbagg_head)->frag_list = skb;
port->skbagg_head->len += skb->len;
port->skbagg_head->data_len += skb->len;
port->skbagg_head->truesize += skb->truesize;
port->skbagg_tail = skb;
port->agg_count++;
if (diff.tv_sec > 0 || diff.tv_nsec > port->egress_agg_params.time_nsec ||
port->agg_count >= port->egress_agg_params.count ||
port->skbagg_head->len == port->egress_agg_params.bytes) {
agg_skb = port->skbagg_head;
reset_aggr_params(port);
spin_unlock_bh(&port->agg_lock);
hrtimer_cancel(&port->hrtimer);
rmnet_send_skb(port, agg_skb);
return len;
}
schedule:
if (!hrtimer_active(&port->hrtimer) && port->agg_state != -EINPROGRESS) {
port->agg_state = -EINPROGRESS;
hrtimer_start(&port->hrtimer,
ns_to_ktime(port->egress_agg_params.time_nsec),
HRTIMER_MODE_REL);
}
spin_unlock_bh(&port->agg_lock);
return len;
no_aggr:
spin_unlock_bh(&port->agg_lock);
skb->protocol = htons(ETH_P_MAP);
dev_queue_xmit(skb);
return len;
}
void rmnet_map_update_ul_agg_config(struct rmnet_port *port, u32 size,
u32 count, u32 time)
{
spin_lock_bh(&port->agg_lock);
port->egress_agg_params.bytes = size;
WRITE_ONCE(port->egress_agg_params.count, count);
port->egress_agg_params.time_nsec = time * NSEC_PER_USEC;
spin_unlock_bh(&port->agg_lock);
}
void rmnet_map_tx_aggregate_init(struct rmnet_port *port)
{
hrtimer_init(&port->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
port->hrtimer.function = rmnet_map_flush_tx_packet_queue;
spin_lock_init(&port->agg_lock);
rmnet_map_update_ul_agg_config(port, 4096, 1, 800);
INIT_WORK(&port->agg_wq, rmnet_map_flush_tx_packet_work);
}
void rmnet_map_tx_aggregate_exit(struct rmnet_port *port)
{
hrtimer_cancel(&port->hrtimer);
cancel_work_sync(&port->agg_wq);
spin_lock_bh(&port->agg_lock);
if (port->agg_state == -EINPROGRESS) {
if (port->skbagg_head) {
dev_kfree_skb_any(port->skbagg_head);
reset_aggr_params(port);
}
port->agg_state = 0;
}
spin_unlock_bh(&port->agg_lock);
}
......@@ -29,7 +29,7 @@ void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
u64_stats_update_end(&pcpu_ptr->syncp);
}
void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
void rmnet_vnd_tx_fixup_len(unsigned int len, struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_pcpu_stats *pcpu_ptr;
......@@ -38,10 +38,15 @@ void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
u64_stats_update_begin(&pcpu_ptr->syncp);
pcpu_ptr->stats.tx_pkts++;
pcpu_ptr->stats.tx_bytes += skb->len;
pcpu_ptr->stats.tx_bytes += len;
u64_stats_update_end(&pcpu_ptr->syncp);
}
void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
{
rmnet_vnd_tx_fixup_len(skb->len, dev);
}
/* Network Device Operations */
static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
......@@ -210,7 +215,52 @@ static void rmnet_get_ethtool_stats(struct net_device *dev,
memcpy(data, st, ARRAY_SIZE(rmnet_gstrings_stats) * sizeof(u64));
}
static int rmnet_get_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_port *port;
port = rmnet_get_port_rtnl(priv->real_dev);
memset(kernel_coal, 0, sizeof(*kernel_coal));
kernel_coal->tx_aggr_max_bytes = port->egress_agg_params.bytes;
kernel_coal->tx_aggr_max_frames = port->egress_agg_params.count;
kernel_coal->tx_aggr_time_usecs = div_u64(port->egress_agg_params.time_nsec,
NSEC_PER_USEC);
return 0;
}
static int rmnet_set_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
struct rmnet_port *port;
port = rmnet_get_port_rtnl(priv->real_dev);
if (kernel_coal->tx_aggr_max_frames < 1 || kernel_coal->tx_aggr_max_frames > 64)
return -EINVAL;
if (kernel_coal->tx_aggr_max_bytes > 32768)
return -EINVAL;
rmnet_map_update_ul_agg_config(port, kernel_coal->tx_aggr_max_bytes,
kernel_coal->tx_aggr_max_frames,
kernel_coal->tx_aggr_time_usecs);
return 0;
}
static const struct ethtool_ops rmnet_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_TX_AGGR,
.get_coalesce = rmnet_get_coalesce,
.set_coalesce = rmnet_set_coalesce,
.get_ethtool_stats = rmnet_get_ethtool_stats,
.get_strings = rmnet_get_strings,
.get_sset_count = rmnet_get_sset_count,
......
......@@ -16,6 +16,7 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
int rmnet_vnd_dellink(u8 id, struct rmnet_port *port,
struct rmnet_endpoint *ep);
void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev);
void rmnet_vnd_tx_fixup_len(unsigned int len, struct net_device *dev);
void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev);
void rmnet_vnd_setup(struct net_device *dev);
int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev);
......
......@@ -217,6 +217,9 @@ __ethtool_get_link_ksettings(struct net_device *dev,
struct kernel_ethtool_coalesce {
u8 use_cqe_mode_tx;
u8 use_cqe_mode_rx;
u32 tx_aggr_max_bytes;
u32 tx_aggr_max_frames;
u32 tx_aggr_time_usecs;
};
/**
......@@ -260,7 +263,10 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
#define ETHTOOL_COALESCE_RATE_SAMPLE_INTERVAL BIT(21)
#define ETHTOOL_COALESCE_USE_CQE_RX BIT(22)
#define ETHTOOL_COALESCE_USE_CQE_TX BIT(23)
#define ETHTOOL_COALESCE_ALL_PARAMS GENMASK(23, 0)
#define ETHTOOL_COALESCE_TX_AGGR_MAX_BYTES BIT(24)
#define ETHTOOL_COALESCE_TX_AGGR_MAX_FRAMES BIT(25)
#define ETHTOOL_COALESCE_TX_AGGR_TIME_USECS BIT(26)
#define ETHTOOL_COALESCE_ALL_PARAMS GENMASK(26, 0)
#define ETHTOOL_COALESCE_USECS \
(ETHTOOL_COALESCE_RX_USECS | ETHTOOL_COALESCE_TX_USECS)
......@@ -288,6 +294,10 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
ETHTOOL_COALESCE_RATE_SAMPLE_INTERVAL)
#define ETHTOOL_COALESCE_USE_CQE \
(ETHTOOL_COALESCE_USE_CQE_RX | ETHTOOL_COALESCE_USE_CQE_TX)
#define ETHTOOL_COALESCE_TX_AGGR \
(ETHTOOL_COALESCE_TX_AGGR_MAX_BYTES | \
ETHTOOL_COALESCE_TX_AGGR_MAX_FRAMES | \
ETHTOOL_COALESCE_TX_AGGR_TIME_USECS)
#define ETHTOOL_STAT_NOT_SET (~0ULL)
......
......@@ -406,6 +406,9 @@ enum {
ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL, /* u32 */
ETHTOOL_A_COALESCE_USE_CQE_MODE_TX, /* u8 */
ETHTOOL_A_COALESCE_USE_CQE_MODE_RX, /* u8 */
ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES, /* u32 */
ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES, /* u32 */
ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS, /* u32 */
/* add new constants above here */
__ETHTOOL_A_COALESCE_CNT,
......
......@@ -105,7 +105,10 @@ static int coalesce_reply_size(const struct ethnl_req_info *req_base,
nla_total_size(sizeof(u32)) + /* _TX_MAX_FRAMES_HIGH */
nla_total_size(sizeof(u32)) + /* _RATE_SAMPLE_INTERVAL */
nla_total_size(sizeof(u8)) + /* _USE_CQE_MODE_TX */
nla_total_size(sizeof(u8)); /* _USE_CQE_MODE_RX */
nla_total_size(sizeof(u8)) + /* _USE_CQE_MODE_RX */
nla_total_size(sizeof(u32)) + /* _TX_AGGR_MAX_BYTES */
nla_total_size(sizeof(u32)) + /* _TX_AGGR_MAX_FRAMES */
nla_total_size(sizeof(u32)); /* _TX_AGGR_TIME_USECS */
}
static bool coalesce_put_u32(struct sk_buff *skb, u16 attr_type, u32 val,
......@@ -180,7 +183,13 @@ static int coalesce_fill_reply(struct sk_buff *skb,
coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_CQE_MODE_TX,
kcoal->use_cqe_mode_tx, supported) ||
coalesce_put_bool(skb, ETHTOOL_A_COALESCE_USE_CQE_MODE_RX,
kcoal->use_cqe_mode_rx, supported))
kcoal->use_cqe_mode_rx, supported) ||
coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES,
kcoal->tx_aggr_max_bytes, supported) ||
coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES,
kcoal->tx_aggr_max_frames, supported) ||
coalesce_put_u32(skb, ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS,
kcoal->tx_aggr_time_usecs, supported))
return -EMSGSIZE;
return 0;
......@@ -227,6 +236,9 @@ const struct nla_policy ethnl_coalesce_set_policy[] = {
[ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL] = { .type = NLA_U32 },
[ETHTOOL_A_COALESCE_USE_CQE_MODE_TX] = NLA_POLICY_MAX(NLA_U8, 1),
[ETHTOOL_A_COALESCE_USE_CQE_MODE_RX] = NLA_POLICY_MAX(NLA_U8, 1),
[ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES] = { .type = NLA_U32 },
[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES] = { .type = NLA_U32 },
[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS] = { .type = NLA_U32 },
};
int ethnl_set_coalesce(struct sk_buff *skb, struct genl_info *info)
......@@ -321,6 +333,12 @@ int ethnl_set_coalesce(struct sk_buff *skb, struct genl_info *info)
tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_TX], &mod);
ethnl_update_u8(&kernel_coalesce.use_cqe_mode_rx,
tb[ETHTOOL_A_COALESCE_USE_CQE_MODE_RX], &mod);
ethnl_update_u32(&kernel_coalesce.tx_aggr_max_bytes,
tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES], &mod);
ethnl_update_u32(&kernel_coalesce.tx_aggr_max_frames,
tb[ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES], &mod);
ethnl_update_u32(&kernel_coalesce.tx_aggr_time_usecs,
tb[ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS], &mod);
ret = 0;
if (!mod)
goto out_ops;
......
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