lkml.org 
[lkml]   [2017]   [Dec]   [11]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[RFC PATCH 4/9] ethtool: netlink bitset handling
    Date
    Declare attribute type constants and add helper functions to handle
    arbitrary length bit sets.

    Signed-off-by: Michal Kubecek <mkubecek@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
    \
     
     \ /
      Last update: 2017-12-11 14:57    [W:2.536 / U:0.036 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site