mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
net: mana: Add support for CDX device ID 0x00C2
Add the CDX transport as mana_cdx.ko for device ID 0x00C2, using the shared gdma_core.ko. Allocate interrupts at probe, limit queues to available vectors, use 32-bit DMA, and keep CDX auxiliary devices separate from PCI. Signed-off-by: Manish Awasthi <mawasthi@linux.microsoft.com> Reviewed-by: Simon Horman <horms@kernel.org> Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com> Link: https://patch.msgid.link/20260924173054.589291-5-mawasthi@linux.microsoft.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
445927de07
commit
3d6792ea46
|
|
@ -35,4 +35,18 @@ config MICROSOFT_MANA
|
|||
To compile this driver as a module, choose M here.
|
||||
The module will be called mana.
|
||||
|
||||
config MICROSOFT_MANA_CDX
|
||||
tristate "Microsoft Azure Network Adapter (MANA) support for the CDX bus"
|
||||
depends on CDX_BUS
|
||||
depends on GENERIC_MSI_IRQ
|
||||
depends on 64BIT && !CPU_BIG_ENDIAN
|
||||
select MICROSOFT_GDMA_CORE
|
||||
help
|
||||
This driver supports the Microsoft Azure Network Adapter (MANA) on
|
||||
systems where it is integrated into the SoC and presented on the CDX
|
||||
bus rather than discovered over PCI.
|
||||
|
||||
To compile this driver as a module, choose M here.
|
||||
The module will be called mana_cdx.
|
||||
|
||||
endif #NET_VENDOR_MICROSOFT
|
||||
|
|
|
|||
|
|
@ -8,3 +8,6 @@ gdma_core-y := gdma_main.o shm_channel.o hw_channel.o mana_en.o mana_ethtool.o \
|
|||
|
||||
obj-$(CONFIG_MICROSOFT_MANA) += mana.o
|
||||
mana-y := gdma_pci.o
|
||||
|
||||
obj-$(CONFIG_MICROSOFT_MANA_CDX) += mana_cdx.o
|
||||
mana_cdx-y := gdma_cdx.o
|
||||
|
|
|
|||
340
drivers/net/ethernet/microsoft/mana/gdma_cdx.c
Normal file
340
drivers/net/ethernet/microsoft/mana/gdma_cdx.c
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2025, Microsoft Corporation. */
|
||||
#include <linux/module.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/cdx/cdx_bus.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/xarray.h>
|
||||
|
||||
#include <net/mana/mana.h>
|
||||
|
||||
#define MANA_CDX_GDMA_REGS_SIZE (GDMA_REG_SHM_OFFSET + sizeof(u64))
|
||||
|
||||
static int mana_cdx_msix_virq(struct gdma_context *gc, int msi)
|
||||
{
|
||||
unsigned int virq = msi_get_virq(gc->dev, msi);
|
||||
|
||||
/* CDX allocates its whole vector pool in mana_cdx_setup_hwc_irqs(),
|
||||
* so a vector with no Linux IRQ was never part of that pool.
|
||||
*/
|
||||
return virq ? virq : -EINVAL;
|
||||
}
|
||||
|
||||
static int mana_cdx_dev_reset(struct gdma_context *gc)
|
||||
{
|
||||
return cdx_dev_reset(gc->dev);
|
||||
}
|
||||
|
||||
static int mana_cdx_setup_hwc_irqs(struct gdma_context *gc)
|
||||
{
|
||||
struct cdx_device *cdx_dev = to_cdx_device(gc->dev);
|
||||
struct gdma_irq_context *gic;
|
||||
unsigned int max_irqs;
|
||||
int nvec;
|
||||
int err, i = 0, j;
|
||||
|
||||
max_irqs = cdx_dev->num_msi;
|
||||
|
||||
/* Need 1 IRQ for HWC and at least 1 for the data path queues */
|
||||
if (max_irqs < 2) {
|
||||
dev_err(&cdx_dev->dev, "Not enough MSI vectors: %u\n",
|
||||
max_irqs);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Constrain the device to 32-bit DMA addresses, for both the MSI
|
||||
* doorbells and the data path buffers, and never widen it afterwards.
|
||||
*
|
||||
* The MSI doorbell address has to land within the low 4GB. The data
|
||||
* path carries a second constraint: the device does not perform
|
||||
* writes to the final page of the 48-bit IOVA window, and
|
||||
* iommu_dma_alloc_iova() allocates downwards from the top of that
|
||||
* window, so the first queue created lands on the unusable page and
|
||||
* its event queue stalls permanently. PCI devices are shielded from
|
||||
* this by the low-4GB first attempt in iommu_dma_alloc_iova(), but
|
||||
* that attempt is gated on dev_is_pci() and CDX devices do not
|
||||
* qualify.
|
||||
*/
|
||||
err = dma_set_mask_and_coherent(gc->dev, DMA_BIT_MASK(32));
|
||||
if (err) {
|
||||
dev_err(&cdx_dev->dev, "Failed to set 32-bit DMA mask\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* The PCI path also calls dma_set_max_seg_size(), but that is
|
||||
* deliberately omitted here: the CDX core does not attach a
|
||||
* dma_parms to the device, so the helper would only WARN, and MANA
|
||||
* never builds scatter-gather lists, so the limit is never read.
|
||||
*/
|
||||
|
||||
err = cdx_enable_msi(cdx_dev);
|
||||
if (err) {
|
||||
dev_err(&cdx_dev->dev, "Failed to enable MSI\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = msi_domain_alloc_irqs(gc->dev, MSI_DEFAULT_DOMAIN, max_irqs);
|
||||
if (err) {
|
||||
dev_err(&cdx_dev->dev, "Failed to alloc MSI IRQs\n");
|
||||
cdx_disable_msi(cdx_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
nvec = max_irqs;
|
||||
|
||||
xa_init(&gc->irq_contexts);
|
||||
|
||||
/* No IRQ affinity hint is applied here. The PCI transport spreads its
|
||||
* vectors over the NUMA node local to the device; the CDX platforms
|
||||
* this driver targets are single-node, so there is nothing to spread
|
||||
* over. Affinity management can be added when a multi-node CDX host
|
||||
* exists to validate it against.
|
||||
*/
|
||||
for (i = 0; i < nvec; i++) {
|
||||
int msi = i;
|
||||
|
||||
gic = mana_gd_get_gic(gc, false, &msi);
|
||||
if (IS_ERR(gic)) {
|
||||
err = PTR_ERR(gic);
|
||||
goto free_irq;
|
||||
}
|
||||
}
|
||||
|
||||
gc->max_num_msix = nvec;
|
||||
gc->num_msix_usable = nvec;
|
||||
|
||||
/* CDX sizes its MSI pool at probe time and cannot grow it later, so
|
||||
* never share vectors: vector 0 is reserved for the HWC and each of
|
||||
* the remaining nvec - 1 vectors backs exactly one EQ. That bounds
|
||||
* the queue count at nvec - 1 and guarantees the HWC vector is never
|
||||
* handed out to a data queue.
|
||||
*/
|
||||
gc->msi_sharing = false;
|
||||
|
||||
gc->msi_bitmap = bitmap_zalloc(nvec, GFP_KERNEL);
|
||||
if (!gc->msi_bitmap) {
|
||||
err = -ENOMEM;
|
||||
goto free_irq;
|
||||
}
|
||||
|
||||
/* Reserve the HWC vector */
|
||||
set_bit(0, gc->msi_bitmap);
|
||||
|
||||
return 0;
|
||||
|
||||
free_irq:
|
||||
for (j = i - 1; j >= 0; j--)
|
||||
mana_gd_put_gic(gc, false, j);
|
||||
|
||||
xa_destroy(&gc->irq_contexts);
|
||||
msi_domain_free_irqs_all(gc->dev, MSI_DEFAULT_DOMAIN);
|
||||
cdx_disable_msi(cdx_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void mana_cdx_remove_irqs(struct gdma_context *gc)
|
||||
{
|
||||
struct cdx_device *cdx_dev = to_cdx_device(gc->dev);
|
||||
int i;
|
||||
|
||||
if (gc->max_num_msix < 1)
|
||||
return;
|
||||
|
||||
for (i = 0; i < gc->max_num_msix; i++) {
|
||||
if (!xa_load(&gc->irq_contexts, i))
|
||||
continue;
|
||||
|
||||
mana_gd_put_gic(gc, false, i);
|
||||
}
|
||||
|
||||
WARN_ON(!xa_empty(&gc->irq_contexts));
|
||||
|
||||
xa_destroy(&gc->irq_contexts);
|
||||
msi_domain_free_irqs_all(gc->dev, MSI_DEFAULT_DOMAIN);
|
||||
cdx_disable_msi(cdx_dev);
|
||||
|
||||
bitmap_free(gc->msi_bitmap);
|
||||
gc->msi_bitmap = NULL;
|
||||
gc->max_num_msix = 0;
|
||||
gc->num_msix_usable = 0;
|
||||
}
|
||||
|
||||
static int mana_cdx_setup_remaining_irqs(struct gdma_context *gc)
|
||||
{
|
||||
unsigned int max_queues_vport;
|
||||
|
||||
/* mana_gd_query_max_resources() may turn MSI sharing back on and round
|
||||
* the per-vPort queue count up to MANA_DEF_NUM_QUEUES. CDX sizes its
|
||||
* vector pool at probe time and cannot grow it later, and the HWC
|
||||
* vector must stay private, so give every vPort a private slice of the
|
||||
* remaining vectors instead. gc->max_num_queues is already capped at
|
||||
* num_msix_usable - 1 by the core.
|
||||
*/
|
||||
gc->msi_sharing = false;
|
||||
max_queues_vport = (gc->num_msix_usable - 1) / gc->num_ports;
|
||||
if (!max_queues_vport) {
|
||||
dev_err(gc->dev, "%u MSI vectors cannot serve %u vPorts\n",
|
||||
gc->num_msix_usable, gc->num_ports);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
gc->max_num_queues_vport = min(gc->max_num_queues, max_queues_vport);
|
||||
|
||||
dev_dbg(gc->dev, "%u MSI vectors, %u vPorts, %u queues, no MSI sharing\n",
|
||||
gc->num_msix_usable, gc->num_ports, gc->max_num_queues_vport);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct gdma_bus_ops mana_cdx_bus_ops = {
|
||||
.bus_name = "cdx",
|
||||
.adev_prefix = "mana_cdx",
|
||||
.msix_virq = mana_cdx_msix_virq,
|
||||
.setup_hwc_irqs = mana_cdx_setup_hwc_irqs,
|
||||
.setup_remaining_irqs = mana_cdx_setup_remaining_irqs,
|
||||
.remove_irqs = mana_cdx_remove_irqs,
|
||||
.dev_reset = mana_cdx_dev_reset,
|
||||
};
|
||||
|
||||
static int mana_cdx_gd_probe(struct cdx_device *cdx_dev)
|
||||
{
|
||||
struct gdma_context *gc;
|
||||
void __iomem *bar0_va;
|
||||
int err;
|
||||
|
||||
/* Each port has 2 CQs, each CQ has at most 1 EQE at a time */
|
||||
BUILD_BUG_ON(2 * MAX_PORTS_IN_MANA_DEV * GDMA_EQE_SIZE > EQ_SIZE);
|
||||
|
||||
err = cdx_dev_reset(&cdx_dev->dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = cdx_set_master(cdx_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
gc = vzalloc(sizeof(*gc));
|
||||
if (!gc) {
|
||||
err = -ENOMEM;
|
||||
goto clear_master;
|
||||
}
|
||||
|
||||
mutex_init(&gc->eq_test_event_mutex);
|
||||
mutex_init(&gc->gic_mutex);
|
||||
dev_set_drvdata(&cdx_dev->dev, gc);
|
||||
gc->bar0_pa = cdx_resource_start(cdx_dev, 0);
|
||||
gc->bar0_size = cdx_resource_len(cdx_dev, 0);
|
||||
if (gc->bar0_size < MANA_CDX_GDMA_REGS_SIZE) {
|
||||
dev_err(&cdx_dev->dev, "BAR0 size %#llx is too small\n",
|
||||
(u64)gc->bar0_size);
|
||||
err = -EINVAL;
|
||||
goto free_gc;
|
||||
}
|
||||
|
||||
bar0_va = ioremap(gc->bar0_pa, gc->bar0_size);
|
||||
if (!bar0_va) {
|
||||
err = -ENOMEM;
|
||||
goto free_gc;
|
||||
}
|
||||
|
||||
gc->numa_node = dev_to_node(&cdx_dev->dev);
|
||||
gc->bar0_va = bar0_va;
|
||||
gc->dev = &cdx_dev->dev;
|
||||
gc->bus_ops = &mana_cdx_bus_ops;
|
||||
|
||||
/* gc->is_pf is intentionally left clear: this device uses the VF
|
||||
* register layout, and the vPort and MAC filter registration that
|
||||
* is_pf selects is not required on this bus.
|
||||
*/
|
||||
|
||||
err = mana_gd_setup(gc);
|
||||
if (err)
|
||||
goto unmap_bar;
|
||||
|
||||
err = mana_probe(&gc->mana, false);
|
||||
if (err)
|
||||
goto cleanup_gd;
|
||||
|
||||
err = mana_rdma_probe(&gc->mana_ib);
|
||||
if (err)
|
||||
goto remove_mana;
|
||||
|
||||
/* If a hardware reset event arrived over HWC during probe, roll back:
|
||||
* the device state the rest of probe observed is no longer valid.
|
||||
*/
|
||||
if (test_and_set_bit(GC_PROBE_SUCCEEDED, &gc->flags)) {
|
||||
err = -EPROTO;
|
||||
goto remove_rdma;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
remove_rdma:
|
||||
mana_rdma_remove(&gc->mana_ib);
|
||||
remove_mana:
|
||||
mana_remove(&gc->mana, false);
|
||||
cleanup_gd:
|
||||
mana_gd_cleanup(gc);
|
||||
unmap_bar:
|
||||
iounmap(bar0_va);
|
||||
free_gc:
|
||||
dev_set_drvdata(&cdx_dev->dev, NULL);
|
||||
vfree(gc);
|
||||
clear_master:
|
||||
cdx_clear_master(cdx_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mana_cdx_gd_remove(struct cdx_device *cdx_dev)
|
||||
{
|
||||
struct gdma_context *gc = dev_get_drvdata(&cdx_dev->dev);
|
||||
|
||||
mana_rdma_remove(&gc->mana_ib);
|
||||
mana_remove(&gc->mana, false);
|
||||
|
||||
mana_gd_cleanup(gc);
|
||||
cdx_clear_master(cdx_dev);
|
||||
|
||||
iounmap(gc->bar0_va);
|
||||
|
||||
dev_set_drvdata(&cdx_dev->dev, NULL);
|
||||
vfree(gc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Quiesce the device for kexec. This is also called upon reboot/shutdown. */
|
||||
static void mana_cdx_gd_shutdown(struct cdx_device *cdx_dev)
|
||||
{
|
||||
struct gdma_context *gc = dev_get_drvdata(&cdx_dev->dev);
|
||||
|
||||
mana_rdma_remove(&gc->mana_ib);
|
||||
mana_remove(&gc->mana, true);
|
||||
|
||||
mana_gd_cleanup(gc);
|
||||
cdx_clear_master(cdx_dev);
|
||||
}
|
||||
|
||||
#define CDX_VENDOR_ID_MICROSOFT 0x1414
|
||||
static const struct cdx_device_id mana_cdx_table[] = {
|
||||
{ CDX_DEVICE(CDX_VENDOR_ID_MICROSOFT, MANA_CDX_DEVICE_ID) },
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(cdx, mana_cdx_table);
|
||||
|
||||
static struct cdx_driver gdma_cdx_driver = {
|
||||
.match_id_table = mana_cdx_table,
|
||||
.probe = mana_cdx_gd_probe,
|
||||
.remove = mana_cdx_gd_remove,
|
||||
.shutdown = mana_cdx_gd_shutdown,
|
||||
.driver = {
|
||||
.name = "mana_cdx",
|
||||
},
|
||||
};
|
||||
|
||||
module_driver(gdma_cdx_driver, cdx_driver_register, cdx_driver_unregister);
|
||||
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("Microsoft Azure Network Adapter driver for the CDX bus");
|
||||
MODULE_IMPORT_NS("CDX_BUS");
|
||||
MODULE_IMPORT_NS("NET_MANA");
|
||||
|
|
@ -619,6 +619,7 @@ static void mana_pci_remove_irqs(struct gdma_context *gc)
|
|||
|
||||
static const struct gdma_bus_ops mana_pci_bus_ops = {
|
||||
.bus_name = "pci",
|
||||
.adev_prefix = "mana",
|
||||
.msix_can_alloc_dyn = mana_pci_msix_can_alloc_dyn,
|
||||
.msix_virq = mana_pci_msix_virq,
|
||||
.msix_alloc_at = mana_pci_msix_alloc_at,
|
||||
|
|
|
|||
|
|
@ -3957,8 +3957,9 @@ static int add_adev(struct gdma_dev *gd, const char *name)
|
|||
|
||||
/* madev is owned by the auxiliary device */
|
||||
madev = NULL;
|
||||
/* Keep the established mana.{eth,rdma} auxiliary match names. */
|
||||
ret = __auxiliary_device_add(adev, "mana");
|
||||
/* Keep match names independent of the common module's name. */
|
||||
ret = __auxiliary_device_add(adev,
|
||||
gd->gdma_context->bus_ops->adev_prefix);
|
||||
if (ret)
|
||||
goto add_fail;
|
||||
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ struct gdma_context;
|
|||
* instance in gdma_context.bus_ops at probe time.
|
||||
*
|
||||
* @bus_name: Bus token used to build the per-vector IRQ names.
|
||||
* @adev_prefix: Prefix used for auxiliary-device match names.
|
||||
* @msix_can_alloc_dyn: True if the bus can allocate MSI-X vectors after
|
||||
* probe time. Buses that size their vector pool at probe and cannot
|
||||
* grow it later leave this NULL.
|
||||
|
|
@ -459,6 +460,7 @@ struct gdma_context;
|
|||
*/
|
||||
struct gdma_bus_ops {
|
||||
const char *bus_name;
|
||||
const char *adev_prefix;
|
||||
bool (*msix_can_alloc_dyn)(struct gdma_context *gc);
|
||||
int (*msix_virq)(struct gdma_context *gc, int msi);
|
||||
int (*msix_alloc_at)(struct gdma_context *gc, int *msi);
|
||||
|
|
@ -684,6 +686,8 @@ struct gdma_eqe {
|
|||
#define MANA_PF2_DEVICE_ID 0x00C1
|
||||
#define MANA_VF_DEVICE_ID 0x00BA
|
||||
|
||||
#define MANA_CDX_DEVICE_ID 0x00C2
|
||||
|
||||
struct gdma_posted_wqe_info {
|
||||
u32 wqe_size_in_bu;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user