引言

在 InfiniBand 网络中,IP over InfiniBand (IPoIB) 是一种将 IP 协议运行在 InfiniBand 网络上的技术。为了实现 IPoIB 设备的管理和配置,Linux 内核提供了 Netlink 接口,允许用户空间程序与内核中的 IPoIB 驱动进行交互。ipoib_netlink.c 文件是 IPoIB 驱动的一部分,主要负责实现与 Netlink 相关的操作,包括设备的创建、配置、信息获取等。

本文将对 ipoib_netlink.c 文件进行详细分析,探讨其实现的功能、数据结构以及关键函数的作用。

文件概述

ipoib_netlink.c 文件的主要功能是通过 Netlink 接口管理 IPoIB 设备。具体来说,它实现了以下功能:

  1. IPoIB 设备的创建和配置:通过 Netlink 接口创建新的 IPoIB 子接口,并配置其属性(如 P_Key、模式、是否启用多播等)。

  2. IPoIB 设备信息的获取:通过 Netlink 接口获取 IPoIB 设备的当前配置信息。

  3. IPoIB 设备属性的修改:通过 Netlink 接口修改 IPoIB 设备的属性(如模式、多播设置等)。

代码分析

1. 头文件包含
#include <linux/netdevice.h>
#include <linux/if_arp.h>      /* For ARPHRD_xxx */
#include <linux/module.h>
#include <net/rtnetlink.h>
#include "ipoib.h"
  • linux/netdevice.h:提供网络设备相关的结构和函数。

  • linux/if_arp.h:定义了以太网地址类型,如 ARPHRD_INFINIBAND。

  • linux/module.h:用于模块的初始化和退出。

  • net/rtnetlink.h:提供与 Netlink 相关的结构和函数,用于内核与用户空间的通信。

  • ipoib.h:自定义的 IPoIB 驱动头文件,包含驱动特定的定义和结构。

2. ipoib_policy 结构体
static const struct nla_policy ipoib_policy[IFLA_IPOIB_MAX + 1] = {
    [IFLA_IPOIB_PKEY]    = { .type = NLA_U16 },
    [IFLA_IPOIB_MODE]    = { .type = NLA_U16 },
    [IFLA_IPOIB_UMCAST]  = { .type = NLA_U16 },
};

ipoib_policy 结构体定义了 Netlink 属性的验证策略。ipoib_policy 数组指定了 IPoIB 设备支持的 Netlink 属性及其类型。例如,IFLA_IPOIB_PKEY 属性是一个 16 位无符号整数(NLA_U16)。

3. ipoib_fill_info 函数
static int ipoib_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
    struct ipoib_dev_priv *priv = ipoib_priv(dev);
    u16 val;

    if (nla_put_u16(skb, IFLA_IPOIB_PKEY, priv->pkey))
        goto nla_put_failure;

    val = test_bit(IPOIB_FLAG_ADMIN_CM, &priv->flags);
    if (nla_put_u16(skb, IFLA_IPOIB_MODE, val))
        goto nla_put_failure;

    val = test_bit(IPOIB_FLAG_UMCAST, &priv->flags);
    if (nla_put_u16(skb, IFLA_IPOIB_UMCAST, val))
        goto nla_put_failure;

    return 0;

nla_put_failure:
    return -EMSGSIZE;
}

ipoib_fill_info 函数用于将 IPoIB 设备的当前配置信息填充到 Netlink 消息中。它通过 nla_put_u16 函数将设备的 P_Key、模式和多播设置等信息添加到 sk_buff 中。如果添加失败,则返回 -EMSGSIZE。

4. ipoib_changelink 函数
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
                            struct nlattr *data[], struct netlink_ext_ack *extack)
{
    struct ipoib_dev_priv *priv = ipoib_priv(dev);
    u16 mode, umcast;
    int ret = 0;

    if (data[IFLA_IPOIB_MODE]) {
        mode  = nla_get_u16(data[IFLA_IPOIB_MODE]);
        if (mode == IPOIB_MODE_DATAGRAM)
            ret = priv->fp.ipoib_set_mode(dev, "datagram\n");
        else if (mode == IPOIB_MODE_CONNECTED)
            ret = priv->fp.ipoib_set_mode(dev, "connected\n");
        else
            ret = -EINVAL;

        if (ret < 0)
            goto out_err;
    }

    if (data[IFLA_IPOIB_UMCAST]) {
        umcast = nla_get_u16(data[IFLA_IPOIB_UMCAST]);
        ipoib_set_umcast(dev, umcast);
    }

out_err:
    return ret;
}

ipoib_changelink 函数用于修改 IPoIB 设备的属性。它根据传入的 Netlink 属性数据(data 参数)来设置设备的工作模式(IPOIB_MODE_DATAGRAM 或 IPOIB_MODE_CONNECTED)以及是否启用多播(umcast)。如果设置失败,则返回相应的错误码。

5. ipoib_new_child_link 函数
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
                                struct nlattr *tb[], struct nlattr *data[],
                                struct netlink_ext_ack *extack)
{
    struct net_device *pdev;
    struct ipoib_dev_priv *ppriv;
    struct ipoib_dev_priv *priv;
    u16 child_pkey;
    int err;

    if (!tb[IFLA_LINK])
        return -EINVAL;

    pdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));

    if (!pdev || pdev->type != ARPHRD_INFINIBAND)
        return -ENODEV;

    ppriv = ipoib_priv(pdev);

    if (test_bit(IPOIB_FLAG_SUBINTERFACE, &ppriv->flags)) {
        ipoib_warn(ppriv, "child creation disallowed for child devices\n");
        return -EINVAL;
    }

    if (!data || !data[IFLA_IPOIB_PKEY]) {
        ipoib_dbg(ppriv, "no pkey specified, using parent pkey\n");
        child_pkey  = ppriv->pkey;
    } else
        child_pkey  = nla_get_u16(data[IFLA_IPOIB_PKEY]);

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    err = ipoib_set_fp_rss(priv, ppriv->ca);
    if (err) {
        ipoib_warn(ppriv, "failed to set pkey function pointers\n");
        goto out;
    }

    err = ipoib_intf_init(ppriv->ca, ppriv->port, dev->name, dev, priv);
    if (err) {
        ipoib_warn(ppriv, "failed to initialize pkey device\n");
        goto out;
    }

    err = __ipoib_vlan_add(ppriv, ipoib_priv(dev),
                           child_pkey, IPOIB_RTNL_CHILD);
    if (err)
        goto out;

    if (data) {
        err = ipoib_changelink(dev, tb, data, extack);
        if (err) {
            unregister_netdevice(dev);
            return err;
        }
    }

    return 0;
out:
    kfree(priv);
    return err;
}

ipoib_new_child_link 函数用于创建一个新的 IPoIB 子接口。它首先检查父设备是否存在并且类型是否正确,然后根据传入的 Netlink 属性数据来初始化子接口的 P_Key 和其他属性。如果初始化成功,则调用 ipoib_changelink 函数来进一步配置设备。

6. ipoib_get_size 函数
static size_t ipoib_get_size(const struct net_device *dev)
{
    return nla_total_size(2) +    /* IFLA_IPOIB_PKEY   */
           nla_total_size(2) +    /* IFLA_IPOIB_MODE   */
           nla_total_size(2);    /* IFLA_IPOIB_UMCAST */
}

ipoib_get_size 函数用于计算 IPoIB 设备的 Netlink 消息大小。它返回设备属性(P_Key、模式、多播设置)的总大小。

7. ipoib_link_ops 结构体
static struct rtnl_link_ops ipoib_link_ops __read_mostly = {
    .kind        = "ipoib",
    .maxtype     = IFLA_IPOIB_MAX,
    .policy      = ipoib_policy,
    .priv_size   = sizeof(struct ipoib_dev_priv),
    .setup       = ipoib_setup_common,
    .newlink     = ipoib_new_child_link,
    .changelink  = ipoib_changelink,
    .get_size    = ipoib_get_size,
    .fill_info   = ipoib_fill_info,
};

ipoib_link_ops 结构体定义了 IPoIB 设备的 Netlink 操作集。它指定了设备类型(kind)、属性策略(policy)、设备私有数据结构的大小(priv_size)以及各种操作函数(如 newlink、changelink、fill_info 等)。

8. ipoib_netlink_init 和 ipoib_netlink_fini 函数
int __init ipoib_netlink_init(void)
{
    return rtnl_link_register(&ipoib_link_ops);
}

void __exit ipoib_netlink_fini(void)
{
    rtnl_link_unregister(&ipoib_link_ops);
}

这两个函数分别用于在模块加载时注册和卸载时注销 IPoIB 的 Netlink 操作集。

9. 模块别名
MODULE_ALIAS_RTNL_LINK("ipoib");

MODULE_ALIAS_RTNL_LINK("ipoib") 为 rtnl 链接操作设置别名,以便在用户空间通过名称引用该接口类型。

总结

ipoib_netlink.c 文件的主要功能是通过 Netlink 接口管理 IPoIB 设备的创建、配置和信息获取。它定义了 IPoIB 设备的 Netlink 操作集,并实现了相关的操作函数。通过这些函数,用户空间程序可以通过 Netlink 接口与内核中的 IPoIB 驱动进行交互,从而配置和管理 IPoIB 设备。

该文件是 IPoIB 驱动的重要组成部分,确保了 IPoIB 设备在内核中的灵活管理和配置。通过 Netlink 接口,用户空间程序可以动态地创建、修改和查询 IPoIB 设备的状态,从而满足不同应用场景的需求。

drivers\infiniband\ulp\ipoib\ipoib_netlink.c

/*
 * Copyright (c) 2012 Mellanox Technologies. -  All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <linux/netdevice.h>
#include <linux/if_arp.h>      /* For ARPHRD_xxx */
#include <linux/module.h>
#include <net/rtnetlink.h>
#include "ipoib.h"

static const struct nla_policy ipoib_policy[IFLA_IPOIB_MAX + 1] = {
	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
};

static int ipoib_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
	struct ipoib_dev_priv *priv = ipoib_priv(dev);
	u16 val;

	if (nla_put_u16(skb, IFLA_IPOIB_PKEY, priv->pkey))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_ADMIN_CM, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_MODE, val))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_UMCAST, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_UMCAST, val))
		goto nla_put_failure;

	return 0;

nla_put_failure:
	return -EMSGSIZE;
}

#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[],
			    struct netlink_ext_ack *extack)
#else
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[])
#endif
{
	struct ipoib_dev_priv *priv = ipoib_priv(dev);
	u16 mode, umcast;
	int ret = 0;

	if (data[IFLA_IPOIB_MODE]) {
		mode  = nla_get_u16(data[IFLA_IPOIB_MODE]);
		if (mode == IPOIB_MODE_DATAGRAM)
			ret = priv->fp.ipoib_set_mode(dev, "datagram\n");
		else if (mode == IPOIB_MODE_CONNECTED)
			ret = priv->fp.ipoib_set_mode(dev, "connected\n");
		else
			ret = -EINVAL;

		if (ret < 0)
			goto out_err;
	}

	if (data[IFLA_IPOIB_UMCAST]) {
		umcast = nla_get_u16(data[IFLA_IPOIB_UMCAST]);
		ipoib_set_umcast(dev, umcast);
	}

out_err:
	return ret;
}

#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
				struct nlattr *tb[], struct nlattr *data[],
				struct netlink_ext_ack *extack)
#elif defined(HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS)
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
				struct nlattr *tb[], struct nlattr *data[])
#else
static int ipoib_new_child_link(struct net_device *dev,
				struct nlattr *tb[], struct nlattr *data[])
#endif
{
	struct net_device *pdev;
	struct ipoib_dev_priv *ppriv;
	struct ipoib_dev_priv *priv;
	u16 child_pkey;
	int err;

	if (!tb[IFLA_LINK])
		return -EINVAL;
#ifdef HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS
	pdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
#else
	pdev = __dev_get_by_index(dev_net(dev), nla_get_u32(tb[IFLA_LINK]));
#endif

	if (!pdev || pdev->type != ARPHRD_INFINIBAND)
		return -ENODEV;

	ppriv = ipoib_priv(pdev);

	if (test_bit(IPOIB_FLAG_SUBINTERFACE, &ppriv->flags)) {
		ipoib_warn(ppriv, "child creation disallowed for child devices\n");
		return -EINVAL;
	}

	if (!data || !data[IFLA_IPOIB_PKEY]) {
		ipoib_dbg(ppriv, "no pkey specified, using parent pkey\n");
		child_pkey  = ppriv->pkey;
	} else
		child_pkey  = nla_get_u16(data[IFLA_IPOIB_PKEY]);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	err = ipoib_set_fp_rss(priv, ppriv->ca);
	if (err) {
		ipoib_warn(ppriv, "failed to set pkey function pointers\n");
		goto out;
	}

	err = ipoib_intf_init(ppriv->ca, ppriv->port, dev->name, dev, priv);
	if (err) {
		ipoib_warn(ppriv, "failed to initialize pkey device\n");
		goto out;
	}

	err = __ipoib_vlan_add(ppriv, ipoib_priv(dev),
			       child_pkey, IPOIB_RTNL_CHILD);
	if (err)
		goto out;

	if (data) {
#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
		err = ipoib_changelink(dev, tb, data, extack);
#else
		err = ipoib_changelink(dev, tb, data);
#endif
		if (err) {
			unregister_netdevice(dev);
			return err;
		}
	}

	return 0;
out:
	kfree(priv);
	return err;
}

static size_t ipoib_get_size(const struct net_device *dev)
{
	return nla_total_size(2) +	/* IFLA_IPOIB_PKEY   */
		nla_total_size(2) +	/* IFLA_IPOIB_MODE   */
		nla_total_size(2);	/* IFLA_IPOIB_UMCAST */
}

static struct rtnl_link_ops ipoib_link_ops __read_mostly = {
	.kind		= "ipoib",
	.maxtype	= IFLA_IPOIB_MAX,
	.policy		= ipoib_policy,
	.priv_size	= sizeof(struct ipoib_dev_priv),
	.setup		= ipoib_setup_common,
	.newlink	= ipoib_new_child_link,
	.changelink	= ipoib_changelink,
	.get_size	= ipoib_get_size,
	.fill_info	= ipoib_fill_info,
};

struct rtnl_link_ops *ipoib_get_link_ops(void)
{
	return &ipoib_link_ops;
}

int __init ipoib_netlink_init(void)
{
	return rtnl_link_register(&ipoib_link_ops);
}

void __exit ipoib_netlink_fini(void)
{
	rtnl_link_unregister(&ipoib_link_ops);
}

MODULE_ALIAS_RTNL_LINK("ipoib");

这个文件 ipoib_netlink.c 是 InfiniBand 协议中的 IP over InfiniBand (IPoIB) 驱动的一部分,主要用于处理与 Linux 内核的网络链接层(Netlink)相关的操作。Netlink 是 Linux 内核与用户空间进程之间进行通信的一种机制,通常用于配置网络设备、路由、策略路由等。

文件功能概述

该文件的主要功能是通过 Netlink 接口来管理和配置 IPoIB 设备。具体来说,它实现了以下功能:

  1. IPoIB 设备的创建和配置:通过 Netlink 接口创建新的 IPoIB 子接口,并配置其属性(如 P_Key、模式、是否启用多播等)。

  2. IPoIB 设备信息的获取:通过 Netlink 接口获取 IPoIB 设备的当前配置信息。

  3. IPoIB 设备属性的修改:通过 Netlink 接口修改 IPoIB 设备的属性(如模式、多播设置等)。

代码分析

1. ipoib_policy 结构体
static const struct nla_policy ipoib_policy[IFLA_IPOIB_MAX + 1] = {
	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
};

这个结构体定义了 Netlink 属性的验证策略。ipoib_policy 数组指定了 IPoIB 设备支持的 Netlink 属性及其类型。例如,IFLA_IPOIB_PKEY 属性是一个 16 位无符号整数(NLA_U16)。

2. ipoib_fill_info 函数
static int ipoib_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
	struct ipoib_dev_priv *priv = ipoib_priv(dev);
	u16 val;

	if (nla_put_u16(skb, IFLA_IPOIB_PKEY, priv->pkey))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_ADMIN_CM, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_MODE, val))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_UMCAST, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_UMCAST, val))
		goto nla_put_failure;

	return 0;

nla_put_failure:
	return -EMSGSIZE;
}

这个函数用于将 IPoIB 设备的当前配置信息填充到 Netlink 消息中。它通过 nla_put_u16 函数将设备的 P_Key、模式和多播设置等信息添加到 sk_buff 中。如果添加失败,则返回 -EMSGSIZE。

3. ipoib_changelink 函数
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[], struct netlink_ext_ack *extack)
{
	struct ipoib_dev_priv *priv = ipoib_priv(dev);
	u16 mode, umcast;
	int ret = 0;

	if (data[IFLA_IPOIB_MODE]) {
		mode  = nla_get_u16(data[IFLA_IPOIB_MODE]);
		if (mode == IPOIB_MODE_DATAGRAM)
			ret = priv->fp.ipoib_set_mode(dev, "datagram\n");
		else if (mode == IPOIB_MODE_CONNECTED)
			ret = priv->fp.ipoib_set_mode(dev, "connected\n");
		else
			ret = -EINVAL;

		if (ret < 0)
			goto out_err;
	}

	if (data[IFLA_IPOIB_UMCAST]) {
		umcast = nla_get_u16(data[IFLA_IPOIB_UMCAST]);
		ipoib_set_umcast(dev, umcast);
	}

out_err:
	return ret;
}

这个函数用于修改 IPoIB 设备的属性。它根据传入的 Netlink 属性数据(data 参数)来设置设备的工作模式(IPOIB_MODE_DATAGRAM 或 IPOIB_MODE_CONNECTED)以及是否启用多播(umcast)。如果设置失败,则返回相应的错误码。

4. ipoib_new_child_link 函数
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
				struct nlattr *tb[], struct nlattr *data[],
				struct netlink_ext_ack *extack)
{
	struct net_device *pdev;
	struct ipoib_dev_priv *ppriv;
	struct ipoib_dev_priv *priv;
	u16 child_pkey;
	int err;

	if (!tb[IFLA_LINK])
		return -EINVAL;

	pdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));

	if (!pdev || pdev->type != ARPHRD_INFINIBAND)
		return -ENODEV;

	ppriv = ipoib_priv(pdev);

	if (test_bit(IPOIB_FLAG_SUBINTERFACE, &ppriv->flags)) {
		ipoib_warn(ppriv, "child creation disallowed for child devices\n");
		return -EINVAL;
	}

	if (!data || !data[IFLA_IPOIB_PKEY]) {
		ipoib_dbg(ppriv, "no pkey specified, using parent pkey\n");
		child_pkey  = ppriv->pkey;
	} else
		child_pkey  = nla_get_u16(data[IFLA_IPOIB_PKEY]);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	err = ipoib_set_fp_rss(priv, ppriv->ca);
	if (err) {
		ipoib_warn(ppriv, "failed to set pkey function pointers\n");
		goto out;
	}

	err = ipoib_intf_init(ppriv->ca, ppriv->port, dev->name, dev, priv);
	if (err) {
		ipoib_warn(ppriv, "failed to initialize pkey device\n");
		goto out;
	}

	err = __ipoib_vlan_add(ppriv, ipoib_priv(dev),
			       child_pkey, IPOIB_RTNL_CHILD);
	if (err)
		goto out;

	if (data) {
		err = ipoib_changelink(dev, tb, data, extack);
		if (err) {
			unregister_netdevice(dev);
			return err;
		}
	}

	return 0;
out:
	kfree(priv);
	return err;
}

这个函数用于创建一个新的 IPoIB 子接口。它首先检查父设备是否存在并且类型是否正确,然后根据传入的 Netlink 属性数据来初始化子接口的 P_Key 和其他属性。如果初始化成功,则调用 ipoib_changelink 函数来进一步配置设备。

5. ipoib_get_size 函数
static size_t ipoib_get_size(const struct net_device *dev)
{
	return nla_total_size(2) +	/* IFLA_IPOIB_PKEY   */
		nla_total_size(2) +	/* IFLA_IPOIB_MODE   */
		nla_total_size(2);	/* IFLA_IPOIB_UMCAST */
}

这个函数用于计算 IPoIB 设备的 Netlink 消息大小。它返回设备属性(P_Key、模式、多播设置)的总大小。

6. ipoib_link_ops 结构体
static struct rtnl_link_ops ipoib_link_ops __read_mostly = {
	.kind		= "ipoib",
	.maxtype	= IFLA_IPOIB_MAX,
	.policy		= ipoib_policy,
	.priv_size	= sizeof(struct ipoib_dev_priv),
	.setup		= ipoib_setup_common,
	.newlink	= ipoib_new_child_link,
	.changelink	= ipoib_changelink,
	.get_size	= ipoib_get_size,
	.fill_info	= ipoib_fill_info,
};

这个结构体定义了 IPoIB 设备的 Netlink 操作集。它指定了设备类型(kind)、属性策略(policy)、设备私有数据结构的大小(priv_size)以及各种操作函数(如 newlink、changelink、fill_info 等)。

7. ipoib_netlink_init 和 ipoib_netlink_fini 函数
int __init ipoib_netlink_init(void)
{
	return rtnl_link_register(&ipoib_link_ops);
}

void __exit ipoib_netlink_fini(void)
{
	rtnl_link_unregister(&ipoib_link_ops);
}

这两个函数分别用于在模块加载时注册和卸载时注销 IPoIB 的 Netlink 操作集。

总结

ipoib_netlink.c 文件的主要功能是通过 Netlink 接口管理 IPoIB 设备的创建、配置和信息获取。它定义了 IPoIB 设备的 Netlink 操作集,并实现了相关的操作函数。通过这些函数,用户空间程序可以通过 Netlink 接口与内核中的 IPoIB 驱动进行交互,从而配置和管理 IPoIB 设备。

该文件是 InfiniBand IP over InfiniBand (IPOIB) 驱动的一部分,主要负责处理与网络命名空间(Netlink)相关的操作。以下是各个部分的详细解释:

1. 头文件包含

#include <linux/netdevice.h>
#include <linux/if_arp.h>      /* For ARPHRD_xxx */
#include <linux/module.h>
#include <net/rtnetlink.h>
#include "ipoib.h"
  • linux/netdevice.h: 提供网络设备相关的结构和函数。
  • linux/if_arp.h: 定义了以太网地址类型,如 ARPHRD_INFINIBAND。
  • linux/module.h: 用于模块的初始化和退出。
  • net/rtnetlink.h: 提供与 Netlink 相关的结构和函数,用于内核与用户空间的通信。
  • ipoib.h: 自定义的 IPOIB 驱动头文件,包含驱动特定的定义和结构。

2. NLA(Netlink Attribute)策略

static const struct nla_policy ipoib_policy[IFLA_IPOIB_MAX + 1] = {
	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
};
  • 定义了 IPOIB 接口支持的 Netlink 属性及其类型:
    • IFLA_IPOIB_PKEY: 16 位 PKEY 值。
    • IFLA_IPOIB_MODE: 16 位模式(如数据报模式或连接模式)。
    • IFLA_IPOIB_UMCAST: 16 位是否启用多播。

3. 填充接口信息

static int ipoib_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
	struct ipoib_dev_priv *priv = ipoib_priv(dev);
	u16 val;

	if (nla_put_u16(skb, IFLA_IPOIB_PKEY, priv->pkey))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_ADMIN_CM, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_MODE, val))
		goto nla_put_failure;

	val = test_bit(IPOIB_FLAG_UMCAST, &priv->flags);
	if (nla_put_u16(skb, IFLA_IPOIB_UMCAST, val))
		goto nla_put_failure;

	return 0;

nla_put_failure:
	return -EMSGSIZE;
}
  • 功能: 将 IPOIB 接口的当前状态信息填充到 Netlink 消息中。
  • 步骤:
    • 获取设备的私有数据 priv。
    • 将 PKEY、模式(通过标志位测试)和多播状态添加到 Netlink 消息中。
    • 如果任何 nla_put_u16 调用失败,返回错误。

4. 处理接口变更

static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[])
  • 功能: 处理通过 Netlink 发送的接口配置变更。
  • 步骤:
    • 从 data 中提取模式 (IFLA_IPOIB_MODE) 并调用相应的设置函数。
    • 处理多播配置 (IFLA_IPOIB_UMCAST)。

5. 创建子接口

static int ipoib_new_child_link(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[])
  • 功能: 创建 IPOIB 的子接口(如 VLAN 或其他虚拟接口)。
  • 步骤:
    • 验证父接口是否存在且为 InfiniBand 类型。
    • 分配并初始化子接口的私有数据。
    • 设置函数指针和初始化接口。
    • 调用 ipoib_changelink 应用配置变更。

6. 获取接口信息的大小

static size_t ipoib_get_size(const struct net_device *dev)
{
	return nla_total_size(2) +	/* IFLA_IPOIB_PKEY   */
		nla_total_size(2) +	/* IFLA_IPOIB_MODE   */
		nla_total_size(2);	/* IFLA_IPOIB_UMCAST */
}
  • 功能: 计算填充接口信息所需的 Netlink 消息大小。

7. 定义 rtnl_link_ops 结构

static struct rtnl_link_ops ipoib_link_ops __read_mostly = {
	.kind		= "ipoib",
	.maxtype	= IFLA_IPOIB_MAX,
	.policy		= ipoib_policy,
	.priv_size	= sizeof(struct ipoib_dev_priv),
	.setup		= ipoib_setup_common,
	.newlink	= ipoib_new_child_link,
	.changelink	= ipoib_changelink,
	.get_size	= ipoib_get_size,
	.fill_info	= ipoib_fill_info,
};
  • 功能: 定义 IPOIB 接口的 Netlink 操作。
  • 字段:
    • kind: 接口类型名称。
    • maxtype: 支持的最大属性类型。
    • policy: 属性验证策略。
    • priv_size: 私有数据结构的大小。
    • setup, newlink, changelink, get_size, fill_info: 各种操作的回调函数。

8. 注册和注销 rtnl_link_ops

struct rtnl_link_ops *ipoib_get_link_ops(void)
{
	return &ipoib_link_ops;
}

int __init ipoib_netlink_init(void)
{
	return rtnl_link_register(&ipoib_link_ops);
}

void __exit ipoib_netlink_fini(void)
{
	rtnl_link_unregister(&ipoib_link_ops);
}
  • 功能: 提供接口注册和注销的函数。
  • ipoib_netlink_init: 在模块初始化时注册 ipoib_link_ops。
  • ipoib_netlink_fini: 在模块退出时注销 ipoib_link_ops。

9. 模块别名

MODULE_ALIAS_RTNL_LINK("ipoib");
  • 功能: 为 rtnl 链接操作设置别名,以便在用户空间通过名称引用该接口类型。

总结

该文件主要实现了 IPOIB 接口在 Netlink 层的操作,包括接口信息的填充、配置变更的处理以及子接口的创建。通过定义 rtnl_link_ops 结构并注册相应的回调函数,驱动能够响应来自用户空间的配置请求,并管理 IPOIB 接口的状态和属性。

以下是对ipoib_netlink.c文件内容的分析:

文件概述

该文件是InfiniBand上的IP over InfiniBand(ipoib)驱动的一部分,主要实现了与网络链路相关的netlink接口功能,用于ipoib设备的创建、修改以及信息获取等操作,使得用户空间可以通过netlink与ipoib驱动进行交互,动态地管理ipoib网络设备。

包含的头文件

  • linux/netdevice.h:包含网络设备相关的定义和函数声明,如struct net_device等。

  • linux/if_arp.h:定义了地址解析协议相关的常量,如ARPHRD_xxx,用于标识网络设备的类型,其中ARPHRD_INFINIBAND表示InfiniBand设备。

  • linux/module.h:用于模块相关操作,如模块加载、卸载等。

  • net/rtnetlink.h:包含路由netlink相关的定义和函数声明,用于处理网络设备的创建、修改等请求。

  • ipoib.h:ipoib驱动的头文件,定义了ipoib相关的数据结构、常量和函数声明等。

定义的常量和数据结构

  • ipoib_policy:定义了一个nla_policy数组,用于指定ipoib设备属性的类型和长度限制。例如,IFLA_IPOIB_PKEY、IFLA_IPOIB_MODE和IFLA_IPOIB_UMCAST分别对应于pkey、模式和umcast属性,它们的类型都是NLA_U16,即16位无符号整数。

  • struct rtnl_link_ops ipoib_link_ops:定义了一个rtnl_link_ops结构体实例,用于注册ipoib设备的netlink操作。其中包含多个成员函数指针,指向ipoib驱动中实现的相应功能函数,如newlink用于创建新的ipoib子设备,changelink用于修改ipoib设备的属性,get_size和fill_info用于获取和填充ipoib设备的信息等。

主要函数

  • ipoib_fill_info:用于填充ipoib设备的信息到一个sk_buff中,以便通过netlink发送给用户空间。它会获取ipoib设备的pkey、模式和umcast属性,并使用nla_put_u16函数将这些属性添加到sk_buff中。如果在添加属性时发生错误(如空间不足),则会跳转到nla_put_failure标签,返回-EMSGSIZE错误码。

  • ipoib_changelink:用于修改ipoib设备的属性。它会根据传入的data数组中的属性,分别处理模式和umcast属性的修改。对于模式属性,会根据传入的模式值调用ipoib_set_mode函数设置ipoib设备的模式;对于umcast属性,会调用ipoib_set_umcast函数设置umcast值。如果在设置过程中发生错误,会返回相应的错误码。

  • ipoib_new_child_link:用于创建新的ipoib子设备。它会首先检查传入的参数,获取父设备和子设备的pkey等信息。然后,会为子设备分配私有数据结构ipoib_dev_priv,并调用相关函数初始化子设备,如设置功能指针、初始化接口等。最后,会调用__ipoib_vlan_add函数将子设备添加到父设备中,并根据传入的data数组调用ipoib_changelink函数设置子设备的属性。如果在创建过程中发生错误,会释放分配的资源并返回错误码。

  • ipoib_get_size:用于计算ipoib设备信息的大小。它会根据ipoib设备的属性数量和类型,计算出需要的空间大小,以便在sk_buff中分配足够的空间来存储设备信息。

  • ipoib_get_link_ops:返回ipoib_link_ops结构体的指针,供其他部分的代码使用。

  • ipoib_netlink_init和ipoib_netlink_fini:分别用于初始化和清理ipoib的netlink接口。在ipoib_netlink_init中,会调用rtnl_link_register函数注册ipoib_link_ops;在ipoib_netlink_fini中,会调用rtnl_link_unregister函数注销ipoib_link_ops。

模块相关宏

  • MODULE_ALIAS_RTNL_LINK("ipoib"):为ipoib模块指定一个别名,使得当用户空间通过netlink请求创建ipoib设备时,内核能够找到并加载ipoib模块。

以下是对 drivers\infiniband\ulp\ipoib\ipoib_netlink.c 文件内容的分析:

1. 版权与许可声明部分

开头部分是版权声明以及软件许可相关的说明,指出该软件可以在两种许可证(GNU General Public License (GPL) Version 2 或者 OpenIB.org BSD 许可证)下选择使用,并详细列出了在 BSD 许可证下使用该软件需满足的条件,比如对源代码和二进制形式再分发时要保留版权声明、遵循相应的免责条款等。

2. 头文件包含部分

#include <linux/netdevice.h>
#include <linux/if_arp.h>      /* For ARPHRD_xxx */
#include <linux/module.h>
#include <net/rtnetlink.h>
#include "ipoib.h"

包含了一系列必要的头文件,像 <linux/netdevice.h> 用于处理网络设备相关的结构体和函数等;<linux/if_arp.h> 提供了与 ARP 相关的定义(此处用于获取 ARPHRD_xxx 相关内容);<linux/module.h> 常用于 Linux 内核模块相关的操作;<net/rtnetlink.h> 用于和网络链路相关的 Netlink 通信操作;"ipoib.h" 应该是和该 ipoib 模块自定义的一些结构体、函数声明等相关的头文件。

3. 网络属性策略定义部分

static const struct nla_policy ipoib_policy[IFLA_IPOIB_MAX + 1] = {
    [IFLA_IPOIB_PKEY]    = {.type = NLA_U16 },
    [IFLA_IPOIB_MODE]    = {.type = NLA_U16 },
    [IFLA_IPOIB_UMCAST] = {.type = NLA_U16 },
};

定义了一个名为 ipoib_policy 的网络属性(Netlink Attributes,NLA)策略数组,用于规定 IFLA_IPOIB_ 开头的几种网络属性(如 PKEY、MODE、UMCAST)的数据类型为无符号 16 位整数(NLA_U16),这在后续处理网络链路相关属性配置时会起到类型检查等作用。

4. ipoib_fill_info 函数

static int ipoib_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
    struct ipoib_dev_priv *priv = ipoib_priv(dev);
    u16 val;

    if (nla_put_u16(skb, IFLA_IPOIB_PKEY, priv->pkey))
        goto nla_put_failure;

    val = test_bit(IPOIB_FLAG_ADMIN_CM, &priv->flags);
    if (nla_put_u16(skb, IFLA_IPOIB_MODE, val))
        goto nla_put_failure;

    val = test_bit(IPOIB_FLAG_UMCAST, &priv->flags);
    if (nla_put_u16(skb, IFLA_IPOIB_UMCAST, val))
        goto nla_put_failure;

    return 0;

nla_put_failure:
    return -EMSGSIZE;
}

这个函数的作用是将 ipoib 相关的设备信息填充到一个 sk_buff 结构体中,主要是将设备的私有数据(通过 ipoib_priv 函数获取)里的 pkey、根据相关标志位确定的 MODE 和 UMCAST 信息,以无符号 16 位整数的形式添加到 sk_buff 中,如果添加过程中出现问题(比如空间不足等情况,通过 nla_put_xxx 函数返回非零值判断),则跳转到错误处理部分返回 -EMSGSIZE 表示消息尺寸相关错误,否则返回 0 表示填充成功。

5. ipoib_changelink 函数

#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
                            struct nlattr *data[],
                            struct netlink_ext_ack *extack)
#else
static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
                            struct nlattr *data[])
#endif
{
    struct ipoib_dev_priv *priv = ipoib_priv(dev);
    u16 mode, umcast;
    int ret = 0;

    if (data[IFLA_IPOIB_MODE]) {
        mode  = nla_get_u16(data[IFLA_IPOIB_MODE]);
        if (mode == IPOIB_MODE_DATAGRAM)
            ret = priv->fp.ipoib_set_mode(dev, "datagram\n");
        else if (mode == IPOIB_MODE_CONNECTED)
            ret = priv->fp.ipoib_set_mode(dev, "connected\n");
        else
            ret = -EINVAL;

        if (ret < 0)
            goto out_err;
    }

    if (data[IFLA_IPOIB_UMCAST]) {
        umcast = nla_get_u16(data[IFLA_IPOIB_UMCAST]);
        ipoib_set_umcast(dev, umcast);
    }

out_err:
    return ret;
}

这个函数用于处理网络链路属性更改操作。它首先从传入的网络属性数据中获取 IFLA_IPOIB_MODE 和 IFLA_IPOIB_UMCAST 相关的值(如果存在的话),对于 MODE,根据不同的值(DATAGRAM 或者 CONNECTED)调用相应的设备模式设置函数,若值不合法则返回错误;对于 UMCAST,则调用 ipoib_set_umcast 函数进行相应设置,最后返回操作的结果(成功或失败对应的返回值)。

6. ipoib_new_child_link 函数

#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
                                struct nlattr *tb[], struct nlattr *data[],
                                struct netlink_ext_ack *extack)
#elif defined(HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS)
static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
                                struct nlattr *tb[], struct nlattr *data[])
#else
static int ipoib_new_child_link(struct net_device *dev,
                                struct nlattr *tb[], struct nlattr *data[])
#endif
{
    // 一系列变量定义和初始化操作
    //...

    if (!tb[IFLA_LINK])
        return -EINVAL;
#ifdef HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS
    pdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
#else
    pdev = __dev_get_by_index(dev_net(dev), nla_get_u32(tb[IFLA_LINK]));
#endif

    if (!pdev || pdev->type!= ARPHRD_INFINIBAND)
        return -ENODEV;

    // 后续一系列设备初始化、属性设置、添加 VLAN 等操作
    //...

    return 0;
}

这个函数用于创建一个新的子链路(ipoib 相关)。首先进行一些参数合法性检查,比如通过 IFLA_LINK 属性获取对应的父设备,判断父设备是否存在以及是否为 Infiniband 类型设备等。然后进行子设备相关的内存分配、函数指针设置、设备初始化、VLAN 添加等操作,过程中如果出现错误会进行相应的错误处理(如释放已分配内存等),最后返回操作结果(成功返回 0,失败返回对应错误码)。

7. ipoib_get_size 函数

static size_t ipoib_get_size(const struct net_device *dev)
{
    return nla_total_size(2) +    /* IFLA_IPOIB_PKEY   */
           nla_total_size(2) +    /* IFLA_IPOIB_MODE   */
           nla_total_size(2);    /* IFLA_IPOIB_UMCAST */
}

该函数用于计算 ipoib 相关网络设备信息所占用的空间大小,通过分别计算 IFLA_IPOIB_PKEY、IFLA_IPOIB_MODE 和 IFLA_IPOIB_UMCAST 这几个属性各自占用的空间(都调用 nla_total_size 来计算无符号 16 位整数类型属性占用空间)并求和返回总的空间大小。

8. ipoib_link_ops 结构体与相关函数

static struct rtnl_link_ops ipoib_link_ops __read_mostly = {
   .kind        = "ipoib",
   .maxtype     = IFLA_IPOIB_MAX,
   .policy      = ipoib_policy,
   .priv_size   = sizeof(struct ipoib_dev_priv),
   .setup       = ipoib_setup_common,
   .newlink     = ipoib_new_child_link,
   .changelink  = ipoib_changelink,
   .get_size    = ipoib_get_size,
   .fill_info   = ipoib_fill_info,
};

struct rtnl_link_ops *ipoib_get_link_ops(void)
{
    return &ipoib_link_ops;
}

int __init ipoib_netlink_init(void)
{
    return rtnl_link_register(&ipoib_link_ops);
}

void __exit ipoib_netlink_fini(void)
{
    rtnl_link_unregister(&ipoib_link_ops);
}

MODULE_ALIAS_RTNL_LINK("ipoib");
  • 定义了 ipoib_link_ops 结构体,它是 rtnl_link_ops 类型,用于描述 ipoib 网络链路相关的操作集合,包括链路类型("ipoib")、最大属性类型(IFLA_IPOIB_MAX)、属性策略(ipoib_policy)、私有数据大小(sizeof(struct ipoib_dev_priv))以及前面介绍的各种链路操作函数(如创建新链路、更改链路属性、获取链路信息大小、填充链路信息等)。
  • ipoib_get_link_ops 函数用于获取 ipoib_link_ops 结构体指针,方便外部模块调用相关链路操作集合。
  • ipoib_netlink_init 函数在模块初始化时被调用(通过 __init 标识),用于将 ipoib_link_ops 注册到内核的网络链路管理机制中,使得内核可以识别并使用 ipoib 相关的链路操作。
  • ipoib_netlink_fini 函数在模块退出时被调用(通过 __exit 标识),用于将之前注册的 ipoib_link_ops 从内核网络链路管理机制中注销,进行相应的清理工作。
  • MODULE_ALIAS_RTNL_LINK("ipoib") 则是给该模块添加一个别名,方便在相关链路操作场景下通过别名来引用该模块。

总体而言,这个文件主要实现了 ipoib 驱动在 Linux 内核中与网络链路相关的 Netlink 操作相关的功能,涵盖了链路信息填充、链路属性更改、子链路创建以及相关的初始化和注销操作等内容,是 ipoib 驱动在内核网络链路层面交互的重要实现部分。

更多推荐