// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Generic netlink support functions to configure an SMC-R PNET table
*
* Copyright IBM Corp. 2016
*
* Author(s): Thomas Richter <tmricht@linux.vnet.ibm.com>
*/
#include <linux/module.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/if.h>
#include <uapi/linux/smc.h>
#include <rdma/ib_verbs.h>
#include <net/netns/generic.h>
#include "smc_netns.h"
#include "smc_pnet.h"
#include "smc_ib.h"
#include "smc_ism.h"
#include "smc_core.h"
#define SMC_ASCII_BLANK 32
static struct net_device *pnet_find_base_ndev(struct net_device *ndev);
static struct nla_policy smc_pnet_policy[SMC_PNETID_MAX + 1] = {
[SMC_PNETID_NAME] = {
.type = NLA_NUL_STRING,
.len = SMC_MAX_PNETID_LEN
},
[SMC_PNETID_ETHNAME] = {
.type = NLA_NUL_STRING,
.len = IFNAMSIZ - 1
},
[SMC_PNETID_IBNAME] = {
.type = NLA_NUL_STRING,
.len = IB_DEVICE_NAME_MAX - 1
},
[SMC_PNETID_IBPORT] = { .type = NLA_U8 }
};
static struct genl_family smc_pnet_nl_family;
/**
* struct smc_user_pnetentry - pnet identifier name entry for/from user
* @list: List node.
* @pnet_name: Pnet identifier name
* @ndev: pointer to network device.
* @smcibdev: Pointer to IB device.
* @ib_port: Port of IB device.
* @smcd_dev: Pointer to smcd device.
*/
struct smc_user_pnetentry {
struct list_head list;
char pnet_name[SMC_MAX_PNETID_LEN + 1];
struct net_device *ndev;
struct smc_ib_device *smcibdev;
u8 ib_port;
struct smcd_dev *smcd_dev;
};
/* pnet entry stored in pnet table */
struct smc_pnetentry {
struct list_head list;
char pnet_name[SMC_MAX_PNETID_LEN + 1];
struct net_device *ndev;
};
/* Check if two given pnetids match */
static bool smc_pnet_match(u8 *pnetid1, u8 *pnetid2)
{
int i;
for (i = 0; i < SMC_MAX_PNETID_LEN; i++) {
if ((pnetid1[i] == 0 || pnetid1[i] == SMC_ASCII_BLANK) &&
(pnetid2[i] == 0 || pnetid2[i] == SMC_ASCII_BLANK))
break;
if (pnetid1[i] != pnetid2[i])
return false;
}
return true;
}
/* Remove a pnetid from the pnet table.
*/
static int smc_pnet_remove_by_pnetid(struct net *net, char *pnet_name)
{
struct smc_pnetentry *pnetelem, *tmp_pe;
struct smc_pnettable *pnettable;
struct smc_ib_device *ibdev;
struct smcd_dev *smcd_dev;
struct smc_net *sn;
int rc = -ENOENT;
int ibport;
/* get pnettable for namespace */
sn = net_generic(net, smc_net_id);
pnettable = &sn->pnettable;
/* remove netdevices */
write_lock(&pnettable->lock);
list_for_each_entry_safe(pnetelem, tmp_pe, &pnettable->pnetlist,
list) {
if (!pnet_name ||
smc_pnet_match(pnetelem->pnet_name, pnet_name)) {
list_del(&pnetelem->list);
dev_put(pnetelem->ndev);
kfree(pnetelem);
rc = 0;
}
}
write_unlock(&pnettable->lock);
/* if this is not the initial namespace, stop here */
if (net != &init_net)
return rc;
/* remove ib devices */
spin_lock(&smc_ib_devices.lock);
list_for_each_entry(ibdev, &smc_ib_devices.list, list) {
for (ibport = 0; ibport < SMC_MAX_PORTS; ibport++) {
if (ibdev->pnetid_by_user[ibport] &&
(!pnet_name ||
smc_pnet_match(pnet_name,
ibdev->pnetid[ibport]))) {
memset(ibdev->pnetid[ibport], 0,
SMC_MAX_PNETID_LEN);
ibdev->pnetid_by_user[ibport] = false;
rc = 0;
}
}
}
spin_unlock(&smc_ib_devices.lock);
/* remove smcd devices */
spin_lock(&smcd_dev_list.lock);
list_for_each_entry(smcd_dev, &smcd_dev_list.list, list) {
if (smcd_dev->pnetid_by_user &&
(!pnet_name ||
smc_pnet_match(pnet_name, smcd_dev->pnetid))) {
memset(smcd_dev->pnetid, 0, SMC_MAX_PNETID_LEN);
smcd_dev->pnetid_by_user = false;
rc = 0;
}
}
spin_unlock(&smcd_dev_list.lock);
return rc;
}
/* Remove a pnet entry mentioning a given network device from the pnet table.
*/
static int smc_pnet_remove_by_ndev(struct net_device *ndev)
{
struct smc_pnetentry *pnetelem, *tmp_pe;
struct smc_pnettable *pnettable;
struct net *net = dev_net(ndev);
struct smc_net *sn;
int rc = -ENOENT;
/* get pnettable for namespace */
sn = net_generic(net, smc_net_id);
pnettable = &sn