| // 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); |
| } |