// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* * Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved */ #include "mlx5_ib.h" #include "data_direct.h" static LIST_HEAD(mlx5_data_direct_dev_list); static LIST_HEAD(mlx5_data_direct_reg_list); /* * This mutex should be held when accessing either of the above lists */ static DEFINE_MUTEX(mlx5_data_direct_mutex); struct mlx5_data_direct_registration { struct mlx5_ib_dev *ibdev; char vuid[MLX5_ST_SZ_BYTES(array1024_auto) + 1]; struct list_head list; }; static const struct pci_device_id mlx5_data_direct_pci_table[] = { { PCI_VDEVICE(MELLANOX, 0x2100) }, /* ConnectX-8 Data Direct */ { 0, } }; static int mlx5_data_direct_vpd_get_vuid(struct mlx5_data_direct_dev *dev) { struct pci_dev *pdev = dev->pdev; unsigned int vpd_size, kw_len; u8 *vpd_data; int start; int ret; vpd_data = pci_vpd_alloc(pdev, &vpd_size); if (IS_ERR(vpd_data)) { pci_err(pdev, "Unable to read VPD, err=%ld\n", PTR_ERR(vpd_data)); return PTR_ERR(vpd_data); } start = pci_vpd_find_ro_info_keyword(vpd_data, vpd_size, "VU", &kw_len); if (start < 0) { ret = start; pci_err(pdev, "VU keyword not found, err=%d\n", ret); goto end; } dev->vuid = kmemdup_nul(vpd_data + start, kw_len, GFP_KERNEL); ret = dev->vuid ? 0 : -ENOMEM; end: kfree(vpd_data); return ret; } static void mlx5_data_direct_shutdown(struct pci_dev *pdev) { pci_disable_device(pdev); } static int mlx5_data_direct_set_dma_caps(struct pci_dev *pdev) { int err; err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (err) { dev_warn(&pdev->dev, "Warning: couldn't set 64-bit PCI DMA mask, err=%d\n", err); err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (err) { dev_err(&pdev->dev, "Can't set PCI DMA mask, err=%d\n", err); return err; } } dma_set_max_seg_size(&pdev->dev, SZ_2G); return 0; } int mlx5_data_direct_ib_reg(struct mlx5_ib_dev *ibdev, char *vuid) { struct mlx5_data_direct_registration *reg; struct mlx5_data_direct_dev *dev; reg = kzalloc(sizeof(*reg), GFP_KERNEL); if (!reg) return -ENOMEM; reg->ibdev = ibdev; strcpy(reg->vuid, vuid); mutex_lock(&mlx5_data_direct_mutex); list_for_each_entry(dev, &mlx5_data_direct_dev_list, list) { if (strcmp(dev->vuid, vuid) == 0) { mlx5_ib_data_direct_bind(ibdev, dev); break; } } /* Add the registration to its global list, to be used upon bind/unbind * of its affiliated data direct device */ list_add_tail(®->list, &mlx5_data_direct_reg_list); mutex_unlock(&mlx5_data_direct_mutex); return 0; } void mlx5_data_direct_ib_unreg(struct mlx5_ib_dev *ibdev) { struct mlx5_data_direct_registration *reg; mutex_lock(&mlx5_data_direct_mutex); list_for_each_entry(reg, &mlx5_data_direct_reg_list, list) { if (reg->ibdev == ibdev) { list_del(®->list); kfree(reg); goto end; } } WARN_ON(true); end: mutex_unlock(&mlx5_data_direct_mutex); } static void mlx5_data_direct_dev_reg(struct mlx5_data_direct_dev *dev) { struct mlx5_data_direct_registration *reg; mutex_lock(&mlx5_data_direct_mutex); list_for_each_entry(reg, &mlx5_data_direct_reg_list, list) { if (strcmp(dev->vuid, reg->vuid) == 0) mlx5_ib_data_direct_bind(reg->ibdev, dev); } /* Add the data direct device to the global list, further IB devices may * use it later as well */ list_add_tail(&dev->list, &mlx5_data_direct_dev_list); mutex_unlock(&mlx5_data_direct_mutex); } static void mlx5_data_direct_dev_unreg(struct mlx5_data_direct_dev *dev) { struct mlx5_data_direct_registration *reg; mutex_lock(&mlx5_data_direct_mutex); /* Prevent any further affiliations */ list_del(&dev->list); list_for_each_entry(reg, &mlx5_data_direct_reg_list, list) { if (strcmp(dev->vuid, reg->vuid) == 0) mlx5_ib_data_direct_unbind(reg->ibdev); } mutex_unlock(&mlx5_data_direct_mutex); } static int mlx5_data_direct_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct mlx5_data_direct_dev *dev; int err; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev->device = &pdev->dev; dev->pdev = pdev; pci_set_drvdata(dev->pdev, dev); err = pci_enable_device(pdev); if (err) { dev_err(dev->device, "Cannot enable PCI device, err=%d\n", err); goto err; } pci_set_master(pdev); err = mlx5_data_direct_set_dma_caps(pdev); if (err) goto err_disable; if (pci_enable_atomic_ops_to_root(pdev, PCI_EXP_DEVCAP2_ATOMIC_COMP32) && pci_enable_atomic_ops_to_root(pdev, PCI_EXP_DEVCAP2_ATOMIC_COMP64) && pci_enable_atomic_ops_to_root(pdev, PCI_EXP_DEVCAP2_ATOMIC_COMP128)) dev_dbg(dev->device, "Enabling pci atomics failed\n"); err = mlx5_data_direct_vpd_get_vuid(dev); if (err) goto err_disable; mlx5_data_direct_dev_reg(dev); return 0; err_disable: pci_disable_device(pdev); err: kfree(dev); return err; } static void mlx5_data_direct_remove(struct pci_dev *pdev) { struct mlx5_data_direct_dev *dev = pci_get_drvdata(pdev); mlx5_data_direct_dev_unreg(dev); pci_disable_device(pdev); kfree(dev->vuid); kfree(dev); } static struct pci_driver mlx5_data_direct_driver = { .name = KBUILD_MODNAME, .id_table = mlx5_data_direct_pci_table, .probe = mlx5_data_direct_probe, .remove = mlx5_data_direct_remove, .shutdown = mlx5_data_direct_shutdown, }; int mlx5_data_direct_driver_register(void) { return pci_register_driver(&mlx5_data_direct_driver); } void mlx5_data_direct_driver_unregister(void) { pci_unregister_driver(&mlx5_data_direct_driver); }