@@ -194,6 +194,17 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx)
return (u8 *)(ethtool_rxfh_context_indir(ctx) + ctx->indir_size);
}
+static inline size_t ethtool_rxfh_context_size(u32 indir_size, u32 key_size,
+ u16 priv_size)
+{
+ size_t indir_bytes = array_size(indir_size, sizeof(u32));
+ size_t flex_len;
+
+ flex_len = size_add(size_add(indir_bytes, key_size),
+ ALIGN(priv_size, sizeof(u32)));
+ return struct_size((struct ethtool_rxfh_context *)0, data, flex_len);
+}
+
/* declare a link mode bitmap */
#define __ETHTOOL_DECLARE_LINK_MODE_MASK(name) \
DECLARE_BITMAP(name, __ETHTOOL_LINK_MODE_MASK_NBITS)
@@ -731,6 +742,8 @@ struct ethtool_mm_stats {
* will remain unchanged.
* Returns a negative error code or zero. An error code must be returned
* if at least one unsupported change was requested.
+ * @rxfh_priv_size: size of the driver private data area the core should
+ * allocate for an RSS context.
* @get_rxfh_context: Get the contents of the RX flow hash indirection table,
* hash key, and/or hash function assiciated to the given rss context.
* Returns a negative error code or zero.
@@ -824,6 +837,7 @@ struct ethtool_ops {
u32 cap_link_lanes_supported:1;
u32 supported_coalesce_params;
u32 supported_ring_params;
+ u16 rxfh_priv_size;
void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *);
int (*get_regs_len)(struct net_device *);
void (*get_regs)(struct net_device *, struct ethtool_regs *, void *);
@@ -1249,6 +1249,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
{
int ret;
const struct ethtool_ops *ops = dev->ethtool_ops;
+ struct ethtool_rxfh_context *ctx = NULL;
struct ethtool_rxnfc rx_rings;
struct ethtool_rxfh rxfh;
u32 dev_indir_size = 0, dev_key_size = 0, i;
@@ -1256,7 +1257,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
u8 *hkey = NULL;
u8 *rss_config;
u32 rss_cfg_offset = offsetof(struct ethtool_rxfh, rss_config[0]);
- bool delete = false;
+ bool create = false, delete = false;
if (!ops->get_rxnfc || !ops->set_rxfh)
return -EOPNOTSUPP;
@@ -1275,6 +1276,7 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
/* Most drivers don't handle rss_context, check it's 0 as well */
if (rxfh.rss_context && !ops->set_rxfh_context)
return -EOPNOTSUPP;
+ create = rxfh.rss_context == ETH_RXFH_CONTEXT_ALLOC;
/* If either indir, hash key or function is valid, proceed further.
* Must request at least one change: indir size, hash key or function.
@@ -1332,13 +1334,42 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
}
}
+ if (create) {
+ if (delete) {
+ ret = -EINVAL;
+ goto out;
+ }
+ ctx = kzalloc(ethtool_rxfh_context_size(dev_indir_size,
+ dev_key_size,
+ ops->rxfh_priv_size),
+ GFP_KERNEL_ACCOUNT);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ ctx->indir_size = dev_indir_size;
+ ctx->key_size = dev_key_size;
+ ctx->hfunc = rxfh.hfunc;
+ ctx->priv_size = ops->rxfh_priv_size;
+ } else if (rxfh.rss_context) {
+ ctx = idr_find(&dev->ethtool->rss_ctx, rxfh.rss_context);
+ if (!ctx) {
+ ret = -ENOENT;
+ goto out;
+ }
+ }
+
if (rxfh.rss_context)
ret = ops->set_rxfh_context(dev, indir, hkey, rxfh.hfunc,
&rxfh.rss_context, delete);
else
ret = ops->set_rxfh(dev, indir, hkey, rxfh.hfunc);
- if (ret)
+ if (ret) {
+ if (create)
+ /* failed to create, free our new tracking entry */
+ kfree(ctx);
goto out;
+ }
if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, rss_context),
&rxfh.rss_context, sizeof(rxfh.rss_context)))
@@ -1351,6 +1382,42 @@ static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE)
dev->priv_flags |= IFF_RXFH_CONFIGURED;
}
+ /* Update rss_ctx tracking */
+ if (create) {
+ /* Ideally this should happen before calling the driver,
+ * so that we can fail more cleanly; but we don't have the
+ * context ID until the driver picks it, so we have to
+ * wait until after.
+ */
+ if (WARN_ON(idr_find(&dev->ethtool->rss_ctx, rxfh.rss_context))) {
+ /* context ID reused, our tracking is screwed */
+ kfree(ctx);
+ goto out;
+ }
+ /* Allocate the exact ID the driver gave us */
+ WARN_ON(idr_alloc(&dev->ethtool->rss_ctx, ctx, rxfh.rss_context,
+ rxfh.rss_context + 1, GFP_KERNEL) !=
+ rxfh.rss_context);
+ ctx->indir_no_change = rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE;
+ ctx->key_no_change = !rxfh.key_size;
+ }
+ if (delete) {
+ WARN_ON(idr_remove(&dev->ethtool->rss_ctx, rxfh.rss_context) != ctx);
+ kfree(ctx);
+ } else if (ctx) {
+ if (indir) {
+ for (i = 0; i < dev_indir_size; i++)
+ ethtool_rxfh_context_indir(ctx)[i] = indir[i];
+ ctx->indir_no_change = 0;
+ }
+ if (hkey) {
+ memcpy(ethtool_rxfh_context_key(ctx), hkey,
+ dev_key_size);
+ ctx->key_no_change = 0;
+ }
+ if (rxfh.hfunc != ETH_RSS_HASH_NO_CHANGE)
+ ctx->hfunc = rxfh.hfunc;
+ }
out:
kfree(rss_config);