root/drivers/net/ethernet/broadcom/bnge/bnge_auxr.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2025 Broadcom.

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/bitops.h>
#include <linux/irq.h>
#include <asm/byteorder.h>
#include <linux/bitmap.h>
#include <linux/auxiliary_bus.h>
#include <linux/bnge/hsi.h>

#include "bnge.h"
#include "bnge_hwrm.h"
#include "bnge_auxr.h"

static DEFINE_IDA(bnge_aux_dev_ids);

static void bnge_fill_msix_vecs(struct bnge_dev *bd,
                                struct bnge_msix_info *info)
{
        struct bnge_auxr_dev *auxr_dev = bd->auxr_dev;
        int num_msix, i;

        if (!auxr_dev->auxr_info->msix_requested) {
                dev_warn(bd->dev, "Requested MSI-X vectors not allocated\n");
                return;
        }
        num_msix = auxr_dev->auxr_info->msix_requested;
        for (i = 0; i < num_msix; i++) {
                info[i].vector = bd->irq_tbl[i].vector;
                info[i].db_offset = bd->db_offset;
                info[i].ring_idx = i;
        }
}

int bnge_register_dev(struct bnge_auxr_dev *auxr_dev,
                      void *handle)
{
        struct bnge_dev *bd = pci_get_drvdata(auxr_dev->pdev);
        struct bnge_auxr_info *auxr_info;
        int rc = 0;

        netdev_lock(bd->netdev);
        mutex_lock(&auxr_dev->auxr_dev_lock);
        if (!bd->irq_tbl) {
                rc = -ENODEV;
                goto exit;
        }

        if (!bnge_aux_has_enough_resources(bd)) {
                rc = -ENOMEM;
                goto exit;
        }

        auxr_info = auxr_dev->auxr_info;
        auxr_info->handle = handle;

        auxr_info->msix_requested = bd->aux_num_msix;

        bnge_fill_msix_vecs(bd, bd->auxr_dev->msix_info);
        auxr_dev->flags |= BNGE_ARDEV_MSIX_ALLOC;

exit:
        mutex_unlock(&auxr_dev->auxr_dev_lock);
        netdev_unlock(bd->netdev);
        return rc;
}
EXPORT_SYMBOL(bnge_register_dev);

void bnge_unregister_dev(struct bnge_auxr_dev *auxr_dev)
{
        struct bnge_dev *bd = pci_get_drvdata(auxr_dev->pdev);
        struct bnge_auxr_info *auxr_info;

        auxr_info = auxr_dev->auxr_info;
        netdev_lock(bd->netdev);
        mutex_lock(&auxr_dev->auxr_dev_lock);
        if (auxr_info->msix_requested)
                auxr_dev->flags &= ~BNGE_ARDEV_MSIX_ALLOC;
        auxr_info->msix_requested = 0;

        mutex_unlock(&auxr_dev->auxr_dev_lock);
        netdev_unlock(bd->netdev);
}
EXPORT_SYMBOL(bnge_unregister_dev);

int bnge_send_msg(struct bnge_auxr_dev *auxr_dev, struct bnge_fw_msg *fw_msg)
{
        struct bnge_dev *bd = pci_get_drvdata(auxr_dev->pdev);
        struct output *resp;
        struct input *req;
        u32 resp_len;
        int rc;

        rc = bnge_hwrm_req_init(bd, req, 0 /* don't care */);
        if (rc)
                return rc;

        rc = bnge_hwrm_req_replace(bd, req, fw_msg->msg, fw_msg->msg_len);
        if (rc)
                goto drop_req;

        bnge_hwrm_req_timeout(bd, req, fw_msg->timeout);
        resp = bnge_hwrm_req_hold(bd, req);
        rc = bnge_hwrm_req_send(bd, req);
        resp_len = le16_to_cpu(resp->resp_len);
        if (resp_len) {
                if (fw_msg->resp_max_len < resp_len)
                        resp_len = fw_msg->resp_max_len;

                memcpy(fw_msg->resp, resp, resp_len);
        }
drop_req:
        bnge_hwrm_req_drop(bd, req);
        return rc;
}
EXPORT_SYMBOL(bnge_send_msg);

void bnge_rdma_aux_device_uninit(struct bnge_dev *bd)
{
        struct bnge_auxr_priv *aux_priv;
        struct auxiliary_device *adev;

        /* Skip if no auxiliary device init was done. */
        if (!bd->aux_priv)
                return;

        aux_priv = bd->aux_priv;
        adev = &aux_priv->aux_dev;
        auxiliary_device_uninit(adev);
}

static void bnge_aux_dev_release(struct device *dev)
{
        struct bnge_auxr_priv *aux_priv =
                        container_of(dev, struct bnge_auxr_priv, aux_dev.dev);
        struct bnge_dev *bd = pci_get_drvdata(aux_priv->auxr_dev->pdev);

        ida_free(&bnge_aux_dev_ids, aux_priv->id);
        kfree(aux_priv->auxr_dev->auxr_info);
        bd->auxr_dev = NULL;
        kfree(aux_priv->auxr_dev);
        kfree(aux_priv);
        bd->aux_priv = NULL;
}

void bnge_rdma_aux_device_del(struct bnge_dev *bd)
{
        if (!bd->auxr_dev)
                return;

        auxiliary_device_delete(&bd->aux_priv->aux_dev);
}

static void bnge_set_auxr_dev_info(struct bnge_auxr_dev *auxr_dev,
                                   struct bnge_dev *bd)
{
        auxr_dev->pdev = bd->pdev;
        auxr_dev->l2_db_size = bd->db_size;
        auxr_dev->l2_db_size_nc = bd->db_size;
        auxr_dev->l2_db_offset = bd->db_offset;
        mutex_init(&auxr_dev->auxr_dev_lock);

        if (bd->flags & BNGE_EN_ROCE_V1)
                auxr_dev->flags |= BNGE_ARDEV_ROCEV1_SUPP;
        if (bd->flags & BNGE_EN_ROCE_V2)
                auxr_dev->flags |= BNGE_ARDEV_ROCEV2_SUPP;

        auxr_dev->chip_num = bd->chip_num;
        auxr_dev->hw_ring_stats_size = bd->hw_ring_stats_size;
        auxr_dev->pf_port_id = bd->pf.port_id;
        auxr_dev->en_state = bd->state;
        auxr_dev->bar0 = bd->bar0;
}

void bnge_rdma_aux_device_add(struct bnge_dev *bd)
{
        struct auxiliary_device *aux_dev;
        int rc;

        if (!bd->auxr_dev)
                return;

        aux_dev = &bd->aux_priv->aux_dev;
        rc = auxiliary_device_add(aux_dev);
        if (rc) {
                dev_warn(bd->dev, "Failed to add auxiliary device for ROCE\n");
                auxiliary_device_uninit(aux_dev);
                bd->flags &= ~BNGE_EN_ROCE;
        }

        bd->auxr_dev->net = bd->netdev;
}

void bnge_rdma_aux_device_init(struct bnge_dev *bd)
{
        struct auxiliary_device *aux_dev;
        struct bnge_auxr_info *auxr_info;
        struct bnge_auxr_priv *aux_priv;
        struct bnge_auxr_dev *auxr_dev;
        int rc;

        if (!bnge_is_roce_en(bd))
                return;

        aux_priv = kzalloc_obj(*aux_priv);
        if (!aux_priv)
                goto exit;

        aux_priv->id = ida_alloc(&bnge_aux_dev_ids, GFP_KERNEL);
        if (aux_priv->id < 0) {
                dev_warn(bd->dev, "ida alloc failed for aux device\n");
                kfree(aux_priv);
                goto exit;
        }

        aux_dev = &aux_priv->aux_dev;
        aux_dev->id = aux_priv->id;
        aux_dev->name = "rdma";
        aux_dev->dev.parent = &bd->pdev->dev;
        aux_dev->dev.release = bnge_aux_dev_release;

        rc = auxiliary_device_init(aux_dev);
        if (rc) {
                ida_free(&bnge_aux_dev_ids, aux_priv->id);
                kfree(aux_priv);
                goto exit;
        }
        bd->aux_priv = aux_priv;

        auxr_dev = kzalloc_obj(*auxr_dev);
        if (!auxr_dev)
                goto aux_dev_uninit;

        aux_priv->auxr_dev = auxr_dev;

        auxr_info = kzalloc_obj(*auxr_info);
        if (!auxr_info)
                goto aux_dev_uninit;

        auxr_dev->auxr_info = auxr_info;
        bd->auxr_dev = auxr_dev;
        bnge_set_auxr_dev_info(auxr_dev, bd);

        return;

aux_dev_uninit:
        auxiliary_device_uninit(aux_dev);
exit:
        bd->flags &= ~BNGE_EN_ROCE;
}