root/drivers/net/ethernet/alibaba/eea/eea_adminq.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);
}