Thread (37 messages) 37 messages, 4 authors, 2018-08-21
STALE2962d

Revision v2 of 2 in this series.

Revisions (2)
  1. rfc [diff vs current]
  2. v2 current

[RFC PATCH net-next v2 05/17] ethtool: netlink bitset handling

From: Michal Kubecek <hidden>
Date: 2018-07-30 12:53:14
Also in: lkml
Subsystem: documentation, networking drivers, networking [ethtool], networking [general], the rest · Maintainers: Jonathan Corbet, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn, Linus Torvalds

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

Signed-off-by: Michal Kubecek <redacted>
---
 Documentation/networking/ethtool-netlink.txt |  56 +++
 include/uapi/linux/ethtool_netlink.h         |  31 ++
 net/ethtool/netlink.c                        | 395 +++++++++++++++++++
 net/ethtool/netlink.h                        |  26 ++
 4 files changed, 508 insertions(+)
diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 120376e0f91b..a49dfe3ef4bb 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -59,6 +59,62 @@ provided by kernel. In dump requests, device is not specified and kernel
 replies with one message per network device (only those for which the request
 is supported).
 
+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 1df263d9feb7..98d6fae315f3 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -23,6 +23,37 @@ enum {
 	ETHA_DEV_MAX = (__ETHA_DEV_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/ethtool/netlink.c b/net/ethtool/netlink.c
index bf234e5bc660..df065fd3dc80 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
 
 #include <linux/module.h>
+#include <linux/bitmap.h>
 #include <net/sock.h>
 #include <linux/ethtool_netlink.h>
 #include "netlink.h"
@@ -67,6 +68,400 @@ int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype)
 	return ret;
 }
 
+/* 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_arr32() is not guaranteed to do "in place" conversion correctly;
+ * moreover, we can use the fact that the conversion is no-op except for 64-bit
+ * big endian architectures
+ */
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords)
+{
+	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
+ */
+int ethnl_bitset32_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);
+}
+
+int ethnl_bitset_size(bool compact, unsigned int size, unsigned long *val,
+		      unsigned long *mask, const char *const *names)
+{
+	const unsigned int words = (size + 31) / 32;
+	u32 mask32[words];
+	u32 val32[words];
+	int ret;
+
+	bitmap_to_arr32(val32, val, size);
+	if (mask)
+		bitmap_to_arr32(mask32, mask, size);
+	ret = ethnl_bitset32_size(compact, size, val32, mask ? mask32 : NULL,
+				  names);
+	bitmap_from_arr32(val, val32, size);
+	if (mask)
+		bitmap_from_arr32(mask, mask32, size);
+
+	return ret;
+}
+
+/* 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)
+ */
+int ethnl_put_bitset32(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;
+}
+
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, bool compact,
+		     unsigned int size, unsigned long *val, unsigned long *mask,
+		     const char *const *names)
+{
+	const unsigned int words = (size + 31) / 32;
+	u32 mask32[words];
+	u32 val32[words];
+	int ret;
+
+	bitmap_to_arr32(val32, val, size);
+	if (mask)
+		bitmap_to_arr32(mask32, mask, size);
+	ret = ethnl_put_bitset32(skb, attrtype, compact, size, val32,
+				 mask ? mask32 : NULL, names);
+	bitmap_from_arr32(val, val32, size);
+	if (mask)
+		bitmap_from_arr32(mask, mask32, size);
+
+	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 (names[i] && !strncmp(names[i], name, name_len))
+			return i;
+
+	return n_names;
+}
+
+static int ethnl_update_bit(unsigned long *bitmap, unsigned long *bitmask,
+			    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) {
+		ETHNL_SET_ERRMSG(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) {
+			ETHNL_SET_ERRMSG(info, "bit index too high");
+			return genl_err_attr(info, -EOPNOTSUPP,
+					     tb[ETHA_BIT_INDEX]);
+		}
+		if (tb[ETHA_BIT_NAME] && names[idx] &&
+		    strncmp(nla_data(tb[ETHA_BIT_NAME]), names[idx],
+			    nla_len(tb[ETHA_BIT_NAME]))) {
+			ETHNL_SET_ERRMSG(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) {
+			ETHNL_SET_ERRMSG(info, "bit name not found");
+			return genl_err_attr(info, -EOPNOTSUPP,
+					     tb[ETHA_BIT_NAME]);
+		}
+	} else {
+		ETHNL_SET_ERRMSG(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);
+	if (bitmask)
+		set_bit(idx, bitmask);
+	return 0;
+}
+
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+			 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);
+		DECLARE_BITMAP(mask, nbits);
+		struct nlattr *bit_attr;
+		int rem;
+
+		*err = -EINVAL;
+		if (tb[ETHA_BITSET_VALUE] || tb[ETHA_BITSET_MASK])
+			return mod;
+		bitmap_copy(val, bitmap, nbits);
+		bitmap_zero(mask, nbits);
+		nla_for_each_nested(bit_attr, tb[ETHA_BITSET_BITS], rem) {
+			*err = ethnl_update_bit(val, mask, nbits, bit_attr,
+						names, info);
+			if (*err < 0)
+				return mod;
+		}
+		mod = !bitmap_equal(val, bitmap, nbits);
+		if (mod)
+			bitmap_copy(bitmap, val, nbits);
+		if (bitmask)
+			bitmap_copy(bitmask, mask, 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_arr32(val, nla_data(tb[ETHA_BITSET_VALUE]),
+				  change_bits);
+		bitmap_zero(mask, max_bits);
+		if (tb[ETHA_BITSET_MASK])
+			bitmap_from_arr32(mask, nla_data(tb[ETHA_BITSET_MASK]),
+					  change_bits);
+		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;
+		}
+
+		if (bitmask)
+			bitmap_copy(bitmask, mask, nbits);
+		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;
+}
+
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+			   struct nlattr *attr, int *err,
+			   const char *const *names, struct genl_info *info)
+{
+	DECLARE_BITMAP(tmp, nbits);
+	DECLARE_BITMAP(tmp_mask, nbits);
+	bool mod;
+
+	bitmap_from_arr32(tmp, bitmap, nbits);
+	bitmap_zero(tmp_mask, nbits);
+	mod = ethnl_update_bitset(tmp, tmp_mask, nbits, attr, err, names, info);
+	if (!*err && mod)
+		bitmap_to_arr32(bitmap, tmp, nbits);
+	if (bitmask)
+		bitmap_to_arr32(bitmask, tmp_mask, nbits);
+	return (!*err && mod);
+}
+
 /* create skb for a reply
  * payload: payload length (without netlink, genetlink and ethnl headers)
  * dev:     device the reply is about
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index acfed5ba6b54..6e9e854eec5d 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -20,6 +20,32 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 				 u16 dev_attrtype, struct genl_info *info,
 				 void **ehdrp);
 
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords);
+#else
+static inline void ethnl_bitmap_to_u32(unsigned long *bitmap,
+				       unsigned int nwords)
+{
+}
+#endif
+
+int ethnl_bitset_size(bool compact, unsigned int size, unsigned long *val,
+		      unsigned long *mask, const char *const *names);
+int ethnl_bitset32_size(bool compact, unsigned int size, u32 *val, u32 *mask,
+			const char *const *names);
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, bool compact,
+		     unsigned int size, unsigned long *val, unsigned long *mask,
+		     const char *const *names);
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, bool compact,
+		       unsigned int size, u32 *val, u32 *mask,
+		       const char *const *names);
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+			 unsigned int nbits, struct nlattr *attr, int *err,
+			 const char *const *names, struct genl_info *info);
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+			   struct nlattr *attr, int *err,
+			   const char *const *names, struct genl_info *info);
+
 static inline int ethnl_str_size(const char *s)
 {
 	return nla_total_size(strlen(s) + 1);
-- 
2.18.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help