eea: introduce PCI framework

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>
This commit is contained in:
Xuan Zhuo
2026-05-14 17:51:31 +08:00
committed by Paolo Abeni
parent 7af2a94f4d
commit 4f68ffc45e
8 changed files with 583 additions and 0 deletions

View 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>

View 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"

View 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/

View File

@@ -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

View File

@@ -0,0 +1,5 @@
#
# Makefile for the Alibaba network device drivers.
#
obj-$(CONFIG_ALIBABA_EEA) += eea/

View File

@@ -0,0 +1,3 @@
obj-$(CONFIG_ALIBABA_EEA) += eea.o
eea-y := eea_pci.o

View File

@@ -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");

View File

@@ -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