Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753212AbdLKNx4 (ORCPT ); Mon, 11 Dec 2017 08:53:56 -0500 Received: from mx2.suse.de ([195.135.220.15]:60212 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753178AbdLKNxx (ORCPT ); Mon, 11 Dec 2017 08:53:53 -0500 Message-Id: In-Reply-To: References: From: Michal Kubecek Subject: [RFC PATCH 4/9] ethtool: netlink bitset handling To: netdev@vger.kernel.org Cc: linux-kernel@vger.kernel.org Date: Mon, 11 Dec 2017 14:53:51 +0100 (CET) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 14549 Lines: 485 Declare attribute type constants and add helper functions to handle arbitrary length bit sets. Signed-off-by: Michal Kubecek --- 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 #include #include +#include #include #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