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
prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.