]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
eea: introduce PCI framework
authorXuan Zhuo <xuanzhuo@linux.alibaba.com>
Thu, 14 May 2026 09:51:31 +0000 (17:51 +0800)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 19 May 2026 10:07:49 +0000 (12:07 +0200)
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).

This commit implements the EEA PCI probe functionality.

Reviewed-by: Dust Li <dust.li@linux.alibaba.com>
Reviewed-by: Philo Lu <lulie@linux.alibaba.com>
Signed-off-by: Wen Gu <guwen@linux.alibaba.com>
Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
Link: https://patch.msgid.link/20260514095138.80680-2-xuanzhuo@linux.alibaba.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
MAINTAINERS
drivers/net/ethernet/Kconfig
drivers/net/ethernet/Makefile
drivers/net/ethernet/alibaba/Kconfig [new file with mode: 0644]
drivers/net/ethernet/alibaba/Makefile [new file with mode: 0644]
drivers/net/ethernet/alibaba/eea/Makefile [new file with mode: 0644]
drivers/net/ethernet/alibaba/eea/eea_pci.c [new file with mode: 0644]
drivers/net/ethernet/alibaba/eea/eea_pci.h [new file with mode: 0644]

index edd161f2c62df25c8bc09fca88ad620c4d2b3aab..5db1a2923dd27cdedcf8a9a61399b4f7632cb9ab 100644 (file)
@@ -808,6 +808,14 @@ S: Maintained
 F:     Documentation/i2c/busses/i2c-ali1563.rst
 F:     drivers/i2c/busses/i2c-ali1563.c
 
+ALIBABA ELASTIC ETHERNET ADAPTER DRIVER
+M:     Xuan Zhuo <xuanzhuo@linux.alibaba.com>
+M:     Wen Gu <guwen@linux.alibaba.com>
+R:     Philo Lu <lulie@linux.alibaba.com>
+L:     netdev@vger.kernel.org
+S:     Maintained
+F:     drivers/net/ethernet/alibaba/eea
+
 ALIBABA ELASTIC RDMA DRIVER
 M:     Cheng Xu <chengyou@linux.alibaba.com>
 M:     Kai Shen <kaishen@linux.alibaba.com>
index b8f70e2a1763ad61e34b4b0895f69929a82a878c..78c79ad7bba5536893d47ce9cc3dde61fdaece4e 100644 (file)
@@ -22,6 +22,7 @@ source "drivers/net/ethernet/aeroflex/Kconfig"
 source "drivers/net/ethernet/agere/Kconfig"
 source "drivers/net/ethernet/airoha/Kconfig"
 source "drivers/net/ethernet/alacritech/Kconfig"
+source "drivers/net/ethernet/alibaba/Kconfig"
 source "drivers/net/ethernet/allwinner/Kconfig"
 source "drivers/net/ethernet/altera/Kconfig"
 source "drivers/net/ethernet/amazon/Kconfig"
index 57344fec6ce047b0d4c8e35f382b241e4fc31129..bba55d9af3877079ab7197ef4a64ce0a5c0b4225 100644 (file)
@@ -12,6 +12,7 @@ obj-$(CONFIG_NET_VENDOR_ADI) += adi/
 obj-$(CONFIG_NET_VENDOR_AGERE) += agere/
 obj-$(CONFIG_NET_VENDOR_AIROHA) += airoha/
 obj-$(CONFIG_NET_VENDOR_ALACRITECH) += alacritech/
+obj-$(CONFIG_NET_VENDOR_ALIBABA) += alibaba/
 obj-$(CONFIG_NET_VENDOR_ALLWINNER) += allwinner/
 obj-$(CONFIG_ALTERA_TSE) += altera/
 obj-$(CONFIG_NET_VENDOR_AMAZON) += amazon/
diff --git a/drivers/net/ethernet/alibaba/Kconfig b/drivers/net/ethernet/alibaba/Kconfig
new file mode 100644 (file)
index 0000000..b8fd3c9
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# Alibaba network device configuration
+#
+
+config NET_VENDOR_ALIBABA
+       bool "Alibaba Devices"
+       default y
+       help
+         If you have a network (Ethernet) device belonging to this class, say Y.
+
+         Note that the answer to this question doesn't directly affect the
+         kernel: saying N will just cause the configurator to skip all
+         the questions about Alibaba devices. If you say Y, you will be asked
+         for your specific device in the following questions.
+
+if NET_VENDOR_ALIBABA
+
+config ALIBABA_EEA
+       tristate "Alibaba Elastic Ethernet Adapter support"
+       depends on PCI_MSI
+       depends on 64BIT
+       select PAGE_POOL
+       help
+         This driver supports Alibaba Elastic Ethernet Adapter.
+
+         To compile this driver as a module, choose M here.
+
+endif #NET_VENDOR_ALIBABA
diff --git a/drivers/net/ethernet/alibaba/Makefile b/drivers/net/ethernet/alibaba/Makefile
new file mode 100644 (file)
index 0000000..63a527e
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Makefile for the Alibaba network device drivers.
+#
+
+obj-$(CONFIG_ALIBABA_EEA) += eea/
diff --git a/drivers/net/ethernet/alibaba/eea/Makefile b/drivers/net/ethernet/alibaba/eea/Makefile
new file mode 100644 (file)
index 0000000..a07f301
--- /dev/null
@@ -0,0 +1,3 @@
+
+obj-$(CONFIG_ALIBABA_EEA) += eea.o
+eea-y := eea_pci.o
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c
new file mode 100644 (file)
index 0000000..65a0ceb
--- /dev/null
@@ -0,0 +1,487 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
+
+#include "eea_pci.h"
+
+#define EEA_PCI_DB_OFFSET 4096
+#define EEA_PCI_DB_MIN_SIZE 8
+#define EEA_PCI_DB_MAX_SIZE 512
+#define EEA_PCI_Q_MAX_NUM 1000
+
+struct eea_pci_cfg {
+       __le32 reserve0;
+       __le32 reserve1;
+       __le32 drv_f_idx;
+       __le32 drv_f;
+
+#define EEA_S_INIT         (BIT(0) | BIT(1))
+#define EEA_S_OK           BIT(2)
+#define EEA_S_FEATURE_DONE BIT(3)
+#define EEA_S_FAILED       BIT(7)
+       u8   device_status;
+       u8   reserved[7];
+
+       __le32 rx_num_max;
+       __le32 tx_num_max;
+       __le32 db_blk_size;
+
+       /* admin queue cfg */
+       __le16 aq_size;
+       __le16 aq_msix_vector;
+       __le32 aq_db_off;
+
+       __le32 aq_sq_addr;
+       __le32 aq_sq_addr_hi;
+       __le32 aq_cq_addr;
+       __le32 aq_cq_addr_hi;
+
+       __le32 reserved1;
+       __le64 hw_ts;
+};
+
+struct eea_pci_device {
+       struct eea_device edev;
+       struct pci_dev *pci_dev;
+
+       u32 msix_vec_n;
+       u32 db_len;
+
+       void __iomem *reg;
+       void __iomem *db_base;
+       void __iomem *db_end;
+
+       bool shutdown;
+};
+
+#define cfg_pointer(reg, item) \
+       ((void __iomem *)((reg) + offsetof(struct eea_pci_cfg, item)))
+
+#define cfg_write8(reg, item, val) iowrite8(val, cfg_pointer(reg, item))
+#define cfg_write32(reg, item, val) iowrite32(val, cfg_pointer(reg, item))
+
+#define cfg_read8(reg, item) ioread8(cfg_pointer(reg, item))
+#define cfg_read32(reg, item) ioread32(cfg_pointer(reg, item))
+#define cfg_read64(reg, item) ioread64(cfg_pointer(reg, item))
+
+const char *eea_pci_name(struct eea_device *edev)
+{
+       return pci_name(edev->ep_dev->pci_dev);
+}
+
+int eea_pci_domain_nr(struct eea_device *edev)
+{
+       return pci_domain_nr(edev->ep_dev->pci_dev->bus);
+}
+
+u16 eea_pci_bdf(struct eea_device *edev)
+{
+       return pci_dev_id(edev->ep_dev->pci_dev);
+}
+
+static void eea_pci_io_set_status(struct eea_device *edev, u8 status)
+{
+       struct eea_pci_device *ep_dev = edev->ep_dev;
+
+       cfg_write8(ep_dev->reg, device_status, status);
+}
+
+static u8 eea_pci_io_get_status(struct eea_device *edev)
+{
+       struct eea_pci_device *ep_dev = edev->ep_dev;
+
+       return cfg_read8(ep_dev->reg, device_status);
+}
+
+static void eea_add_status(struct eea_device *dev, u32 status)
+{
+       eea_pci_io_set_status(dev, eea_pci_io_get_status(dev) | status);
+}
+
+#define EEA_RESET_TIMEOUT_US (60 * 1000 * 1000)
+
+int eea_device_reset(struct eea_device *edev)
+{
+       struct eea_pci_device *ep_dev = edev->ep_dev;
+       int err;
+       u8 val;
+
+       eea_pci_io_set_status(edev, 0);
+
+       /* We are no longer waiting for device ack during the shutdown flow. */
+       if (ep_dev->shutdown)
+               return 0;
+
+       /* A longer timeout is set here to handle edge cases, though it should
+        * return promptly in most scenarios.
+        *
+        * In our case, all replies are handled by the DPU software, so there is
+        * no race condition between the hardware processes and the register.
+        */
+       err = read_poll_timeout(cfg_read8, val, (!val || val == 0xFF), 20,
+                               EEA_RESET_TIMEOUT_US,
+                               false, ep_dev->reg, device_status);
+
+       /* Surprise PCIe Removal */
+       if (val == 0xFF)
+               return -EINVAL;
+
+       return err;
+}
+
+int eea_pci_set_aq_up(struct eea_device *edev)
+{
+       struct eea_pci_device *ep_dev = edev->ep_dev;
+       u8 status = eea_pci_io_get_status(edev);
+       int err;
+       u8 val;
+
+       eea_pci_io_set_status(edev, status | EEA_S_OK);
+
+       /* A longer timeout is set here to handle edge cases, though it should
+        * return promptly in most scenarios.
+        *
+        * In our case, all replies are handled by the DPU software, so there is
+        * no race condition between the hardware processes and the register.
+        */
+       err = read_poll_timeout(cfg_read8, val,
+                               val & (EEA_S_OK | EEA_S_FAILED),
+                               20, EEA_RESET_TIMEOUT_US,
+                               false, ep_dev->reg, device_status);
+
+       /* Surprise PCIe Removal */
+       if (val == 0xFF)
+               return -EINVAL;
+
+       /* device fail */
+       if (val & EEA_S_FAILED)
+               return -EINVAL;
+
+       return err;
+}
+
+static int eea_negotiate(struct eea_device *edev)
+{
+       struct eea_pci_device *ep_dev;
+       u32 status;
+
+       ep_dev = edev->ep_dev;
+
+       edev->features = 0;
+
+       cfg_write32(ep_dev->reg, drv_f_idx, 0);
+       cfg_write32(ep_dev->reg, drv_f, lower_32_bits(edev->features));
+       cfg_write32(ep_dev->reg, drv_f_idx, 1);
+       cfg_write32(ep_dev->reg, drv_f, upper_32_bits(edev->features));
+
+       eea_add_status(edev, EEA_S_FEATURE_DONE);
+       status = eea_pci_io_get_status(edev);
+
+       /* Surprise PCIe Removal */
+       if (status == 0xFF)
+               return -EINVAL;
+
+       if (!(status & EEA_S_FEATURE_DONE))
+               return -ENODEV;
+
+       return 0;
+}
+
+static void eea_pci_release_resource(struct eea_pci_device *ep_dev)
+{
+       struct pci_dev *pci_dev = ep_dev->pci_dev;
+
+       if (ep_dev->reg) {
+               pci_iounmap(pci_dev, ep_dev->reg);
+               ep_dev->reg = NULL;
+       }
+
+       if (ep_dev->msix_vec_n) {
+               ep_dev->msix_vec_n = 0;
+               pci_free_irq_vectors(ep_dev->pci_dev);
+       }
+
+       pci_clear_master(pci_dev);
+       pci_release_regions(pci_dev);
+       pci_disable_device(pci_dev);
+}
+
+static int eea_pci_setup(struct pci_dev *pci_dev, struct eea_pci_device *ep_dev)
+{
+       int err, n, ret, len;
+
+       ep_dev->pci_dev = pci_dev;
+
+       err = pci_enable_device(pci_dev);
+       if (err)
+               return err;
+
+       err = pci_request_regions(pci_dev, "EEA");
+       if (err)
+               goto err_disable_dev;
+
+       if (pci_resource_len(pci_dev, 0) < EEA_PCI_DB_OFFSET) {
+               dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
+                       (u64)pci_resource_len(pci_dev, 0));
+               err = -EINVAL;
+               goto err_release_regions;
+       }
+
+       ep_dev->reg = pci_iomap(pci_dev, 0, 0);
+       if (!ep_dev->reg) {
+               dev_err(&pci_dev->dev, "Failed to map pci bar!\n");
+               err = -ENOMEM;
+               goto err_release_regions;
+       }
+
+       err = eea_device_reset(&ep_dev->edev);
+       if (err) {
+               dev_err(&pci_dev->dev, "Failed to reset device for setup!\n");
+               goto err_unmap_reg;
+       }
+
+       err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
+       if (err) {
+               dev_warn(&pci_dev->dev, "Failed to enable 64-bit DMA.\n");
+               goto err_unmap_reg;
+       }
+
+       pci_set_master(pci_dev);
+
+       ep_dev->edev.rx_num = cfg_read32(ep_dev->reg, rx_num_max);
+       ep_dev->edev.tx_num = cfg_read32(ep_dev->reg, tx_num_max);
+
+       if (ep_dev->edev.rx_num > EEA_PCI_Q_MAX_NUM ||
+           ep_dev->edev.tx_num > EEA_PCI_Q_MAX_NUM) {
+               dev_err(&pci_dev->dev, "Invalid queue num %u %u\n",
+                       ep_dev->edev.rx_num,
+                       ep_dev->edev.tx_num);
+               err = -EINVAL;
+               goto err_clear_master;
+       }
+
+       ep_dev->edev.db_blk_size = cfg_read32(ep_dev->reg, db_blk_size);
+       if (!IS_ALIGNED(ep_dev->edev.db_blk_size, 8) ||
+           ep_dev->edev.db_blk_size > EEA_PCI_DB_MAX_SIZE ||
+           ep_dev->edev.db_blk_size < EEA_PCI_DB_MIN_SIZE) {
+               dev_err(&pci_dev->dev, "Invalid db size %u\n",
+                       ep_dev->edev.db_blk_size);
+               err = -EINVAL;
+               goto err_clear_master;
+       }
+
+       ep_dev->db_len = ep_dev->edev.db_blk_size * (ep_dev->edev.rx_num +
+                                                    ep_dev->edev.tx_num + 1);
+       ep_dev->db_base = ep_dev->reg + EEA_PCI_DB_OFFSET;
+       ep_dev->db_end = ep_dev->db_base + ep_dev->db_len;
+
+       len = ep_dev->db_end - ep_dev->reg;
+
+       if (pci_resource_len(pci_dev, 0) < len) {
+               dev_err(&pci_dev->dev, "Bar 0 is too small %llu\n",
+                       (u64)pci_resource_len(pci_dev, 0));
+               err = -EINVAL;
+               goto err_clear_master;
+       }
+
+       /* In our design, the number of hardware interrupts matches the maximum
+        * number of queues. If pci_alloc_irq_vectors failed, return directly.
+        *
+        * 2: adminq, error handle
+        */
+       n = ep_dev->edev.rx_num + 2;
+       ret = pci_alloc_irq_vectors(ep_dev->pci_dev, n, n, PCI_IRQ_MSIX);
+       if (ret != n) {
+               err = ret;
+               goto err_clear_master;
+       }
+
+       ep_dev->msix_vec_n = ret;
+
+       return 0;
+
+err_clear_master:
+       pci_clear_master(pci_dev);
+
+err_unmap_reg:
+       pci_iounmap(pci_dev, ep_dev->reg);
+       ep_dev->reg = NULL;
+
+err_release_regions:
+       pci_release_regions(pci_dev);
+
+err_disable_dev:
+       pci_disable_device(pci_dev);
+
+       return err;
+}
+
+void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off)
+{
+       u32 max_off;
+
+       if (!IS_ALIGNED(off, 8))
+               return NULL;
+
+       max_off = edev->ep_dev->db_len - edev->db_blk_size;
+
+       if (off > max_off)
+               return NULL;
+
+       return edev->ep_dev->db_base + off;
+}
+
+u64 eea_pci_device_ts(struct eea_device *edev)
+{
+       struct eea_pci_device *ep_dev = edev->ep_dev;
+
+       return cfg_read64(ep_dev->reg, hw_ts);
+}
+
+static int eea_init_device(struct eea_device *edev)
+{
+       int err;
+
+       err = eea_device_reset(edev);
+       if (err)
+               return err;
+
+       eea_pci_io_set_status(edev, EEA_S_INIT);
+
+       err = eea_negotiate(edev);
+       if (err)
+               goto err;
+
+       /* do net device probe ... */
+
+       return 0;
+err:
+       eea_add_status(edev, EEA_S_FAILED);
+       return err;
+}
+
+static int __eea_pci_probe(struct pci_dev *pci_dev,
+                          struct eea_pci_device *ep_dev)
+{
+       int err;
+
+       pci_set_drvdata(pci_dev, ep_dev);
+
+       err = eea_pci_setup(pci_dev, ep_dev);
+       if (err)
+               return err;
+
+       err = eea_init_device(&ep_dev->edev);
+       if (err)
+               goto err_pci_rel;
+
+       return 0;
+
+err_pci_rel:
+       eea_pci_release_resource(ep_dev);
+       return err;
+}
+
+static void __eea_pci_remove(struct pci_dev *pci_dev)
+{
+       struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+       struct device *dev = get_device(&ep_dev->pci_dev->dev);
+
+       eea_pci_release_resource(ep_dev);
+
+       put_device(dev);
+}
+
+static int eea_pci_probe(struct pci_dev *pci_dev,
+                        const struct pci_device_id *id)
+{
+       struct eea_pci_device *ep_dev;
+       struct eea_device *edev;
+       int err;
+
+       ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL);
+       if (!ep_dev)
+               return -ENOMEM;
+
+       edev = &ep_dev->edev;
+
+       edev->ep_dev = ep_dev;
+       edev->dma_dev = &pci_dev->dev;
+
+       ep_dev->pci_dev = pci_dev;
+
+       err = __eea_pci_probe(pci_dev, ep_dev);
+       if (err) {
+               pci_set_drvdata(pci_dev, NULL);
+               kfree(ep_dev);
+       }
+
+       return err;
+}
+
+static void eea_pci_remove(struct pci_dev *pci_dev)
+{
+       struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+
+       eea_device_reset(&ep_dev->edev);
+
+       __eea_pci_remove(pci_dev);
+
+       pci_set_drvdata(pci_dev, NULL);
+       kfree(ep_dev);
+}
+
+static void eea_pci_shutdown(struct pci_dev *pci_dev)
+{
+       struct eea_pci_device *ep_dev = pci_get_drvdata(pci_dev);
+       struct eea_device *edev;
+
+       edev = &ep_dev->edev;
+
+       ep_dev->shutdown = true;
+
+       /* do net device stop and clear. */
+
+       eea_device_reset(edev);
+
+       pci_clear_master(pci_dev);
+}
+
+static const struct pci_device_id eea_pci_id_table[] = {
+       { PCI_DEVICE(PCI_VENDOR_ID_ALIBABA, 0x500B) },
+       { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, eea_pci_id_table);
+
+static struct pci_driver eea_pci_driver = {
+       .name            = "alibaba_eea",
+       .id_table        = eea_pci_id_table,
+       .probe           = eea_pci_probe,
+       .remove          = eea_pci_remove,
+       .shutdown        = eea_pci_shutdown,
+       .sriov_configure = pci_sriov_configure_simple,
+};
+
+static __init int eea_pci_init(void)
+{
+       return pci_register_driver(&eea_pci_driver);
+}
+
+static __exit void eea_pci_exit(void)
+{
+       pci_unregister_driver(&eea_pci_driver);
+}
+
+module_init(eea_pci_init);
+module_exit(eea_pci_exit);
+
+MODULE_DESCRIPTION("Driver for Alibaba Elastic Ethernet Adapter");
+MODULE_AUTHOR("Xuan Zhuo <xuanzhuo@linux.alibaba.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.h b/drivers/net/ethernet/alibaba/eea/eea_pci.h
new file mode 100644 (file)
index 0000000..746cce4
--- /dev/null
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Driver for Alibaba Elastic Ethernet Adapter.
+ *
+ * Copyright (C) 2025 Alibaba Inc.
+ */
+
+#ifndef __EEA_PCI_H__
+#define __EEA_PCI_H__
+
+#include <linux/pci.h>
+
+struct eea_pci_cap {
+       __u8 cap_vndr;
+       __u8 cap_next;
+       __u8 cap_len;
+       __u8 cfg_type;
+};
+
+struct eea_pci_reset_reg {
+       struct eea_pci_cap cap;
+       __le16 driver;
+       __le16 device;
+};
+
+struct eea_pci_device;
+
+struct eea_device {
+       struct eea_pci_device *ep_dev;
+       struct device         *dma_dev;
+       struct eea_net        *enet;
+
+       u64 features;
+
+       u32 rx_num;
+       u32 tx_num;
+       u32 db_blk_size;
+};
+
+const char *eea_pci_name(struct eea_device *edev);
+int eea_pci_domain_nr(struct eea_device *edev);
+u16 eea_pci_bdf(struct eea_device *edev);
+
+int eea_device_reset(struct eea_device *dev);
+int eea_pci_set_aq_up(struct eea_device *dev);
+
+u64 eea_pci_device_ts(struct eea_device *edev);
+
+void __iomem *eea_pci_db_addr(struct eea_device *edev, u32 off);
+#endif