Files
linux/drivers/net/ethernet/alibaba/eea/eea_adminq.c
Xuan Zhuo 4c45a51e5b eea: probe the netdevice and create adminq
Add basic driver framework for the Alibaba Elastic Ethernet Adapter(EEA).

This commit creates the netdevice after PCI probe,
and initializes the admin queue to send commands to the device.

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-4-xuanzhuo@linux.alibaba.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2026-05-19 12:07:50 +02:00

543 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Alibaba Elastic Ethernet Adapter.
*
* Copyright (C) 2025 Alibaba Inc.
*/
#include <linux/etherdevice.h>
#include <linux/iopoll.h>
#include <linux/utsname.h>
#include <linux/version.h>
#include "eea_adminq.h"
#include "eea_net.h"
#include "eea_pci.h"
#include "eea_ring.h"
#define EEA_AQ_CMD_CFG_QUERY ((0 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_CREATE ((1 << 8) | 0)
#define EEA_AQ_CMD_QUEUE_DESTROY_ALL ((1 << 8) | 1)
#define EEA_AQ_CMD_HOST_INFO ((2 << 8) | 0)
#define EEA_AQ_CMD_DEV_STATUS ((3 << 8) | 0)
#define EEA_RING_DESC_F_AQ_PHASE (BIT(15) | BIT(7))
#define EEA_QUEUE_FLAGS_HW_SPLIT_HDR BIT(0)
#define EEA_QUEUE_FLAGS_SQCQ BIT(1)
#define EEA_QUEUE_FLAGS_HWTS BIT(2)
struct eea_aq_create {
__le32 flags;
/* queue index.
* rx: 0 == qidx % 2
* tx: 1 == qidx % 2
*/
__le16 qidx;
/* the depth of the queue */
__le16 depth;
/* 0: without SPLIT HDR
* 1: 128B
* 2: 256B
* 3: 512B
*/
u8 hdr_buf_size;
u8 sq_desc_size;
u8 cq_desc_size;
u8 reserve0;
/* The vector for the irq. rx,tx share the same vector */
__le16 msix_vector;
__le16 reserve;
/* sq ring cfg. */
__le32 sq_addr_low;
__le32 sq_addr_high;
/* cq ring cfg. Just valid when flags include EEA_QUEUE_FLAGS_SQCQ. */
__le32 cq_addr_low;
__le32 cq_addr_high;
};
struct eea_aq_queue_drv_status {
__le16 qidx;
__le16 sq_head;
__le16 cq_head;
__le16 reserved;
};
#define EEA_OS_DISTRO 0
#define EEA_DRV_TYPE 0
#define EEA_OS_LINUX 1
#define EEA_SPEC_VER_MAJOR 1
#define EEA_SPEC_VER_MINOR 0
struct eea_aq_host_info_cfg {
__le16 os_type;
__le16 os_dist;
__le16 drv_type;
__le16 kern_ver_major;
__le16 kern_ver_minor;
__le16 kern_ver_sub_minor;
__le16 drv_ver_major;
__le16 drv_ver_minor;
__le16 drv_ver_sub_minor;
__le16 spec_ver_major;
__le16 spec_ver_minor;
__le16 pci_bdf;
__le32 pci_domain;
u8 os_ver_str[64];
u8 isa_str[64];
};
#define EEA_HINFO_MAX_REP_LEN 1024
#define EEA_HINFO_REP_BAD 2
struct eea_aq_host_info_rep {
u8 op_code;
u8 has_reply;
u8 reply_str[EEA_HINFO_MAX_REP_LEN];
};
static struct eea_ring *qid_to_ering(struct eea_net *enet, u32 qid)
{
struct eea_ring *ering;
if (qid % 2 == 0)
ering = enet->rx[qid / 2]->ering;
else
ering = enet->tx[qid / 2].ering;
return ering;
}
#define EEA_AQ_TIMEOUT_US (60 * 1000 * 1000)
static void eea_device_broken(struct eea_net *enet)
{
if (enet->adminq.broken)
return;
eea_device_reset(enet->edev);
enet->adminq.broken = true;
}
static int eea_adminq_submit(struct eea_net *enet, u16 cmd,
dma_addr_t req_addr, dma_addr_t res_addr,
u32 req_size, u32 res_size, u32 *reply_len)
{
struct eea_aq_cdesc *cdesc;
struct eea_aq_desc *desc;
int ret;
if (enet->adminq.broken)
return -EIO;
desc = eea_ering_aq_alloc_desc(enet->adminq.ring);
desc->classid = cmd >> 8;
desc->command = cmd & 0xff;
desc->data_addr = cpu_to_le64(req_addr);
desc->data_len = cpu_to_le32(req_size);
desc->reply_addr = cpu_to_le64(res_addr);
desc->reply_len = cpu_to_le32(res_size);
/* for update flags */
dma_wmb();
desc->flags = cpu_to_le16(enet->adminq.phase);
eea_ering_sq_commit_desc(enet->adminq.ring);
eea_ering_kick(enet->adminq.ring);
++enet->adminq.num;
if ((enet->adminq.num % enet->adminq.ring->num) == 0)
enet->adminq.phase ^= EEA_RING_DESC_F_AQ_PHASE;
ret = read_poll_timeout(eea_ering_cq_get_desc, cdesc, cdesc, 10,
EEA_AQ_TIMEOUT_US, false, enet->adminq.ring);
if (ret) {
netdev_err(enet->netdev,
"adminq exec timeout. cmd: %d reset device.\n",
cmd);
/* The device must be reset before unmapping buffers to avoid
* potential DMA writes after the memory is freed.
*/
eea_device_broken(enet);
return ret;
}
/* Returns 0 on success, or a negative error code on failure. */
ret = le32_to_cpu(cdesc->status);
eea_ering_cq_ack_desc(enet->adminq.ring, 1);
if (ret)
netdev_err(enet->netdev,
"adminq exec failed. cmd: %d ret %d\n", cmd, ret);
else
*reply_len = le32_to_cpu(cdesc->reply_len);
return ret;
}
static int eea_adminq_exec(struct eea_net *enet, u16 cmd,
void *req, u32 req_size,
void *res, u32 res_size,
u32 *reply)
{
dma_addr_t req_addr = 0, res_addr = 0;
struct device *dma;
u32 reply_len = 0;
int ret;
if (reply)
*reply = 0;
dma = enet->edev->dma_dev;
if (req) {
req_addr = dma_map_single(dma, req, req_size, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(dma, req_addr)))
return -ENOMEM;
}
if (res) {
res_addr = dma_map_single(dma, res, res_size, DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(dma, res_addr))) {
ret = -ENOMEM;
goto err_unmap_req;
}
}
mutex_lock(&enet->adminq.lock);
ret = eea_adminq_submit(enet, cmd, req_addr, res_addr,
req_size, res_size, &reply_len);
mutex_unlock(&enet->adminq.lock);
if (res) {
dma_unmap_single(dma, res_addr, res_size, DMA_FROM_DEVICE);
if (ret)
memset(res, 0, res_size);
else if (res_size > reply_len)
memset(res + reply_len, 0, res_size - reply_len);
if (reply)
*reply = reply_len;
}
err_unmap_req:
if (req)
dma_unmap_single(dma, req_addr, req_size, DMA_TO_DEVICE);
return ret;
}
void eea_destroy_adminq(struct eea_net *enet)
{
struct eea_aq *aq;
aq = &enet->adminq;
if (aq->ring) {
eea_ering_free(aq->ring);
aq->ring = NULL;
aq->phase = 0;
}
kfree(aq->q_req_buf);
kfree(aq->q_res_buf);
aq->q_req_buf = NULL;
aq->q_res_buf = NULL;
}
int eea_create_adminq(struct eea_net *enet, u32 qid)
{
u32 db_size, q_size, num;
struct eea_ring *ering;
struct eea_aq *aq;
int err = -ENOMEM;
num = enet->edev->rx_num + enet->edev->tx_num;
aq = &enet->adminq;
ering = eea_ering_alloc(qid, 64, enet->edev, sizeof(struct eea_aq_desc),
sizeof(struct eea_aq_cdesc), "adminq");
if (!ering)
return -ENOMEM;
aq->ring = ering;
err = eea_pci_active_aq(ering, qid / 2 + 1);
if (err)
goto err;
aq->phase = BIT(7);
aq->num = 0;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
aq->q_req_size = q_size;
aq->q_res_size = db_size;
err = -ENOMEM;
aq->q_req_buf = kzalloc(q_size, GFP_KERNEL);
if (!aq->q_req_buf)
goto err;
aq->q_res_buf = kzalloc(db_size, GFP_KERNEL);
if (!aq->q_res_buf)
goto err;
/* Before we set up the AQ, the device remains in an inactive state, so
* there will be no DMA operations. If the 'set up AQ' process fails, we
* can safely free the DMA-related memory.
*/
err = eea_pci_set_aq_up(enet->edev);
if (err)
goto err;
aq->broken = false;
mutex_init(&aq->lock);
return 0;
err:
eea_destroy_adminq(enet);
return err;
}
int eea_adminq_query_cfg(struct eea_net *enet, struct eea_aq_cfg *cfg)
{
return eea_adminq_exec(enet, EEA_AQ_CMD_CFG_QUERY, NULL, 0, cfg,
sizeof(*cfg), NULL);
}
static void qcfg_fill(struct eea_aq_create *qcfg, struct eea_ring *ering,
u32 flags)
{
qcfg->flags = cpu_to_le32(flags);
qcfg->qidx = cpu_to_le16(ering->index);
qcfg->depth = cpu_to_le16(ering->num);
qcfg->hdr_buf_size = flags & EEA_QUEUE_FLAGS_HW_SPLIT_HDR ? 1 : 0;
qcfg->sq_desc_size = ering->sq.desc_size;
qcfg->cq_desc_size = ering->cq.desc_size;
qcfg->msix_vector = cpu_to_le16(ering->msix_vec);
qcfg->sq_addr_low = cpu_to_le32(lower_32_bits(ering->sq.dma_addr));
qcfg->sq_addr_high = cpu_to_le32(upper_32_bits(ering->sq.dma_addr));
qcfg->cq_addr_low = cpu_to_le32(lower_32_bits(ering->cq.dma_addr));
qcfg->cq_addr_high = cpu_to_le32(upper_32_bits(ering->cq.dma_addr));
}
int eea_adminq_create_q(struct eea_net *enet, u32 num, u32 flags)
{
int i, db_size, q_size, err = -ENOMEM;
struct eea_net_cfg *cfg;
struct eea_ring *ering;
struct eea_aq *aq;
u32 reply_len;
cfg = &enet->cfg;
aq = &enet->adminq;
if (cfg->split_hdr)
flags |= EEA_QUEUE_FLAGS_HW_SPLIT_HDR;
flags |= EEA_QUEUE_FLAGS_SQCQ;
flags |= EEA_QUEUE_FLAGS_HWTS;
q_size = sizeof(*aq->q_req_buf) * num;
db_size = sizeof(*aq->q_res_buf) * num;
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
qcfg_fill(aq->q_req_buf + i, ering, flags);
}
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_CREATE,
aq->q_req_buf, q_size,
aq->q_res_buf, db_size,
&reply_len);
if (err)
return err;
if (reply_len != db_size) {
eea_adminq_destroy_all_q(enet);
netdev_err(enet->netdev, "invalid reply len %u\n", reply_len);
return -EINVAL;
}
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = eea_pci_db_addr(ering->edev,
le32_to_cpu(aq->q_res_buf[i]));
if (!ering->db) {
netdev_err(enet->netdev, "invalid db off %u\n",
le32_to_cpu(aq->q_res_buf[i]));
goto err;
}
}
return err;
err:
eea_adminq_destroy_all_q(enet);
for (i = 0; i < num; i++) {
ering = qid_to_ering(enet, i);
ering->db = NULL;
}
return -EIO;
}
int eea_adminq_destroy_all_q(struct eea_net *enet)
{
int err;
err = eea_adminq_exec(enet, EEA_AQ_CMD_QUEUE_DESTROY_ALL, NULL, 0,
NULL, 0, NULL);
if (err) {
/* The device must be reset before unmapping buffers to avoid
* potential DMA writes after the memory is freed.
*/
mutex_lock(&enet->adminq.lock);
eea_device_broken(enet);
mutex_unlock(&enet->adminq.lock);
netdev_err(enet->netdev, "QUEUE_DESTROY fail: reset device.\n");
}
return err;
}
/* The caller must ensure that both the 'rx' and 'tx' arrays are valid. */
int eea_adminq_dev_status(struct eea_net *enet,
struct eea_aq_dev_status *dstatus)
{
struct eea_aq_queue_drv_status *drv_status;
struct __eea_aq_dev_status *dev_status;
int err, i, io_num, size, q_num;
struct eea_ring *ering;
void *rep, *req;
q_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num + 1;
io_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num;
req = kcalloc(q_num, sizeof(struct eea_aq_queue_drv_status),
GFP_KERNEL);
if (!req)
return -ENOMEM;
size = struct_size(dev_status, q_status, q_num);
rep = kzalloc(size, GFP_KERNEL);
if (!rep) {
kfree(req);
return -ENOMEM;
}
drv_status = req;
for (i = 0; i < io_num; ++i, ++drv_status) {
ering = qid_to_ering(enet, i);
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(ering->cq.head);
drv_status->sq_head = cpu_to_le16(ering->sq.head);
}
drv_status->qidx = cpu_to_le16(i);
drv_status->cq_head = cpu_to_le16(enet->adminq.ring->cq.head);
drv_status->sq_head = cpu_to_le16(enet->adminq.ring->sq.head);
err = eea_adminq_exec(enet, EEA_AQ_CMD_DEV_STATUS, req,
q_num * sizeof(struct eea_aq_queue_drv_status),
rep, size, NULL);
kfree(req);
if (err) {
kfree(rep);
return err;
}
dstatus->num = q_num;
dstatus->status = rep;
return 0;
}
void eea_adminq_config_host_info(struct eea_net *enet)
{
struct device *dev = enet->edev->dma_dev;
struct eea_aq_host_info_cfg *cfg;
struct eea_aq_host_info_rep *rep;
int rc = -ENOMEM;
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg)
return;
rep = kzalloc(sizeof(*rep), GFP_KERNEL);
if (!rep)
goto err_free_cfg;
cfg->os_type = cpu_to_le16(EEA_OS_LINUX);
cfg->os_dist = cpu_to_le16(EEA_OS_DISTRO);
cfg->drv_type = cpu_to_le16(EEA_DRV_TYPE);
cfg->kern_ver_major = cpu_to_le16(LINUX_VERSION_MAJOR);
cfg->kern_ver_minor = cpu_to_le16(LINUX_VERSION_PATCHLEVEL);
cfg->kern_ver_sub_minor = cpu_to_le16(LINUX_VERSION_SUBLEVEL);
cfg->drv_ver_major = cpu_to_le16(EEA_VER_MAJOR);
cfg->drv_ver_minor = cpu_to_le16(EEA_VER_MINOR);
cfg->drv_ver_sub_minor = cpu_to_le16(EEA_VER_SUB_MINOR);
cfg->spec_ver_major = cpu_to_le16(EEA_SPEC_VER_MAJOR);
cfg->spec_ver_minor = cpu_to_le16(EEA_SPEC_VER_MINOR);
cfg->pci_bdf = cpu_to_le16(eea_pci_bdf(enet->edev));
cfg->pci_domain = cpu_to_le32(eea_pci_domain_nr(enet->edev));
strscpy(cfg->os_ver_str, utsname()->release, sizeof(cfg->os_ver_str));
strscpy(cfg->isa_str, utsname()->machine, sizeof(cfg->isa_str));
rc = eea_adminq_exec(enet, EEA_AQ_CMD_HOST_INFO,
cfg, sizeof(*cfg), rep, sizeof(*rep), NULL);
if (!rc) {
if (rep->op_code == EEA_HINFO_REP_BAD)
dev_warn(dev, "The hardware-driven state validation may be abnormal.\n");
if (rep->has_reply) {
char buf[EEA_HINFO_MAX_REP_LEN] = {0};
rep->reply_str[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
string_escape_str(rep->reply_str, buf, sizeof(buf),
ESCAPE_NP, NULL);
buf[EEA_HINFO_MAX_REP_LEN - 1] = '\0';
dev_warn(dev, "Device replied: %s\n", buf);
}
}
kfree(rep);
err_free_cfg:
kfree(cfg);
}