blob: 44962f221c352fdd17978444728879dfd722b9a6 [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017, Microsoft Corporation.
* Copyright (C) 2018, LG Electronics.
* Copyright (c) 2025 Stefan Metzmacher
*/
#include "internal.h"
static u8 smbdirect_ib_device_rdma_capable_node_type(struct ib_device *ib_dev)
{
if (!smbdirect_frwr_is_supported(&ib_dev->attrs))
return RDMA_NODE_UNSPECIFIED;
switch (ib_dev->node_type) {
case RDMA_NODE_IB_CA: /* Infiniband, RoCE v1 and v2 */
case RDMA_NODE_RNIC: /* iWarp */
return ib_dev->node_type;
}
return RDMA_NODE_UNSPECIFIED;
}
static int smbdirect_ib_client_add(struct ib_device *ib_dev)
{
u8 node_type = smbdirect_ib_device_rdma_capable_node_type(ib_dev);
struct smbdirect_device *sdev;
const char *node_str;
const char *action;
u32 pidx;
switch (node_type) {
case RDMA_NODE_IB_CA:
node_str = "IB_CA";
action = "added";
break;
case RDMA_NODE_RNIC:
node_str = "RNIC";
action = "added";
break;
case RDMA_NODE_UNSPECIFIED:
node_str = "UNSPECIFIED";
action = "ignored";
break;
default:
node_str = "UNKNOWN";
action = "ignored";
node_type = RDMA_NODE_UNSPECIFIED;
break;
}
pr_info("ib_dev[%.*s]: %s: %s %s=%u %s=0x%llx %s=0x%llx %s=0x%llx\n",
IB_DEVICE_NAME_MAX,
ib_dev->name,
action,
node_str,
"max_fast_reg_page_list_len",
ib_dev->attrs.max_fast_reg_page_list_len,
"device_cap_flags",
ib_dev->attrs.device_cap_flags,
"kernel_cap_flags",
ib_dev->attrs.kernel_cap_flags,
"page_size_cap",
ib_dev->attrs.page_size_cap);
if (node_type == RDMA_NODE_UNSPECIFIED)
return 0;
pr_info("ib_dev[%.*s]: %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u %s=%u\n",
IB_DEVICE_NAME_MAX,
ib_dev->name,
"num_ports",
rdma_end_port(ib_dev),
"max_qp_rd_atom",
ib_dev->attrs.max_qp_rd_atom,
"max_qp_init_rd_atom",
ib_dev->attrs.max_qp_init_rd_atom,
"max_sgl_rd",
ib_dev->attrs.max_sgl_rd,
"max_sge_rd",
ib_dev->attrs.max_sge_rd,
"max_cqe",
ib_dev->attrs.max_cqe,
"max_qp_wr",
ib_dev->attrs.max_qp_wr,
"max_send_sge",
ib_dev->attrs.max_send_sge,
"max_recv_sge",
ib_dev->attrs.max_recv_sge);
rdma_for_each_port(ib_dev, pidx) {
const struct ib_port_immutable *ib_pi =
ib_port_immutable_read(ib_dev, pidx);
u32 core_cap_flags = ib_pi ? ib_pi->core_cap_flags : 0;
pr_info("ib_dev[%.*s]PORT[%u]: %s=%u %s=%u %s=%u %s=%u %s=%u %s=0x%x\n",
IB_DEVICE_NAME_MAX,
ib_dev->name,
pidx,
"iwarp",
rdma_protocol_iwarp(ib_dev, pidx),
"ib",
rdma_protocol_ib(ib_dev, pidx),
"roce",
rdma_protocol_roce(ib_dev, pidx),
"v1",
rdma_protocol_roce_eth_encap(ib_dev, pidx),
"v2",
rdma_protocol_roce_udp_encap(ib_dev, pidx),
"core_cap_flags",
core_cap_flags);
}
sdev = kzalloc_obj(*sdev);
if (!sdev)
return -ENOMEM;
sdev->ib_dev = ib_dev;
snprintf(sdev->ib_name, ARRAY_SIZE(sdev->ib_name), "%.*s",
IB_DEVICE_NAME_MAX, ib_dev->name);
write_lock(&smbdirect_globals.devices.lock);
list_add(&sdev->list, &smbdirect_globals.devices.list);
write_unlock(&smbdirect_globals.devices.lock);
return 0;
}
static void smbdirect_ib_client_remove(struct ib_device *ib_dev, void *client_data)
{
struct smbdirect_device *sdev, *tmp;
write_lock(&smbdirect_globals.devices.lock);
list_for_each_entry_safe(sdev, tmp, &smbdirect_globals.devices.list, list) {
if (sdev->ib_dev == ib_dev) {
list_del(&sdev->list);
pr_info("ib_dev[%.*s] removed\n",
IB_DEVICE_NAME_MAX, sdev->ib_name);
kfree(sdev);
break;
}
}
write_unlock(&smbdirect_globals.devices.lock);
}
static void smbdirect_ib_client_rename(struct ib_device *ib_dev, void *client_data)
{
struct smbdirect_device *sdev;
write_lock(&smbdirect_globals.devices.lock);
list_for_each_entry(sdev, &smbdirect_globals.devices.list, list) {
if (sdev->ib_dev == ib_dev) {
pr_info("ib_dev[%.*s] renamed to [%.*s]\n",
IB_DEVICE_NAME_MAX, sdev->ib_name,
IB_DEVICE_NAME_MAX, ib_dev->name);
snprintf(sdev->ib_name, ARRAY_SIZE(sdev->ib_name), "%.*s",
IB_DEVICE_NAME_MAX, ib_dev->name);
break;
}
}
write_unlock(&smbdirect_globals.devices.lock);
}
static struct ib_client smbdirect_ib_client = {
.name = "smbdirect_ib_client",
.add = smbdirect_ib_client_add,
.remove = smbdirect_ib_client_remove,
.rename = smbdirect_ib_client_rename,
};
static u8 smbdirect_netdev_find_rdma_capable_node_type(struct net_device *netdev)
{
struct smbdirect_device *sdev;
u8 node_type = RDMA_NODE_UNSPECIFIED;
read_lock(&smbdirect_globals.devices.lock);
list_for_each_entry(sdev, &smbdirect_globals.devices.list, list) {
u32 pi;
rdma_for_each_port(sdev->ib_dev, pi) {
struct net_device *ndev;
ndev = ib_device_get_netdev(sdev->ib_dev, pi);
if (!ndev)
continue;
if (ndev == netdev) {
dev_put(ndev);
node_type = sdev->ib_dev->node_type;
goto out;
}
dev_put(ndev);
}
}
out:
read_unlock(&smbdirect_globals.devices.lock);
if (node_type == RDMA_NODE_UNSPECIFIED) {
struct ib_device *ibdev;
ibdev = ib_device_get_by_netdev(netdev, RDMA_DRIVER_UNKNOWN);
if (ibdev) {
node_type = smbdirect_ib_device_rdma_capable_node_type(ibdev);
ib_device_put(ibdev);
}
}
return node_type;
}
/*
* Returns RDMA_NODE_UNSPECIFIED when the netdev has
* no support for smbdirect capable rdma.
*
* Otherwise RDMA_NODE_RNIC is returned for iwarp devices
* and RDMA_NODE_IB_CA or Infiniband and RoCE (v1 and v2)
*/
u8 smbdirect_netdev_rdma_capable_node_type(struct net_device *netdev)
{
struct net_device *lower_dev;
struct list_head *iter;
u8 node_type = RDMA_NODE_UNSPECIFIED;
node_type = smbdirect_netdev_find_rdma_capable_node_type(netdev);
if (node_type != RDMA_NODE_UNSPECIFIED)
return node_type;
/* check if netdev is bridge or VLAN */
if (netif_is_bridge_master(netdev) || netdev->priv_flags & IFF_802_1Q_VLAN)
netdev_for_each_lower_dev(netdev, lower_dev, iter) {
node_type = smbdirect_netdev_find_rdma_capable_node_type(lower_dev);
if (node_type != RDMA_NODE_UNSPECIFIED)
return node_type;
}
/* check if netdev is IPoIB safely without layer violation */
if (netdev->type == ARPHRD_INFINIBAND)
return RDMA_NODE_IB_CA;
return RDMA_NODE_UNSPECIFIED;
}
__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_netdev_rdma_capable_node_type);
__init int smbdirect_devices_init(void)
{
int ret;
rwlock_init(&smbdirect_globals.devices.lock);
INIT_LIST_HEAD(&smbdirect_globals.devices.list);
ret = ib_register_client(&smbdirect_ib_client);
if (ret) {
pr_crit("failed to ib_register_client: %d %1pe\n",
ret, SMBDIRECT_DEBUG_ERR_PTR(ret));
return ret;
}
return 0;
}
__exit void smbdirect_devices_exit(void)
{
struct smbdirect_device *sdev, *tmp;
/*
* On exist we just cleanup so that
* smbdirect_ib_client_remove() won't
* print removals of devices.
*/
write_lock(&smbdirect_globals.devices.lock);
list_for_each_entry_safe(sdev, tmp, &smbdirect_globals.devices.list, list) {
list_del(&sdev->list);
kfree(sdev);
}
write_unlock(&smbdirect_globals.devices.lock);
ib_unregister_client(&smbdirect_ib_client);
}