Thread (26 messages) flat view 26 messages, 7 authors, 2017-12-18
STALE3197d

[RFC PATCH 4/9] ethtool: netlink bitset handling

From: Michal Kubecek <hidden>
Date: 2017-12-11 13:53:56
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/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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help