Declare attribute type constants and add helper functions to handle
arbitrary length bit sets.

Signed-off-by: Michal Kubecek <mkube...@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  56 +++++
 include/uapi/linux/ethtool_netlink.h         |  31 +++
 net/core/ethtool_netlink.c                   | 346 +++++++++++++++++++++++++++
 3 files changed, 433 insertions(+)

diff --git a/Documentation/networking/ethtool-netlink.txt 
b/Documentation/networking/ethtool-netlink.txt
index c94da66cb5fb..893e5156f6a7 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -48,6 +48,62 @@ in "set" requests). For these attributes, the "true" value 
should be passed as
 number 1 but any non-zero value should be understood as "true" by recipient.
 
 
+Bit sets
+--------
+
+For short bitmaps of (reasonably) fixed length, standard NLA_BITFIELD32 type
+is used. For arbitrary length bitmaps, ethtool netlink uses a nested attribute
+with contents of one of two forms: compact (two binary bitmaps representing
+bit values and mask of affected bits) and bit-by-bit (list of bits identified
+by either index or name).
+
+Compact form: nested (bitset) atrribute contents:
+
+    ETHA_BITSET_SIZE   (u32)           number of significant bits
+    ETHA_BITSET_VALUE  (binary)        bitmap of bit values
+    ETHA_BITSET_MASK   (binary)        bitmap of valid bits
+
+Value and mask must have length at least ETHA_BITSET_SIZE bits rounded up to
+a multiple of 32 bits. They consist of 32-bit words in host byt order, words
+ordered from least significant to most significant (i.e. the same way as
+bitmaps are passed with ioctl interface).
+
+For compact form, ETHA_BITSET_SIZE and ETHA_BITSET_VALUE are mandatory.
+Similar to BITFIELD32, a compact form bit set requests to set bits in the mask
+to 1 (if set in value) or 0 (if not) and preserve the rest. If the mask is
+omitted, it is supposed to be "all ones", i.e. set all bits according to
+value.
+
+Kernel bit set length may differ from userspace length if older application is
+used on newer kernel or vice versa. If userspace bitmaps are longer, error is
+only issued if request actually tries to set bits not implemented in kernel.
+
+Bit-by-bit form: nested (bitset) attribute contents:
+
+    ETHA_BITSET_SIZE   (u32)           number of significant bits (optional)
+    ETHA_BITSET_BITS   (nested)        array of bits
+       ETHA_BITSET_BIT
+           ETHA_BIT_INDEX      (u32)           bit index (0 for LSB)
+            ETHA_BIT_NAME      (string)        bit name
+           ETHA_BIT_VALUE      (flag)          present if bit is set
+        ETHA_BITSET_BIT
+       ...
+
+Bit size is optional for bit-by-bit form. ETHA_BITSET_BITS nest can only
+contain ETHA_BITS_BIT attributes but there can be an arbitrary number of them.
+A bit may be identified by its index or by its name. When used in requests,
+listed bits are set to 0 or 1 according to ETHA_BIT_VALUE, the rest is
+preserved. A request fails if index exceeds kernel bit length or if name is
+not recognized.
+
+In requests, application can use either form. Form used by kernel in reply is
+determined by a flag in flags field of request header. Semantics of value and
+mask depends on the attribute. General idea is that flags control request
+processing, info_mask control which parts of the information are returned in
+"get" request and index identifies a particular subcommand or an object to
+which the request applies.
+
+
 List of message types
 ---------------------
 
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index 06cff2b52dfe..6db35f00ea32 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -26,6 +26,37 @@ enum {
        ETHTOOL_CMD_MAX = (__ETHTOOL_CMD_MAX - 1),
 };
 
+/* bit sets */
+
+enum {
+       ETHA_BIT_UNSPEC,
+       ETHA_BIT_INDEX,                         /* u32 */
+       ETHA_BIT_NAME,                          /* string */
+       ETHA_BIT_VALUE,                         /* flag */
+
+       __ETHA_BIT_MAX,
+       ETHA_BIT_MAX = (__ETHA_BIT_MAX - 1),
+};
+
+enum {
+       ETHA_BITS_UNSPEC,
+       ETHA_BITS_BIT,
+
+       __ETHA_BITS_MAX,
+       ETHA_BITS_MAX = (__ETHA_BITS_MAX - 1),
+};
+
+enum {
+       ETHA_BITSET_UNSPEC,
+       ETHA_BITSET_SIZE,                       /* u32 */
+       ETHA_BITSET_BITS,                       /* nest - ETHA_BITS_* */
+       ETHA_BITSET_VALUE,                      /* binary */
+       ETHA_BITSET_MASK,                       /* binary */
+
+       __ETHA_BITSET_MAX,
+       ETHA_BITSET_MAX = (__ETHA_BITSET_MAX - 1),
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/core/ethtool_netlink.c b/net/core/ethtool_netlink.c
index 22d23d057623..9f287e67f30b 100644
--- a/net/core/ethtool_netlink.c
+++ b/net/core/ethtool_netlink.c
@@ -3,6 +3,7 @@
 #include <linux/module.h>
 #include <linux/ethtool_netlink.h>
 #include <linux/netdevice.h>
+#include <linux/bitmap.h>
 #include <net/genetlink.h>
 #include "ethtool_common.h"
 
@@ -130,6 +131,351 @@ static struct net_device *ethnl_dev_get(struct genl_info 
*info)
        return ERR_PTR(-EINVAL);
 }
 
+/* bitset helper functions */
+
+static bool ethnl_test_bit(u32 *val, unsigned int index)
+{
+       if (val)
+               return val[index / 32] & (1 << (index % 32));
+       else
+               return true;
+}
+
+static void ethnl_copy_bitmap(u32 *dst, u32 *src, unsigned int size)
+{
+       unsigned int full_words = size / 32;
+
+       memcpy(dst, src, full_words * sizeof(u32));
+       if (size % 32 != 0)
+               dst[full_words] = src[full_words] & ((1U << (size % 32)) - 1);
+}
+
+static void ethnl_fill_bitmap(u32 *dst, unsigned int size)
+{
+       unsigned int full_words = size / 32;
+
+       memset(dst, 0xff, full_words * sizeof(u32));
+       if (size % 32 != 0)
+               dst[full_words] = (1U << (size % 32)) - 1;
+}
+
+/* convert standard kernel bitmap (long sized words) to ethtool one (u32 words)
+ * bitmap_to_u32array() can in fact do an "in place" conversion but it's not
+ * documented so we cannot rely on it; moreover, we can use the fact that this
+ * conversion is no-op except for 64-bit big endian architectures
+ */
+static void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords)
+{
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+       u32 *dst = (u32 *)bitmap;
+       unsigned int i;
+
+       for (i = 0; i < nwords; i++) {
+               unsigned long tmp = READ_ONCE(bitmap[i]);
+
+               dst[2 * i] = tmp & 0xffffffff;
+               dst[2 * i + 1] = tmp >> 32;
+       }
+#endif
+}
+
+/* calculate size for a bitset attribute
+ * see ethnl_put_bitset() for arguments
+ */
+static int ethnl_bitset_size(bool compact, unsigned int size, u32 *val,
+                            u32 *mask, const char *const *names)
+{
+       unsigned int nwords = (size + 31) / 32;
+       unsigned int len;
+
+       if (WARN_ON(!compact && !names))
+               return -EINVAL;
+       /* size */
+       len = nla_total_size(sizeof(u32));
+
+       if (compact) {
+               /* values, mask */
+               len += 2 * nla_total_size(nwords * sizeof(u32));
+       } else {
+               unsigned int bits_len = 0;
+               unsigned int bit_len, i;
+
+               for (i = 0; i < size; i++) {
+                       if (!ethnl_test_bit(mask, i))
+                               continue;
+                       /* index */
+                       bit_len = nla_total_size(sizeof(u32));
+                       /* name */
+                       bit_len += ethnl_str_size(names[i]);
+                       /* value */
+                       if (ethnl_test_bit(val, i))
+                               bit_len += nla_total_size(0);
+
+                       /* bit nest */
+                       bits_len += nla_total_size(bit_len);
+               }
+               len += nla_total_size(bits_len);
+       }
+
+       /* outermost nest */
+       return nla_total_size(len);
+}
+
+/* put bitset into a message
+ * skb:      skb with the message
+ * attrtype: attribute type for the bitset
+ * compact:  compact (bitmaps) or verbose (bit-by-bit with names) format
+ * size:     size of the set in bits
+ * val:      bitset values
+ * mask:     mask of valid bits
+ * names:    bit names (only used for verbose format)
+ */
+static int ethnl_put_bitset(struct sk_buff *skb, int attrtype, bool compact,
+                           unsigned int size, u32 *val, u32 *mask,
+                           const char *const *names)
+{
+       struct nlattr *nest;
+       struct nlattr *attr;
+       int ret;
+
+       if (WARN_ON(!compact && !names))
+               return -EINVAL;
+       nest = ethnl_nest_start(skb, attrtype);
+       if (!nest)
+               return -EMSGSIZE;
+
+       ret = -EMSGSIZE;
+       if (nla_put_u32(skb, ETHA_BITSET_SIZE, size))
+               goto err;
+       if (compact) {
+               unsigned int bytesize = ((size + 31) / 32) * sizeof(u32);
+
+               ret = -EMSGSIZE;
+               attr = nla_reserve(skb, ETHA_BITSET_VALUE, bytesize);
+               if (!attr)
+                       goto err;
+               ethnl_copy_bitmap(nla_data(attr), val, size);
+               attr = nla_reserve(skb, ETHA_BITSET_MASK, bytesize);
+               if (!attr)
+                       goto err;
+               if (mask)
+                       ethnl_copy_bitmap(nla_data(attr), mask, size);
+               else
+                       ethnl_fill_bitmap(nla_data(attr), size);
+       } else {
+               struct nlattr *bits;
+               unsigned int i;
+
+               bits = ethnl_nest_start(skb, ETHA_BITSET_BITS);
+               if (!bits)
+                       goto err;
+               for (i = 0; i < size; i++) {
+                       if (!ethnl_test_bit(mask, i))
+                               continue;
+                       attr = ethnl_nest_start(skb, ETHA_BITS_BIT);
+                       if (!attr ||
+                           nla_put_u32(skb, ETHA_BIT_INDEX, i) ||
+                           nla_put_string(skb, ETHA_BIT_NAME, names[i]))
+                               goto err;
+                       if (ethnl_test_bit(val, i))
+                               if (nla_put_flag(skb, ETHA_BIT_VALUE))
+                                       goto err;
+                       nla_nest_end(skb, attr);
+               }
+               nla_nest_end(skb, bits);
+       }
+
+       nla_nest_end(skb, nest);
+       return 0;
+err:
+       nla_nest_cancel(skb, nest);
+       return ret;
+}
+
+static const struct nla_policy bitset_policy[ETHA_BITSET_MAX + 1] = {
+       [ETHA_BITSET_UNSPEC]            = { .type = NLA_UNSPEC },
+       [ETHA_BITSET_SIZE]              = { .type = NLA_U32 },
+       [ETHA_BITSET_BITS]              = { .type = NLA_NESTED },
+       [ETHA_BITSET_VALUE]             = { .type = NLA_BINARY },
+       [ETHA_BITSET_MASK]              = { .type = NLA_BINARY },
+};
+
+static const struct nla_policy bit_policy[ETHA_BIT_MAX + 1] = {
+       [ETHA_BIT_UNSPEC]               = { .type = NLA_UNSPEC },
+       [ETHA_BIT_INDEX]                = { .type = NLA_U32 },
+       [ETHA_BIT_NAME]                 = { .type = NLA_STRING },
+       [ETHA_BIT_VALUE]                = { .type = NLA_FLAG },
+};
+
+static int ethnl_name_to_idx(const char *const *names, unsigned int n_names,
+                            const char *name, unsigned int name_len)
+{
+       unsigned int i;
+
+       for (i = 0; i < n_names; i++)
+               if (!strncmp(names[i], name, name_len))
+                       return i;
+
+       return n_names;
+}
+
+static int ethnl_update_bit(unsigned long *bitmap, unsigned int nbits,
+                           struct nlattr *bit_attr, const char *const *names,
+                           struct genl_info *info)
+{
+       struct nlattr *tb[ETHA_BIT_MAX + 1];
+       int ret, idx;
+
+       if (nla_type(bit_attr) != ETHA_BITS_BIT) {
+               GENL_SET_ERR_MSG(info,
+                                "ETHA_BITSET_BITS can contain only 
ETHA_BITS_BIT");
+               return genl_err_attr(info, -EINVAL, bit_attr);
+       }
+       ret = nla_parse_nested(tb, ETHA_BIT_MAX, bit_attr, bit_policy,
+                              info->extack);
+       if (ret < 0)
+               return ret;
+
+       if (tb[ETHA_BIT_INDEX]) {
+               idx = nla_get_u32(tb[ETHA_BIT_INDEX]);
+               if (idx >= nbits) {
+                       GENL_SET_ERR_MSG(info, "bit index too high");
+                       return genl_err_attr(info, -EOPNOTSUPP,
+                                            tb[ETHA_BIT_INDEX]);
+               }
+               if (tb[ETHA_BIT_NAME] &&
+                   strncmp(nla_data(tb[ETHA_BIT_NAME]), names[idx],
+                           nla_len(tb[ETHA_BIT_NAME]))) {
+                       GENL_SET_ERR_MSG(info, "bit index and name mismatch");
+                       return genl_err_attr(info, -EINVAL, bit_attr);
+               }
+       } else if (tb[ETHA_BIT_NAME]) {
+               idx = ethnl_name_to_idx(names, nbits,
+                                       nla_data(tb[ETHA_BIT_NAME]),
+                                       nla_len(tb[ETHA_BIT_NAME]));
+               if (idx >= nbits) {
+                       GENL_SET_ERR_MSG(info, "bit index too high");
+                       return genl_err_attr(info, -EOPNOTSUPP,
+                                            tb[ETHA_BIT_NAME]);
+               }
+       } else {
+               GENL_SET_ERR_MSG(info, "neither bit index nor name specified");
+               return genl_err_attr(info, -EINVAL, bit_attr);
+       }
+
+       if (tb[ETHA_BIT_VALUE])
+               set_bit(idx, bitmap);
+       else
+               clear_bit(idx, bitmap);
+       return 0;
+}
+
+static bool ethnl_update_bitset(unsigned long *bitmap, unsigned int nbits,
+                               struct nlattr *attr, int *err,
+                               const char *const *names,
+                               struct genl_info *info)
+{
+       struct nlattr *tb[ETHA_BITSET_MAX + 1];
+       unsigned int change_bits = 0;
+       bool mod = false;
+
+       if (!attr)
+               return false;
+       *err = nla_parse_nested(tb, ETHA_BITSET_MAX, attr, bitset_policy,
+                               info->extack);
+       if (*err < 0)
+               return mod;
+
+       /* To let new userspace to work with old kernel, we allow bitmaps
+        * from userspace to be longer than kernel ones and only issue an
+        * error if userspace actually tries to change a bit not existing
+        * in kernel.
+        */
+       if (tb[ETHA_BITSET_SIZE])
+               change_bits = nla_get_u32(tb[ETHA_BITSET_SIZE]);
+
+       if (tb[ETHA_BITSET_BITS]) {
+               DECLARE_BITMAP(val, nbits);
+               struct nlattr *bit_attr;
+               int rem;
+
+               *err = -EINVAL;
+               if (tb[ETHA_BITSET_VALUE] || tb[ETHA_BITSET_MASK])
+                       return mod;
+               bitmap_copy(val, bitmap, __ETHTOOL_LINK_MODE_MASK_NBITS);
+               nla_for_each_nested(bit_attr, tb[ETHA_BITSET_BITS], rem) {
+                       *err = ethnl_update_bit(val, nbits, bit_attr, names,
+                                               info);
+                       if (*err < 0)
+                               return mod;
+               }
+               mod = !bitmap_equal(val, bitmap, nbits);
+               if (mod)
+                       bitmap_copy(bitmap, val, nbits);
+       } else {
+               const unsigned int max_bits =
+                       max_t(unsigned int, nbits, change_bits);
+               unsigned int nwords = (change_bits + 31) / 32;
+               DECLARE_BITMAP(mask, max_bits);
+               DECLARE_BITMAP(val, max_bits);
+
+               *err = -EINVAL;
+               if (!change_bits || !tb[ETHA_BITSET_VALUE])
+                       return mod;
+               if (nla_len(tb[ETHA_BITSET_VALUE]) < nwords * sizeof(u32))
+                       return mod;
+               if (tb[ETHA_BITSET_MASK] &&
+                   nla_len(tb[ETHA_BITSET_VALUE]) < nwords * sizeof(u32))
+                       return mod;
+
+               bitmap_zero(val, max_bits);
+               bitmap_from_u32array(val, change_bits,
+                                    nla_data(tb[ETHA_BITSET_VALUE]), nwords);
+               bitmap_zero(mask, max_bits);
+               if (tb[ETHA_BITSET_MASK])
+                       bitmap_from_u32array(mask, change_bits,
+                                            nla_data(tb[ETHA_BITSET_MASK]),
+                                            nwords);
+               else
+                       bitmap_fill(mask, change_bits);
+
+               if (nbits < change_bits) {
+                       unsigned int idx = find_next_bit(mask, max_bits, nbits);
+
+                       *err = -EINVAL;
+                       if (idx >= nbits)
+                               return mod;
+               }
+
+               bitmap_and(val, val, mask, nbits);
+               bitmap_complement(mask, mask, nbits);
+               bitmap_and(mask, mask, bitmap, nbits);
+               bitmap_or(val, val, mask, nbits);
+               mod = !bitmap_equal(val, bitmap, nbits);
+               if (mod)
+                       bitmap_copy(bitmap, val, nbits);
+       }
+
+       *err = 0;
+       return mod;
+}
+
+static bool ethnl_update_bitset32(u32 *bitmap, unsigned int nbits,
+                                 struct nlattr *attr, int *err,
+                                 const char *const *names,
+                                 struct genl_info *info)
+{
+       unsigned int nwords = (nbits + 31) / 32;
+       DECLARE_BITMAP(tmp, nbits);
+       bool mod;
+
+       bitmap_from_u32array(tmp, nbits, bitmap, nwords);
+       mod = ethnl_update_bitset(tmp, nbits, attr, err, names, info);
+       if (!*err && mod)
+               bitmap_to_u32array(bitmap, nwords, tmp, nbits);
+       return (!*err && mod);
+}
+
 static void warn_partial_info(struct genl_info *info)
 {
        GENL_SET_ERR_MSG(info, "not all requested data could be retrieved");
-- 
2.15.1

Reply via email to