Linux RDMA and InfiniBand development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yanze Zhang" <zhang.yanze@zte.com.cn>
Cc: linux-rdma@vger.kernel.org
Subject: Re: [PATCH 7/7] RDMA/zrdma: Implement device and port query operations
Date: Sat, 24 Oct 2026 07:45:16 +0000	[thread overview]
Message-ID: <sashiko-outbox-183592@kernel.org> (raw)
In-Reply-To: <20261024071539.2182864-8-zhang.yanze@zte.com.cn>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] RDMA/zrdma: NULL pointer dereference of udata in zxdh_query_device()
- [Medium] RDMA/zrdma: missing max_send_sge and max_recv_sge in zxdh_query_device()
- [Medium] RDMA/zrdma: broken rtnl_trylock() fallback in zxdh_get_drvinfo()
- [Medium] RDMA/zrdma: flawed speed query and caching in zxdh_get_eth_netdev_speed()
- [Medium] RDMA/zrdma: zxdh_get_dev_fw_str() overwrites N/A fallback on parse failure
- [Medium] RDMA/zrdma: memory leak on probe error paths due to late ib_set_device_ops()
- [Low] RDMA/zrdma: dead zxdh_query_gid_roce() callback and misplaced build fixes
--

commit 5562e1f8054bd9cdd1fa5be99a78d65f32fb0baf
Author: Yanze Zhang <zhang.yanze@zte.com.cn>

RDMA/zrdma: Implement device and port query operations

This commit implements RDMA device and port query callbacks, including device
attributes, port attributes, GID/PKey queries, link layer reporting, firmware
version extraction, and device registration setup.

> diff --git a/drivers/infiniband/hw/zrdma/zrdma_main.h b/drivers/infiniband/hw/zrdma/zrdma_main.h
> index 060d6f627b0dd..62e52e5a23ee3 100644
> --- a/drivers/infiniband/hw/zrdma/zrdma_main.h
> +++ b/drivers/infiniband/hw/zrdma/zrdma_main.h
> @@ -208,7 +208,7 @@ static inline void zxdh_free_rsrc(struct zxdh_pci_f *rf,
>  	spin_unlock_irqrestore(&rf->rsrc_lock, flags);
>  }
>  
> -int zxdh_get_del_rdma_name(const char *sbdf, char *name);
> +int zxdh_get_del_rdma_name(const char *sbdf, char *name, size_t name_len);
>  int zxdh_ctrl_init_hw(struct zxdh_pci_f *rf);
>  int zxdh_ib_register_device(struct zxdh_device *zdev);
>  void zxdh_del_handler(struct zxdh_handler *hdl);
> diff --git a/drivers/infiniband/hw/zrdma/zrdma_verbs.c b/drivers/infiniband/hw/zrdma/zrdma_verbs.c
> index 54b016ea8f5de..a16e00e785353 100644
> --- a/drivers/infiniband/hw/zrdma/zrdma_verbs.c
> +++ b/drivers/infiniband/hw/zrdma/zrdma_verbs.c

[ ... ]

> @@ -157,7 +159,7 @@ static int zxdh_alloc_ucontext(struct ib_ucontext *uctx, struct ib_udata *udata)
>  	ucontext->zdev = zdev;
>  	ucontext->abi_ver = req.userspace_ver;
>  
> -	uresp.srq_db_bar_off = SRQ_DB_OFFSET;
> +	uresp.srq_db_bar_off = SRQ_DB_BAR_OFF & ZXDH_PAGE_OFFSET_NUM;
>  
>  	ucontext->sq_db_mmap_entry =
>  		zxdh_user_mmap_entry_insert(ucontext, SQ_DB_BAR_OFF,
> @@ -332,29 +334,327 @@ static int zxdh_mmap(struct ib_ucontext *context, struct vm_area_struct *vma)
>  	return ret;
>  }
>  
> -static void zxdh_init_roce_device(struct zxdh_device *zdev)
> +/* Fetch the driver info for @zdev->netdev, or for its first lower device
> + * when it is a bond. adj_list.lower is rtnl_lock protected, but the core
> + * may call this from a netdev notifier already holding rtnl_lock.
> + */
> +static bool zxdh_get_drvinfo(struct zxdh_device *zdev,
> +			     struct ethtool_drvinfo *info)
>  {
> -	zdev->ibdev.node_type = RDMA_NODE_IB_CA;
> -	addrconf_addr_eui48((u8 *)&zdev->ibdev.node_guid,
> -			    zdev->netdev->dev_addr);
> +	struct net_device *slave = NULL, *dev;
> +	struct list_head *iter;
> +	bool locked = false;
> +	bool ret = false;
> +
> +	memset(info, 0, sizeof(*info));
> +
> +	if ((zdev->netdev->priv_flags & IFF_BONDING) && rtnl_trylock()) {
> +		netdev_for_each_lower_dev(zdev->netdev, slave, iter)
> +			break;
> +		locked = true;
> +	}
> +
> +	dev = slave ? slave : zdev->netdev;
> +	if (dev->ethtool_ops && dev->ethtool_ops->get_drvinfo) {
> +		dev->ethtool_ops->get_drvinfo(dev, info);
> +		ret = true;
> +	}
> +
> +	if (locked)
> +		rtnl_unlock();
> +
> +	return ret;
>  }

[Severity: Medium]
What happens in zxdh_get_drvinfo() when zdev->netdev is a bonding device and
rtnl_lock is already held by the current thread or contended by another
thread?

Because rtnl_mutex is not recursive, rtnl_trylock() returns 0 even when the
caller already holds rtnl_lock. In that case, the lower device lookup is
skipped and slave remains NULL, so dev falls back to zdev->netdev (the
bonding master) instead of the lower hardware device.

Calling dev->ethtool_ops->get_drvinfo(dev, info) on the bonding master invokes
bond_ethtool_get_drvinfo(), which fills info->fw_version with BOND_ABI_VERSION
("2") and causes zxdh_get_drvinfo() to return true. When extract_version()
parses that string, it fails and outputs an empty string, resulting in
zxdh_query_device() setting props->fw_ver = 0 and zxdh_get_dev_fw_str()
overwriting "N/A" with "".

In addition, when zdev->netdev is not a bond or when rtnl_trylock() fails,
dev->ethtool_ops->get_drvinfo() is invoked without holding rtnl_lock or the
netdev ops lock.

>  
> -static int zxdh_init_rdma_device(struct zxdh_device *zdev)
> +static void extract_version(const char *input, char *output)
>  {
> +	const char *v_start, *p;
> +	int dots = 0;
> +
> +	if (!output || !input)
> +		goto fail;
> +
> +	if (strstr(input, " (")) {
> +		v_start = input;
> +		for (p = v_start; *p && *p != ' '; p++) {
> +			if (*p == '.' && ++dots == 4)
> +				break;
> +			*output++ = *p;
> +		}
> +	} else {
> +		/* Format: "E310-V25.30.103.01" - find 'V' after last '-' */
> +		const char *last_dash = strrchr(input, '-');
> +
> +		if (!last_dash)
> +			goto fail;
> +		v_start = strstr(last_dash, "V");
> +		if (!v_start)
> +			goto fail;
> +		v_start++;  /* skip 'V' */
> +
> +		for (p = v_start; *p && dots < 4; p++) {
> +			if (*p == '.' && ++dots == 4)
> +				break;
> +			*output++ = *p;
> +		}
> +	}
> +
> +fail:
> +	if (output)
> +		*output = '\0';
> +}
> +
> +static int zxdh_query_device(struct ib_device *ibdev,
> +			     struct ib_device_attr *props,
> +			     struct ib_udata *udata)
> +{
> +	struct zxdh_device *zdev = to_zdev(ibdev);
>  	struct pci_dev *pcidev = zdev->rf->pcidev;
> +	int major, sub_major, minor, sub_minor;
> +	struct ethtool_drvinfo info;
> +	char extracted_version[32];
> +
> +	if (zxdh_get_drvinfo(zdev, &info)) {
> +		extract_version(info.fw_version, extracted_version);
> +		if (sscanf(extracted_version, "%d.%d.%d.%d", &major,
> +			   &sub_major, &minor, &sub_minor) != 4) {
> +			major = 0;
> +			sub_major = 0;
> +			minor = 0;
> +			sub_minor = 0;
> +		}
> +	} else {
> +		major = sub_major = minor = sub_minor = 0;
> +	}
>  
> -	if (zdev->roce_mode)
> -		zxdh_init_roce_device(zdev);
> +	if (udata->inlen || udata->outlen)
> +		return -EINVAL;

[Severity: High]
Won't dereferencing udata without a NULL check in zxdh_query_device() cause a
NULL pointer dereference during device registration?

When zxdh_ib_register_device() calls ib_register_device(), setup_device() in
drivers/infiniband/core/device.c queries the device attributes with udata set
to NULL:

drivers/infiniband/core/device.c:setup_device() {
	...
	memset(&device->attrs, 0, sizeof(device->attrs));
	ret = device->ops.query_device(device, &device->attrs, NULL);
	...
}

Should this check for a non-NULL udata pointer first, for example
if (udata && (udata->inlen || udata->outlen))?

> +
> +	memset(props, 0, sizeof(*props));
> +	addrconf_addr_eui48((u8 *)&props->sys_image_guid,
> +			    zdev->netdev->dev_addr);
> +	props->fw_ver = ((u64)major << 48 | (u64)sub_major << 32 |
> +			 (u64)minor << 16 | sub_minor);
> +	props->device_cap_flags =
> +		IB_DEVICE_MEM_WINDOW | IB_DEVICE_MEM_MGT_EXTENSIONS |
> +		IB_DEVICE_BAD_QKEY_CNTR | IB_DEVICE_SYS_IMAGE_GUID |
> +		IB_DEVICE_RC_RNR_NAK_GEN | IB_DEVICE_N_NOTIFY_CQ;
> +	props->vendor_id = pcidev->vendor;
> +	props->vendor_part_id = pcidev->device;
> +	props->hw_ver = pcidev->revision;
> +	props->page_size_cap = SZ_4K | SZ_2M | SZ_1G;
> +	props->max_mr_size = MAX_MR_SIZE;
> +	props->max_qp = MAX_QP;
> +	props->max_qp_wr = MAX_QP_WR;
> +	props->max_cq = MAX_CQ;
> +	props->max_cqe = MAX_CQE;
> +	props->max_mr = MAX_MR;
> +	props->max_mw = MAX_MW;
> +	props->max_pd = MAX_PD;
> +	props->max_sge_rd = MAX_SGE_RD;

[Severity: Medium]
Should zxdh_query_device() also populate props->max_send_sge and
props->max_recv_sge here?

Because props is zeroed with memset() and neither field is assigned,
device->attrs.max_send_sge and device->attrs.max_recv_sge remain 0 after
setup_device() runs.

When userspace queries the device through ib_uverbs_query_device(),
copy_query_dev_fields() in drivers/infiniband/core/uverbs_cmd.c computes:

	resp->max_sge = min(attr->max_send_sge, attr->max_recv_sge);

which reports max_sge as 0 to userspace and leaves kernel ULPs seeing 0
supported SGEs in device->attrs.

[ ... ]

> +static u32 zxdh_get_eth_netdev_speed(struct zxdh_device *zdev, u32 port_num)
> +{
> +	struct ethtool_link_ksettings lksettings;
> +	u32 netdev_speed = (u32)SPEED_UNKNOWN;
> +	struct zxdh_pci_f *rf = zdev->rf;
> +	int rc;
> +
> +	if (rdma_port_get_link_layer(&zdev->ibdev, port_num) !=
> +	    IB_LINK_LAYER_ETHERNET)
> +		return -EINVAL;
> +
> +	if (zdev->netdev_speed != (u32)SPEED_UNKNOWN)
> +		return zdev->netdev_speed;
> +
> +	if (rf->drv_np_cap == ZXDH_RDMA_COMMON_FUNC_CAP &&
> +	    rf->common_func_num_max > ZXDH_FUNC_NETDEV_SPEED_GET) {
> +		rc = rf->gen_ops.zxdh_common_func(zdev->zxdh_adev->parent,
> +						  &netdev_speed,
> +						  ZXDH_FUNC_NETDEV_SPEED_GET);
> +
> +		if (rc || netdev_speed == (u32)SPEED_UNKNOWN)
> +			netdev_speed = SPEED_1000;
> +		else
> +			zdev->netdev_speed = netdev_speed;
> +	} else {
> +		if (!rtnl_trylock())
> +			return SPEED_1000;
> +
> +		rc = __ethtool_get_link_ksettings(zdev->netdev, &lksettings);
> +		rtnl_unlock();
> +
> +		if (!rc &&
> +		    lksettings.base.speed != (u32)SPEED_UNKNOWN) {
> +			netdev_speed = lksettings.base.speed;
> +			zdev->netdev_speed = netdev_speed;
> +		} else {
> +			netdev_speed = SPEED_1000;
> +		}
> +	}
> +
> +	return netdev_speed;
> +}
> +
> +static int zxdh_get_eth_speed(struct ib_device *dev, struct net_device *netdev,
> +			      u32 port_num, u16 *speed, u8 *width)
> +{
> +	struct zxdh_device *zdev = to_zdev(dev);
> +	u32 netdev_speed;
> +
> +	if (zdev)
> +		netdev_speed = zxdh_get_eth_netdev_speed(zdev, port_num);
>  	else
> -		return -EPFNOSUPPORT;
> +		netdev_speed = SPEED_1000;
> +
> +	if (netdev_speed <= SPEED_1000) {
> +		*width = IB_WIDTH_1X;
> +		*speed = IB_SPEED_SDR;
> +	} else if (netdev_speed <= SPEED_10000) {
> +		*width = IB_WIDTH_1X;
> +		*speed = IB_SPEED_FDR10;
> +	} else if (netdev_speed <= SPEED_20000) {
> +		*width = IB_WIDTH_4X;
> +		*speed = IB_SPEED_DDR;
> +	} else if (netdev_speed <= SPEED_25000) {
> +		*width = IB_WIDTH_1X;
> +		*speed = IB_SPEED_EDR;
> +	} else if (netdev_speed <= SPEED_40000) {
> +		*width = IB_WIDTH_4X;
> +		*speed = IB_SPEED_FDR10;
> +	} else if (netdev_speed <= SPEED_100000) {
> +		*width = IB_WIDTH_4X;
> +		*speed = IB_SPEED_EDR;
> +	} else {
> +		*width = IB_WIDTH_4X;
> +		*speed = IB_SPEED_HDR;
> +	}
>  
> -	zdev->ibdev.phys_port_cnt = 1;
> -	zdev->ibdev.num_comp_vectors = zdev->rf->ceqs_count;
> -	zdev->ibdev.dev.parent = &pcidev->dev;
> +	return 0;
> +}

[Severity: Medium]
Can zxdh_get_eth_netdev_speed() and zxdh_get_eth_speed() report stale or
bogus link speeds in zxdh_query_port()?

First, once zxdh_get_eth_netdev_speed() writes a speed into
zdev->netdev_speed, every subsequent call returns the cached
zdev->netdev_speed immediately. Nothing in the driver ever invalidates or
updates zdev->netdev_speed when the link speed changes, and concurrent reads
and writes to zdev->netdev_speed are unsynchronized.

Second, when querying via __ethtool_get_link_ksettings(), if rtnl_trylock()
fails because rtnl_mutex is held or contended while zdev->netdev_speed is
still SPEED_UNKNOWN, zxdh_get_eth_netdev_speed() returns a fabricated
SPEED_1000 (which maps to IB_WIDTH_1X and IB_SPEED_SDR).

Third, zxdh_get_eth_netdev_speed() returns u32, so returning -EINVAL converts
to 0xffffffea, which zxdh_get_eth_speed() treats as an unsigned speed greater
than SPEED_100000 and maps to IB_WIDTH_4X and IB_SPEED_HDR. Could
ib_get_eth_speed() be used instead, or the speed queried without permanent
caching and signed-to-unsigned conversion?

[ ... ]

> +static int zxdh_query_gid_roce(struct ib_device *ibdev, u32 port, int index,
> +			       union ib_gid *gid)
> +{
> +	int ret;
> +
> +	ret = rdma_query_gid(ibdev, port, index, gid);
> +	if (ret == -EAGAIN) {
> +		memcpy(gid, &zgid, sizeof(*gid));
> +		return 0;
> +	}
> +
> +	return ret;
> +}

[Severity: Low]
Is zxdh_query_gid_roce() ever called by the RDMA core?

For RoCE ports, the RDMA core manages the GID cache via roce_gid_mgmt, and
ib_cache_update() in drivers/infiniband/core/cache.c only calls
device->ops.query_gid via config_non_roce_gid_cache() when
!rdma_protocol_roce(device, port). Even if invoked, rdma_query_gid() only
returns 0, -EINVAL, or -ENOENT, so the ret == -EAGAIN branch cannot be
reached.

Also, the two changes earlier in this patch to the zxdh_get_del_rdma_name()
prototype in drivers/infiniband/hw/zrdma/zrdma_main.h and uresp.srq_db_bar_off
in zxdh_alloc_ucontext() appear to fix build errors introduced in earlier
commits in the series ("RDMA/zrdma: Add device probe and remove logic" and
"RDMA/zrdma: Implement context, PD, and mmap operations"). Should those fixes
be folded into the earlier commits to preserve bisectability?

[ ... ]

> +static void zxdh_get_dev_fw_str(struct ib_device *dev, char *str)
> +{
> +	struct zxdh_device *zdev = to_zdev(dev);
> +	struct ethtool_drvinfo info;
> +	char extracted_version[32];
> +
> +	strscpy(str, "N/A", IB_FW_VERSION_NAME_MAX);
> +
> +	if (!zxdh_get_drvinfo(zdev, &info))
> +		return;
> +
> +	extract_version(info.fw_version, extracted_version);
> +	strscpy(str, extracted_version, IB_FW_VERSION_NAME_MAX);
> +}

[Severity: Medium]
If extract_version() fails to match the version format in info.fw_version (for
example, when info.fw_version is empty or does not contain " (" or "-...V"),
it jumps to fail and sets extracted_version[0] = '\0'. Won't the unconditional
strscpy(str, extracted_version, IB_FW_VERSION_NAME_MAX) in
zxdh_get_dev_fw_str() overwrite the "N/A" fallback with an empty string?

Also, in zxdh_query_device(), major, sub_major, minor, and sub_minor are
parsed as signed int values with "%d.%d.%d.%d" and combined via:

	props->fw_ver = ((u64)major << 48 | (u64)sub_major << 32 |
			 (u64)minor << 16 | sub_minor);

Should extracted_version[0] be checked before overwriting str in
zxdh_get_dev_fw_str(), and should the version components in
zxdh_query_device() be parsed as unsigned 16-bit values (or masked to 0xffff)
so negative or oversized values cannot corrupt upper bits of props->fw_ver?

[ ... ]

> +static int zxdh_init_rdma_device(struct zxdh_device *zdev)
> +{
> +	struct pci_dev *pcidev = zdev->rf->pcidev;
> +
> +	if (zdev->roce_mode)
> +		zxdh_init_roce_device(zdev);
> +	else
> +		return -EPFNOSUPPORT;
> +
> +	zdev->ibdev.phys_port_cnt = 1;
> +	zdev->ibdev.num_comp_vectors = zdev->rf->ceqs_count;
> +	zdev->ibdev.dev.parent = &pcidev->dev;
> +
> +	ib_set_device_ops(&zdev->ibdev, &zxdh_dev_ops);
> +
> +	return 0;
> +}

[Severity: Medium]
Does calling ib_set_device_ops(&zdev->ibdev, &zxdh_dev_ops) here at the end of
zxdh_init_rdma_device() leak zdev->rf, zdev->hdl, rf->qp_cnt_8k_idxs, and
rf->mem_rsrc if an earlier error path is taken in zxdh_probe()?

In drivers/infiniband/hw/zrdma/zrdma_main.c:zxdh_probe(), after allocating
zdev with ib_alloc_device(), the driver allocates zdev->rf, initializes
zdev->ah_list_lock in zxdh_fill_device_info(), allocates zdev->hdl, and
allocates rf->qp_cnt_8k_idxs and rf->mem_rsrc in zxdh_ctrl_init_hw():

drivers/infiniband/hw/zrdma/zrdma_main.c:zxdh_probe() {
	...
	hdl = kzalloc_obj(*hdl);
	if (!hdl) {
		ib_dealloc_device(&zdev->ibdev);
		return -ENOMEM;
	}
	...
	err = zxdh_ctrl_init_hw(rf);
	if (err)
		goto err_ctrl_init;

	err = zxdh_ib_register_device(zdev);
	if (err)
		goto err_ib_reg;
	...
err_ib_reg:
err_ctrl_init:
	ib_dealloc_device(&zdev->ibdev);

	return err;
}

All of those failure paths (as well as the !zdev->roce_mode return path in
zxdh_init_rdma_device()) rely on ib_dealloc_device(&zdev->ibdev) invoking
zxdh_ib_dealloc_device() via zdev->ibdev.ops.dealloc_driver, which is Not set
until ib_set_device_ops() runs. Should ib_set_device_ops() be called
immediately after ib_alloc_device() in zxdh_probe() (or at the start of device
initialization) so dealloc_driver is always registered before any error path
calls ib_dealloc_device()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261024071539.2182864-1-zhang.yanze@zte.com.cn?part=7

      reply	other threads:[~2026-10-24  7:45 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24  7:15 [PATCH rdma v6 0/7] Add ZTE DingHai Ethernet Protocol Driver for RDMA Yanze Zhang
2026-10-24  7:15 ` [PATCH 1/7] RDMA/zrdma: Add build infrastructure and module skeleton Yanze Zhang
2026-10-24  7:34   ` sashiko-bot
2026-10-24  7:15 ` [PATCH 2/7] RDMA/zrdma: Add core data structures and ABI definitions Yanze Zhang
2026-10-24  7:32   ` sashiko-bot
2026-10-24 18:33   ` kernel test robot
2026-10-24  7:15 ` [PATCH 3/7] RDMA/zrdma: Add internal headers and hardware register definitions Yanze Zhang
2026-10-24  7:31   ` sashiko-bot
2026-10-24  8:43   ` [PATCH v6? " Markus Elfring
2026-10-24  7:15 ` [PATCH 4/7] RDMA/zrdma: Add hardware resource initialization and verbs skeleton Yanze Zhang
2026-10-24  7:36   ` sashiko-bot
2026-10-24  7:15 ` [PATCH 5/7] RDMA/zrdma: Add device probe and remove logic Yanze Zhang
2026-10-24  7:38   ` sashiko-bot
2026-10-24  7:15 ` [PATCH 6/7] RDMA/zrdma: Implement context, PD, and mmap operations Yanze Zhang
2026-10-24  7:43   ` sashiko-bot
2026-10-24  7:15 ` [PATCH 7/7] RDMA/zrdma: Implement device and port query operations Yanze Zhang
2026-10-24  7:45   ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-183592@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=zhang.yanze@zte.com.cn \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox