root/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_macsec.c
// SPDX-License-Identifier: GPL-2.0
/* Marvell MACSEC hardware offload driver
 *
 * Copyright (C) 2022 Marvell.
 */

#include <crypto/skcipher.h>
#include <linux/rtnetlink.h>
#include <linux/bitfield.h>
#include "otx2_common.h"

#define MCS_TCAM0_MAC_DA_MASK           GENMASK_ULL(47, 0)
#define MCS_TCAM0_MAC_SA_MASK           GENMASK_ULL(63, 48)
#define MCS_TCAM1_MAC_SA_MASK           GENMASK_ULL(31, 0)
#define MCS_TCAM1_ETYPE_MASK            GENMASK_ULL(47, 32)

#define MCS_SA_MAP_MEM_SA_USE           BIT_ULL(9)

#define MCS_RX_SECY_PLCY_RW_MASK        GENMASK_ULL(49, 18)
#define MCS_RX_SECY_PLCY_RP             BIT_ULL(17)
#define MCS_RX_SECY_PLCY_AUTH_ENA       BIT_ULL(16)
#define MCS_RX_SECY_PLCY_CIP            GENMASK_ULL(8, 5)
#define MCS_RX_SECY_PLCY_VAL            GENMASK_ULL(2, 1)
#define MCS_RX_SECY_PLCY_ENA            BIT_ULL(0)

#define MCS_TX_SECY_PLCY_MTU            GENMASK_ULL(43, 28)
#define MCS_TX_SECY_PLCY_ST_TCI         GENMASK_ULL(27, 22)
#define MCS_TX_SECY_PLCY_ST_OFFSET      GENMASK_ULL(21, 15)
#define MCS_TX_SECY_PLCY_INS_MODE       BIT_ULL(14)
#define MCS_TX_SECY_PLCY_AUTH_ENA       BIT_ULL(13)
#define MCS_TX_SECY_PLCY_CIP            GENMASK_ULL(5, 2)
#define MCS_TX_SECY_PLCY_PROTECT        BIT_ULL(1)
#define MCS_TX_SECY_PLCY_ENA            BIT_ULL(0)

#define MCS_GCM_AES_128                 0
#define MCS_GCM_AES_256                 1
#define MCS_GCM_AES_XPN_128             2
#define MCS_GCM_AES_XPN_256             3

#define MCS_TCI_ES                      0x40 /* end station */
#define MCS_TCI_SC                      0x20 /* SCI present */
#define MCS_TCI_SCB                     0x10 /* epon */
#define MCS_TCI_E                       0x08 /* encryption */
#define MCS_TCI_C                       0x04 /* changed text */

#define CN10K_MAX_HASH_LEN              16
#define CN10K_MAX_SAK_LEN               32

static int cn10k_ecb_aes_encrypt(struct otx2_nic *pfvf, u8 *sak,
                                 u16 sak_len, u8 *hash)
{
        u8 data[CN10K_MAX_HASH_LEN] = { 0 };
        struct skcipher_request *req = NULL;
        struct scatterlist sg_src, sg_dst;
        struct crypto_skcipher *tfm;
        DECLARE_CRYPTO_WAIT(wait);
        int err;

        tfm = crypto_alloc_skcipher("ecb(aes)", 0, 0);
        if (IS_ERR(tfm)) {
                dev_err(pfvf->dev, "failed to allocate transform for ecb-aes\n");
                return PTR_ERR(tfm);
        }

        req = skcipher_request_alloc(tfm, GFP_KERNEL);
        if (!req) {
                dev_err(pfvf->dev, "failed to allocate request for skcipher\n");
                err = -ENOMEM;
                goto free_tfm;
        }

        err = crypto_skcipher_setkey(tfm, sak, sak_len);
        if (err) {
                dev_err(pfvf->dev, "failed to set key for skcipher\n");
                goto free_req;
        }

        /* build sg list */
        sg_init_one(&sg_src, data, CN10K_MAX_HASH_LEN);
        sg_init_one(&sg_dst, hash, CN10K_MAX_HASH_LEN);

        skcipher_request_set_callback(req, 0, crypto_req_done, &wait);
        skcipher_request_set_crypt(req, &sg_src, &sg_dst,
                                   CN10K_MAX_HASH_LEN, NULL);

        err = crypto_skcipher_encrypt(req);
        err = crypto_wait_req(err, &wait);

free_req:
        skcipher_request_free(req);
free_tfm:
        crypto_free_skcipher(tfm);
        return err;
}

static struct cn10k_mcs_txsc *cn10k_mcs_get_txsc(struct cn10k_mcs_cfg *cfg,
                                                 struct macsec_secy *secy)
{
        struct cn10k_mcs_txsc *txsc;

        list_for_each_entry(txsc, &cfg->txsc_list, entry) {
                if (txsc->sw_secy == secy)
                        return txsc;
        }

        return NULL;
}

static struct cn10k_mcs_rxsc *cn10k_mcs_get_rxsc(struct cn10k_mcs_cfg *cfg,
                                                 struct macsec_secy *secy,
                                                 struct macsec_rx_sc *rx_sc)
{
        struct cn10k_mcs_rxsc *rxsc;

        list_for_each_entry(rxsc, &cfg->rxsc_list, entry) {
                if (rxsc->sw_rxsc == rx_sc && rxsc->sw_secy == secy)
                        return rxsc;
        }

        return NULL;
}

static const char *rsrc_name(enum mcs_rsrc_type rsrc_type)
{
        switch (rsrc_type) {
        case MCS_RSRC_TYPE_FLOWID:
                return "FLOW";
        case MCS_RSRC_TYPE_SC:
                return "SC";
        case MCS_RSRC_TYPE_SECY:
                return "SECY";
        case MCS_RSRC_TYPE_SA:
                return "SA";
        default:
                return "Unknown";
        }
}

static int cn10k_mcs_alloc_rsrc(struct otx2_nic *pfvf, enum mcs_direction dir,
                                enum mcs_rsrc_type type, u16 *rsrc_id)
{
        struct mbox *mbox = &pfvf->mbox;
        struct mcs_alloc_rsrc_req *req;
        struct mcs_alloc_rsrc_rsp *rsp;
        int ret = -ENOMEM;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_alloc_resources(mbox);
        if (!req)
                goto fail;

        req->rsrc_type = type;
        req->rsrc_cnt  = 1;
        req->dir = dir;

        ret = otx2_sync_mbox_msg(mbox);
        if (ret)
                goto fail;

        rsp = (struct mcs_alloc_rsrc_rsp *)otx2_mbox_get_rsp(&pfvf->mbox.mbox,
                                                             0, &req->hdr);
        if (IS_ERR(rsp) || req->rsrc_cnt != rsp->rsrc_cnt ||
            req->rsrc_type != rsp->rsrc_type || req->dir != rsp->dir) {
                ret = -EINVAL;
                goto fail;
        }

        switch (rsp->rsrc_type) {
        case MCS_RSRC_TYPE_FLOWID:
                *rsrc_id = rsp->flow_ids[0];
                break;
        case MCS_RSRC_TYPE_SC:
                *rsrc_id = rsp->sc_ids[0];
                break;
        case MCS_RSRC_TYPE_SECY:
                *rsrc_id = rsp->secy_ids[0];
                break;
        case MCS_RSRC_TYPE_SA:
                *rsrc_id = rsp->sa_ids[0];
                break;
        default:
                ret = -EINVAL;
                goto fail;
        }

        mutex_unlock(&mbox->lock);

        return 0;
fail:
        dev_err(pfvf->dev, "Failed to allocate %s %s resource\n",
                dir == MCS_TX ? "TX" : "RX", rsrc_name(type));
        mutex_unlock(&mbox->lock);
        return ret;
}

static void cn10k_mcs_free_rsrc(struct otx2_nic *pfvf, enum mcs_direction dir,
                                enum mcs_rsrc_type type, u16 hw_rsrc_id,
                                bool all)
{
        struct mcs_clear_stats *clear_req;
        struct mbox *mbox = &pfvf->mbox;
        struct mcs_free_rsrc_req *req;

        mutex_lock(&mbox->lock);

        clear_req = otx2_mbox_alloc_msg_mcs_clear_stats(mbox);
        if (!clear_req)
                goto fail;

        clear_req->id = hw_rsrc_id;
        clear_req->type = type;
        clear_req->dir = dir;

        req = otx2_mbox_alloc_msg_mcs_free_resources(mbox);
        if (!req)
                goto fail;

        req->rsrc_id = hw_rsrc_id;
        req->rsrc_type = type;
        req->dir = dir;
        if (all)
                req->all = 1;

        if (otx2_sync_mbox_msg(&pfvf->mbox))
                goto fail;

        mutex_unlock(&mbox->lock);

        return;
fail:
        dev_err(pfvf->dev, "Failed to free %s %s resource\n",
                dir == MCS_TX ? "TX" : "RX", rsrc_name(type));
        mutex_unlock(&mbox->lock);
}

static int cn10k_mcs_alloc_txsa(struct otx2_nic *pfvf, u16 *hw_sa_id)
{
        return cn10k_mcs_alloc_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SA, hw_sa_id);
}

static int cn10k_mcs_alloc_rxsa(struct otx2_nic *pfvf, u16 *hw_sa_id)
{
        return cn10k_mcs_alloc_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SA, hw_sa_id);
}

static void cn10k_mcs_free_txsa(struct otx2_nic *pfvf, u16 hw_sa_id)
{
        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SA, hw_sa_id, false);
}

static void cn10k_mcs_free_rxsa(struct otx2_nic *pfvf, u16 hw_sa_id)
{
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SA, hw_sa_id, false);
}

static int cn10k_mcs_write_rx_secy(struct otx2_nic *pfvf,
                                   struct macsec_secy *secy, u8 hw_secy_id)
{
        struct mcs_secy_plcy_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        u64 policy;
        u8 cipher;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_secy_plcy_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        policy = FIELD_PREP(MCS_RX_SECY_PLCY_RW_MASK, secy->replay_window);
        if (secy->replay_protect)
                policy |= MCS_RX_SECY_PLCY_RP;

        policy |= MCS_RX_SECY_PLCY_AUTH_ENA;

        switch (secy->key_len) {
        case 16:
                cipher = secy->xpn ? MCS_GCM_AES_XPN_128 : MCS_GCM_AES_128;
                break;
        case 32:
                cipher = secy->xpn ? MCS_GCM_AES_XPN_256 : MCS_GCM_AES_256;
                break;
        default:
                cipher = MCS_GCM_AES_128;
                dev_warn(pfvf->dev, "Unsupported key length\n");
                break;
        }

        policy |= FIELD_PREP(MCS_RX_SECY_PLCY_CIP, cipher);
        policy |= FIELD_PREP(MCS_RX_SECY_PLCY_VAL, secy->validate_frames);

        policy |= MCS_RX_SECY_PLCY_ENA;

        req->plcy = policy;
        req->secy_id = hw_secy_id;
        req->dir = MCS_RX;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_rx_flowid(struct otx2_nic *pfvf,
                                     struct cn10k_mcs_rxsc *rxsc, u8 hw_secy_id)
{
        struct macsec_rx_sc *sw_rx_sc = rxsc->sw_rxsc;
        struct macsec_secy *secy = rxsc->sw_secy;
        struct mcs_flowid_entry_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        u64 mac_da;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_flowid_entry_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        mac_da = ether_addr_to_u64(secy->netdev->dev_addr);

        req->data[0] = FIELD_PREP(MCS_TCAM0_MAC_DA_MASK, mac_da);
        req->mask[0] = ~0ULL;
        req->mask[0] &= ~MCS_TCAM0_MAC_DA_MASK;

        req->data[1] = FIELD_PREP(MCS_TCAM1_ETYPE_MASK, ETH_P_MACSEC);
        req->mask[1] = ~0ULL;
        req->mask[1] &= ~MCS_TCAM1_ETYPE_MASK;

        req->mask[2] = ~0ULL;
        req->mask[3] = ~0ULL;

        req->flow_id = rxsc->hw_flow_id;
        req->secy_id = hw_secy_id;
        req->sc_id = rxsc->hw_sc_id;
        req->dir = MCS_RX;

        if (sw_rx_sc->active)
                req->ena = 1;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_sc_cam(struct otx2_nic *pfvf,
                                  struct cn10k_mcs_rxsc *rxsc, u8 hw_secy_id)
{
        struct macsec_rx_sc *sw_rx_sc = rxsc->sw_rxsc;
        struct mcs_rx_sc_cam_write_req *sc_req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        sc_req = otx2_mbox_alloc_msg_mcs_rx_sc_cam_write(mbox);
        if (!sc_req) {
                ret = -ENOMEM;
                goto fail;
        }

        sc_req->sci = (__force u64)cpu_to_be64((__force u64)sw_rx_sc->sci);
        sc_req->sc_id = rxsc->hw_sc_id;
        sc_req->secy_id = hw_secy_id;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_keys(struct otx2_nic *pfvf,
                                struct macsec_secy *secy,
                                struct mcs_sa_plcy_write_req *req,
                                u8 *sak, u8 *salt, ssci_t ssci)
{
        u8 hash_rev[CN10K_MAX_HASH_LEN];
        u8 sak_rev[CN10K_MAX_SAK_LEN];
        u8 salt_rev[MACSEC_SALT_LEN];
        u8 hash[CN10K_MAX_HASH_LEN];
        u32 ssci_63_32;
        int err, i;

        err = cn10k_ecb_aes_encrypt(pfvf, sak, secy->key_len, hash);
        if (err) {
                dev_err(pfvf->dev, "Generating hash using ECB(AES) failed\n");
                return err;
        }

        for (i = 0; i < secy->key_len; i++)
                sak_rev[i] = sak[secy->key_len - 1 - i];

        for (i = 0; i < CN10K_MAX_HASH_LEN; i++)
                hash_rev[i] = hash[CN10K_MAX_HASH_LEN - 1 - i];

        for (i = 0; i < MACSEC_SALT_LEN; i++)
                salt_rev[i] = salt[MACSEC_SALT_LEN - 1 - i];

        ssci_63_32 = (__force u32)cpu_to_be32((__force u32)ssci);

        memcpy(&req->plcy[0][0], sak_rev, secy->key_len);
        memcpy(&req->plcy[0][4], hash_rev, CN10K_MAX_HASH_LEN);
        memcpy(&req->plcy[0][6], salt_rev, MACSEC_SALT_LEN);
        req->plcy[0][7] |= (u64)ssci_63_32 << 32;

        return 0;
}

static int cn10k_mcs_write_rx_sa_plcy(struct otx2_nic *pfvf,
                                      struct macsec_secy *secy,
                                      struct cn10k_mcs_rxsc *rxsc,
                                      u8 assoc_num, bool sa_in_use)
{
        struct mcs_sa_plcy_write_req *plcy_req;
        u8 *sak = rxsc->sa_key[assoc_num];
        u8 *salt = rxsc->salt[assoc_num];
        struct mcs_rx_sc_sa_map *map_req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        plcy_req = otx2_mbox_alloc_msg_mcs_sa_plcy_write(mbox);
        if (!plcy_req) {
                ret = -ENOMEM;
                goto fail;
        }

        map_req = otx2_mbox_alloc_msg_mcs_rx_sc_sa_map_write(mbox);
        if (!map_req) {
                otx2_mbox_reset(&mbox->mbox, 0);
                ret = -ENOMEM;
                goto fail;
        }

        ret = cn10k_mcs_write_keys(pfvf, secy, plcy_req, sak,
                                   salt, rxsc->ssci[assoc_num]);
        if (ret)
                goto fail;

        plcy_req->sa_index[0] = rxsc->hw_sa_id[assoc_num];
        plcy_req->sa_cnt = 1;
        plcy_req->dir = MCS_RX;

        map_req->sa_index = rxsc->hw_sa_id[assoc_num];
        map_req->sa_in_use = sa_in_use;
        map_req->sc_id = rxsc->hw_sc_id;
        map_req->an = assoc_num;

        /* Send two messages together */
        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_rx_sa_pn(struct otx2_nic *pfvf,
                                    struct cn10k_mcs_rxsc *rxsc,
                                    u8 assoc_num, u64 next_pn)
{
        struct mcs_pn_table_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_pn_table_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->pn_id = rxsc->hw_sa_id[assoc_num];
        req->next_pn = next_pn;
        req->dir = MCS_RX;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_tx_secy(struct otx2_nic *pfvf,
                                   struct macsec_secy *secy,
                                   struct cn10k_mcs_txsc *txsc)
{
        struct mcs_secy_plcy_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        struct macsec_tx_sc *sw_tx_sc;
        u8 sectag_tci = 0;
        u8 tag_offset;
        u64 policy;
        u8 cipher;
        int ret;

        /* Insert SecTag after 12 bytes (DA+SA) or 16 bytes
         * if VLAN tag needs to be sent in clear text.
         */
        tag_offset = txsc->vlan_dev ? 16 : 12;
        sw_tx_sc = &secy->tx_sc;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_secy_plcy_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        if (sw_tx_sc->send_sci) {
                sectag_tci |= MCS_TCI_SC;
        } else {
                if (sw_tx_sc->end_station)
                        sectag_tci |= MCS_TCI_ES;
                if (sw_tx_sc->scb)
                        sectag_tci |= MCS_TCI_SCB;
        }

        if (sw_tx_sc->encrypt)
                sectag_tci |= (MCS_TCI_E | MCS_TCI_C);

        policy = FIELD_PREP(MCS_TX_SECY_PLCY_MTU,
                            pfvf->netdev->mtu + OTX2_ETH_HLEN);
        /* Write SecTag excluding AN bits(1..0) */
        policy |= FIELD_PREP(MCS_TX_SECY_PLCY_ST_TCI, sectag_tci >> 2);
        policy |= FIELD_PREP(MCS_TX_SECY_PLCY_ST_OFFSET, tag_offset);
        policy |= MCS_TX_SECY_PLCY_INS_MODE;
        policy |= MCS_TX_SECY_PLCY_AUTH_ENA;

        switch (secy->key_len) {
        case 16:
                cipher = secy->xpn ? MCS_GCM_AES_XPN_128 : MCS_GCM_AES_128;
                break;
        case 32:
                cipher = secy->xpn ? MCS_GCM_AES_XPN_256 : MCS_GCM_AES_256;
                break;
        default:
                cipher = MCS_GCM_AES_128;
                dev_warn(pfvf->dev, "Unsupported key length\n");
                break;
        }

        policy |= FIELD_PREP(MCS_TX_SECY_PLCY_CIP, cipher);

        if (secy->protect_frames)
                policy |= MCS_TX_SECY_PLCY_PROTECT;

        /* If the encodingsa does not exist/active and protect is
         * not set then frames can be sent out as it is. Hence enable
         * the policy irrespective of secy operational when !protect.
         */
        if (!secy->protect_frames || secy->operational)
                policy |= MCS_TX_SECY_PLCY_ENA;

        req->plcy = policy;
        req->secy_id = txsc->hw_secy_id_tx;
        req->dir = MCS_TX;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_tx_flowid(struct otx2_nic *pfvf,
                                     struct macsec_secy *secy,
                                     struct cn10k_mcs_txsc *txsc)
{
        struct mcs_flowid_entry_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        u64 mac_sa;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_flowid_entry_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        mac_sa = ether_addr_to_u64(secy->netdev->dev_addr);

        req->data[0] = FIELD_PREP(MCS_TCAM0_MAC_SA_MASK, mac_sa);
        req->data[1] = FIELD_PREP(MCS_TCAM1_MAC_SA_MASK, mac_sa >> 16);

        req->mask[0] = ~0ULL;
        req->mask[0] &= ~MCS_TCAM0_MAC_SA_MASK;

        req->mask[1] = ~0ULL;
        req->mask[1] &= ~MCS_TCAM1_MAC_SA_MASK;

        req->mask[2] = ~0ULL;
        req->mask[3] = ~0ULL;

        req->flow_id = txsc->hw_flow_id;
        req->secy_id = txsc->hw_secy_id_tx;
        req->sc_id = txsc->hw_sc_id;
        req->sci = (__force u64)cpu_to_be64((__force u64)secy->sci);
        req->dir = MCS_TX;
        /* This can be enabled since stack xmits packets only when interface is up */
        req->ena = 1;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_link_tx_sa2sc(struct otx2_nic *pfvf,
                                   struct macsec_secy *secy,
                                   struct cn10k_mcs_txsc *txsc,
                                   u8 sa_num, bool sa_active)
{
        struct mcs_tx_sc_sa_map *map_req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        /* Link the encoding_sa only to SC out of all SAs */
        if (txsc->encoding_sa != sa_num)
                return 0;

        mutex_lock(&mbox->lock);

        map_req = otx2_mbox_alloc_msg_mcs_tx_sc_sa_map_write(mbox);
        if (!map_req) {
                otx2_mbox_reset(&mbox->mbox, 0);
                ret = -ENOMEM;
                goto fail;
        }

        map_req->sa_index0 = txsc->hw_sa_id[sa_num];
        map_req->sa_index0_vld = sa_active;
        map_req->sectag_sci = (__force u64)cpu_to_be64((__force u64)secy->sci);
        map_req->sc_id = txsc->hw_sc_id;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_write_tx_sa_plcy(struct otx2_nic *pfvf,
                                      struct macsec_secy *secy,
                                      struct cn10k_mcs_txsc *txsc,
                                      u8 assoc_num)
{
        struct mcs_sa_plcy_write_req *plcy_req;
        u8 *sak = txsc->sa_key[assoc_num];
        u8 *salt = txsc->salt[assoc_num];
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        plcy_req = otx2_mbox_alloc_msg_mcs_sa_plcy_write(mbox);
        if (!plcy_req) {
                ret = -ENOMEM;
                goto fail;
        }

        ret = cn10k_mcs_write_keys(pfvf, secy, plcy_req, sak,
                                   salt, txsc->ssci[assoc_num]);
        if (ret)
                goto fail;

        plcy_req->plcy[0][8] = assoc_num;
        plcy_req->sa_index[0] = txsc->hw_sa_id[assoc_num];
        plcy_req->sa_cnt = 1;
        plcy_req->dir = MCS_TX;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_write_tx_sa_pn(struct otx2_nic *pfvf,
                                struct cn10k_mcs_txsc *txsc,
                                u8 assoc_num, u64 next_pn)
{
        struct mcs_pn_table_write_req *req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_pn_table_write(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->pn_id = txsc->hw_sa_id[assoc_num];
        req->next_pn = next_pn;
        req->dir = MCS_TX;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_ena_dis_flowid(struct otx2_nic *pfvf, u16 hw_flow_id,
                                    bool enable, enum mcs_direction dir)
{
        struct mcs_flowid_ena_dis_entry *req;
        struct mbox *mbox = &pfvf->mbox;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_flowid_ena_entry(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->flow_id = hw_flow_id;
        req->ena = enable;
        req->dir = dir;

        ret = otx2_sync_mbox_msg(mbox);

fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_sa_stats(struct otx2_nic *pfvf, u8 hw_sa_id,
                              struct mcs_sa_stats *rsp_p,
                              enum mcs_direction dir, bool clear)
{
        struct mcs_clear_stats *clear_req;
        struct mbox *mbox = &pfvf->mbox;
        struct mcs_stats_req *req;
        struct mcs_sa_stats *rsp;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_get_sa_stats(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->id = hw_sa_id;
        req->dir = dir;

        if (!clear)
                goto send_msg;

        clear_req = otx2_mbox_alloc_msg_mcs_clear_stats(mbox);
        if (!clear_req) {
                ret = -ENOMEM;
                goto fail;
        }
        clear_req->id = hw_sa_id;
        clear_req->dir = dir;
        clear_req->type = MCS_RSRC_TYPE_SA;

send_msg:
        ret = otx2_sync_mbox_msg(mbox);
        if (ret)
                goto fail;

        rsp = (struct mcs_sa_stats *)otx2_mbox_get_rsp(&pfvf->mbox.mbox,
                                                       0, &req->hdr);
        if (IS_ERR(rsp)) {
                ret = PTR_ERR(rsp);
                goto fail;
        }

        memcpy(rsp_p, rsp, sizeof(*rsp_p));

        mutex_unlock(&mbox->lock);

        return 0;
fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_sc_stats(struct otx2_nic *pfvf, u8 hw_sc_id,
                              struct mcs_sc_stats *rsp_p,
                              enum mcs_direction dir, bool clear)
{
        struct mcs_clear_stats *clear_req;
        struct mbox *mbox = &pfvf->mbox;
        struct mcs_stats_req *req;
        struct mcs_sc_stats *rsp;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_get_sc_stats(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->id = hw_sc_id;
        req->dir = dir;

        if (!clear)
                goto send_msg;

        clear_req = otx2_mbox_alloc_msg_mcs_clear_stats(mbox);
        if (!clear_req) {
                ret = -ENOMEM;
                goto fail;
        }
        clear_req->id = hw_sc_id;
        clear_req->dir = dir;
        clear_req->type = MCS_RSRC_TYPE_SC;

send_msg:
        ret = otx2_sync_mbox_msg(mbox);
        if (ret)
                goto fail;

        rsp = (struct mcs_sc_stats *)otx2_mbox_get_rsp(&pfvf->mbox.mbox,
                                                       0, &req->hdr);
        if (IS_ERR(rsp)) {
                ret = PTR_ERR(rsp);
                goto fail;
        }

        memcpy(rsp_p, rsp, sizeof(*rsp_p));

        mutex_unlock(&mbox->lock);

        return 0;
fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static int cn10k_mcs_secy_stats(struct otx2_nic *pfvf, u8 hw_secy_id,
                                struct mcs_secy_stats *rsp_p,
                                enum mcs_direction dir, bool clear)
{
        struct mcs_clear_stats *clear_req;
        struct mbox *mbox = &pfvf->mbox;
        struct mcs_secy_stats *rsp;
        struct mcs_stats_req *req;
        int ret;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_get_secy_stats(mbox);
        if (!req) {
                ret = -ENOMEM;
                goto fail;
        }

        req->id = hw_secy_id;
        req->dir = dir;

        if (!clear)
                goto send_msg;

        clear_req = otx2_mbox_alloc_msg_mcs_clear_stats(mbox);
        if (!clear_req) {
                ret = -ENOMEM;
                goto fail;
        }
        clear_req->id = hw_secy_id;
        clear_req->dir = dir;
        clear_req->type = MCS_RSRC_TYPE_SECY;

send_msg:
        ret = otx2_sync_mbox_msg(mbox);
        if (ret)
                goto fail;

        rsp = (struct mcs_secy_stats *)otx2_mbox_get_rsp(&pfvf->mbox.mbox,
                                                         0, &req->hdr);
        if (IS_ERR(rsp)) {
                ret = PTR_ERR(rsp);
                goto fail;
        }

        memcpy(rsp_p, rsp, sizeof(*rsp_p));

        mutex_unlock(&mbox->lock);

        return 0;
fail:
        mutex_unlock(&mbox->lock);
        return ret;
}

static struct cn10k_mcs_txsc *cn10k_mcs_create_txsc(struct otx2_nic *pfvf)
{
        struct cn10k_mcs_txsc *txsc;
        int ret;

        txsc = kzalloc_obj(*txsc);
        if (!txsc)
                return ERR_PTR(-ENOMEM);

        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_FLOWID,
                                   &txsc->hw_flow_id);
        if (ret)
                goto fail;

        /* For a SecY, one TX secy and one RX secy HW resources are needed */
        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SECY,
                                   &txsc->hw_secy_id_tx);
        if (ret)
                goto free_flowid;

        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SECY,
                                   &txsc->hw_secy_id_rx);
        if (ret)
                goto free_tx_secy;

        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SC,
                                   &txsc->hw_sc_id);
        if (ret)
                goto free_rx_secy;

        return txsc;
free_rx_secy:
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SECY,
                            txsc->hw_secy_id_rx, false);
free_tx_secy:
        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SECY,
                            txsc->hw_secy_id_tx, false);
free_flowid:
        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_FLOWID,
                            txsc->hw_flow_id, false);
fail:
        kfree(txsc);
        return ERR_PTR(ret);
}

/* Free Tx SC and its SAs(if any) resources to AF
 */
static void cn10k_mcs_delete_txsc(struct otx2_nic *pfvf,
                                  struct cn10k_mcs_txsc *txsc)
{
        u8 sa_bmap = txsc->sa_bmap;
        u8 sa_num = 0;

        while (sa_bmap) {
                if (sa_bmap & 1) {
                        cn10k_mcs_write_tx_sa_plcy(pfvf, txsc->sw_secy,
                                                   txsc, sa_num);
                        cn10k_mcs_free_txsa(pfvf, txsc->hw_sa_id[sa_num]);
                }
                sa_num++;
                sa_bmap >>= 1;
        }

        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SC,
                            txsc->hw_sc_id, false);
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SECY,
                            txsc->hw_secy_id_rx, false);
        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SECY,
                            txsc->hw_secy_id_tx, false);
        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_FLOWID,
                            txsc->hw_flow_id, false);
}

static struct cn10k_mcs_rxsc *cn10k_mcs_create_rxsc(struct otx2_nic *pfvf)
{
        struct cn10k_mcs_rxsc *rxsc;
        int ret;

        rxsc = kzalloc_obj(*rxsc);
        if (!rxsc)
                return ERR_PTR(-ENOMEM);

        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_FLOWID,
                                   &rxsc->hw_flow_id);
        if (ret)
                goto fail;

        ret = cn10k_mcs_alloc_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SC,
                                   &rxsc->hw_sc_id);
        if (ret)
                goto free_flowid;

        return rxsc;
free_flowid:
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_FLOWID,
                            rxsc->hw_flow_id, false);
fail:
        kfree(rxsc);
        return ERR_PTR(ret);
}

/* Free Rx SC and its SAs(if any) resources to AF
 */
static void cn10k_mcs_delete_rxsc(struct otx2_nic *pfvf,
                                  struct cn10k_mcs_rxsc *rxsc)
{
        u8 sa_bmap = rxsc->sa_bmap;
        u8 sa_num = 0;

        while (sa_bmap) {
                if (sa_bmap & 1) {
                        cn10k_mcs_write_rx_sa_plcy(pfvf, rxsc->sw_secy, rxsc,
                                                   sa_num, false);
                        cn10k_mcs_free_rxsa(pfvf, rxsc->hw_sa_id[sa_num]);
                }
                sa_num++;
                sa_bmap >>= 1;
        }

        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SC,
                            rxsc->hw_sc_id, false);
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_FLOWID,
                            rxsc->hw_flow_id, false);
}

static int cn10k_mcs_secy_tx_cfg(struct otx2_nic *pfvf, struct macsec_secy *secy,
                                 struct cn10k_mcs_txsc *txsc,
                                 struct macsec_tx_sa *sw_tx_sa, u8 sa_num)
{
        if (sw_tx_sa) {
                cn10k_mcs_write_tx_sa_plcy(pfvf, secy, txsc, sa_num);
                cn10k_write_tx_sa_pn(pfvf, txsc, sa_num, sw_tx_sa->next_pn);
                cn10k_mcs_link_tx_sa2sc(pfvf, secy, txsc, sa_num,
                                        sw_tx_sa->active);
        }

        cn10k_mcs_write_tx_secy(pfvf, secy, txsc);
        cn10k_mcs_write_tx_flowid(pfvf, secy, txsc);
        /* When updating secy, change RX secy also */
        cn10k_mcs_write_rx_secy(pfvf, secy, txsc->hw_secy_id_rx);

        return 0;
}

static int cn10k_mcs_secy_rx_cfg(struct otx2_nic *pfvf,
                                 struct macsec_secy *secy, u8 hw_secy_id)
{
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct cn10k_mcs_rxsc *mcs_rx_sc;
        struct macsec_rx_sc *sw_rx_sc;
        struct macsec_rx_sa *sw_rx_sa;
        u8 sa_num;

        for (sw_rx_sc = rcu_dereference_bh(secy->rx_sc); sw_rx_sc && sw_rx_sc->active;
             sw_rx_sc = rcu_dereference_bh(sw_rx_sc->next)) {
                mcs_rx_sc = cn10k_mcs_get_rxsc(cfg, secy, sw_rx_sc);
                if (unlikely(!mcs_rx_sc))
                        continue;

                for (sa_num = 0; sa_num < CN10K_MCS_SA_PER_SC; sa_num++) {
                        sw_rx_sa = rcu_dereference_bh(sw_rx_sc->sa[sa_num]);
                        if (!sw_rx_sa)
                                continue;

                        cn10k_mcs_write_rx_sa_plcy(pfvf, secy, mcs_rx_sc,
                                                   sa_num, sw_rx_sa->active);
                        cn10k_mcs_write_rx_sa_pn(pfvf, mcs_rx_sc, sa_num,
                                                 sw_rx_sa->next_pn);
                }

                cn10k_mcs_write_rx_flowid(pfvf, mcs_rx_sc, hw_secy_id);
                cn10k_mcs_write_sc_cam(pfvf, mcs_rx_sc, hw_secy_id);
        }

        return 0;
}

static int cn10k_mcs_disable_rxscs(struct otx2_nic *pfvf,
                                   struct macsec_secy *secy,
                                   bool delete)
{
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct cn10k_mcs_rxsc *mcs_rx_sc;
        struct macsec_rx_sc *sw_rx_sc;
        int ret;

        for (sw_rx_sc = rcu_dereference_bh(secy->rx_sc); sw_rx_sc && sw_rx_sc->active;
             sw_rx_sc = rcu_dereference_bh(sw_rx_sc->next)) {
                mcs_rx_sc = cn10k_mcs_get_rxsc(cfg, secy, sw_rx_sc);
                if (unlikely(!mcs_rx_sc))
                        continue;

                ret = cn10k_mcs_ena_dis_flowid(pfvf, mcs_rx_sc->hw_flow_id,
                                               false, MCS_RX);
                if (ret)
                        dev_err(pfvf->dev, "Failed to disable TCAM for SC %d\n",
                                mcs_rx_sc->hw_sc_id);
                if (delete) {
                        cn10k_mcs_delete_rxsc(pfvf, mcs_rx_sc);
                        list_del(&mcs_rx_sc->entry);
                        kfree(mcs_rx_sc);
                }
        }

        return 0;
}

static void cn10k_mcs_sync_stats(struct otx2_nic *pfvf, struct macsec_secy *secy,
                                 struct cn10k_mcs_txsc *txsc)
{
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct mcs_secy_stats rx_rsp = { 0 };
        struct mcs_sc_stats sc_rsp = { 0 };
        struct cn10k_mcs_rxsc *rxsc;

        /* Because of shared counters for some stats in the hardware, when
         * updating secy policy take a snapshot of current stats and reset them.
         * Below are the effected stats because of shared counters.
         */

        /* Check if sync is really needed */
        if (secy->validate_frames == txsc->last_validate_frames &&
            secy->replay_protect == txsc->last_replay_protect)
                return;

        cn10k_mcs_secy_stats(pfvf, txsc->hw_secy_id_rx, &rx_rsp, MCS_RX, true);

        txsc->stats.InPktsBadTag += rx_rsp.pkt_badtag_cnt;
        txsc->stats.InPktsUnknownSCI += rx_rsp.pkt_nosa_cnt;
        txsc->stats.InPktsNoSCI += rx_rsp.pkt_nosaerror_cnt;
        if (txsc->last_validate_frames == MACSEC_VALIDATE_STRICT)
                txsc->stats.InPktsNoTag += rx_rsp.pkt_untaged_cnt;
        else
                txsc->stats.InPktsUntagged += rx_rsp.pkt_untaged_cnt;

        list_for_each_entry(rxsc, &cfg->rxsc_list, entry) {
                cn10k_mcs_sc_stats(pfvf, rxsc->hw_sc_id, &sc_rsp, MCS_RX, true);

                rxsc->stats.InOctetsValidated += sc_rsp.octet_validate_cnt;
                rxsc->stats.InOctetsDecrypted += sc_rsp.octet_decrypt_cnt;

                rxsc->stats.InPktsInvalid += sc_rsp.pkt_invalid_cnt;
                rxsc->stats.InPktsNotValid += sc_rsp.pkt_notvalid_cnt;

                if (txsc->last_replay_protect)
                        rxsc->stats.InPktsLate += sc_rsp.pkt_late_cnt;
                else
                        rxsc->stats.InPktsDelayed += sc_rsp.pkt_late_cnt;

                if (txsc->last_validate_frames == MACSEC_VALIDATE_DISABLED)
                        rxsc->stats.InPktsUnchecked += sc_rsp.pkt_unchecked_cnt;
                else
                        rxsc->stats.InPktsOK += sc_rsp.pkt_unchecked_cnt;
        }

        txsc->last_validate_frames = secy->validate_frames;
        txsc->last_replay_protect = secy->replay_protect;
}

static int cn10k_mdo_open(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct macsec_tx_sa *sw_tx_sa;
        struct cn10k_mcs_txsc *txsc;
        u8 sa_num;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        sa_num = txsc->encoding_sa;
        sw_tx_sa = rcu_dereference_bh(secy->tx_sc.sa[sa_num]);

        err = cn10k_mcs_secy_tx_cfg(pfvf, secy, txsc, sw_tx_sa, sa_num);
        if (err)
                return err;

        return cn10k_mcs_secy_rx_cfg(pfvf, secy, txsc->hw_secy_id_rx);
}

static int cn10k_mdo_stop(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct cn10k_mcs_txsc *txsc;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        err = cn10k_mcs_ena_dis_flowid(pfvf, txsc->hw_flow_id, false, MCS_TX);
        if (err)
                return err;

        return cn10k_mcs_disable_rxscs(pfvf, ctx->secy, false);
}

static int cn10k_mdo_add_secy(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct cn10k_mcs_txsc *txsc;

        if (secy->icv_len != MACSEC_DEFAULT_ICV_LEN)
                return -EOPNOTSUPP;

        txsc = cn10k_mcs_create_txsc(pfvf);
        if (IS_ERR(txsc))
                return -ENOSPC;

        txsc->sw_secy = secy;
        txsc->encoding_sa = secy->tx_sc.encoding_sa;
        txsc->last_validate_frames = secy->validate_frames;
        txsc->last_replay_protect = secy->replay_protect;
        txsc->vlan_dev = is_vlan_dev(ctx->netdev);

        list_add(&txsc->entry, &cfg->txsc_list);

        if (netif_running(secy->netdev))
                return cn10k_mcs_secy_tx_cfg(pfvf, secy, txsc, NULL, 0);

        return 0;
}

static int cn10k_mdo_upd_secy(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct macsec_tx_sa *sw_tx_sa;
        struct cn10k_mcs_txsc *txsc;
        bool active;
        u8 sa_num;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, secy);
        if (!txsc)
                return -ENOENT;

        /* Encoding SA got changed */
        if (txsc->encoding_sa != secy->tx_sc.encoding_sa) {
                txsc->encoding_sa = secy->tx_sc.encoding_sa;
                sa_num = txsc->encoding_sa;
                sw_tx_sa = rcu_dereference_bh(secy->tx_sc.sa[sa_num]);
                active = sw_tx_sa ? sw_tx_sa->active : false;
                cn10k_mcs_link_tx_sa2sc(pfvf, secy, txsc, sa_num, active);
        }

        if (netif_running(secy->netdev)) {
                cn10k_mcs_sync_stats(pfvf, secy, txsc);

                err = cn10k_mcs_secy_tx_cfg(pfvf, secy, txsc, NULL, 0);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_del_secy(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct cn10k_mcs_txsc *txsc;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        cn10k_mcs_ena_dis_flowid(pfvf, txsc->hw_flow_id, false, MCS_TX);
        cn10k_mcs_disable_rxscs(pfvf, ctx->secy, true);
        cn10k_mcs_delete_txsc(pfvf, txsc);
        list_del(&txsc->entry);
        kfree(txsc);

        return 0;
}

static int cn10k_mdo_add_txsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_tx_sa *sw_tx_sa = ctx->sa.tx_sa;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_txsc *txsc;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, secy);
        if (!txsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        if (cn10k_mcs_alloc_txsa(pfvf, &txsc->hw_sa_id[sa_num]))
                return -ENOSPC;

        memcpy(&txsc->sa_key[sa_num], ctx->sa.key, secy->key_len);
        memcpy(&txsc->salt[sa_num], sw_tx_sa->key.salt.bytes, MACSEC_SALT_LEN);
        txsc->ssci[sa_num] = sw_tx_sa->ssci;

        txsc->sa_bmap |= 1 << sa_num;

        if (netif_running(secy->netdev)) {
                err = cn10k_mcs_write_tx_sa_plcy(pfvf, secy, txsc, sa_num);
                if (err)
                        return err;

                err = cn10k_write_tx_sa_pn(pfvf, txsc, sa_num,
                                           sw_tx_sa->next_pn);
                if (err)
                        return err;

                err = cn10k_mcs_link_tx_sa2sc(pfvf, secy, txsc,
                                              sa_num, sw_tx_sa->active);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_upd_txsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_tx_sa *sw_tx_sa = ctx->sa.tx_sa;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_txsc *txsc;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, secy);
        if (!txsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        if (netif_running(secy->netdev)) {
                /* Keys cannot be changed after creation */
                if (ctx->sa.update_pn) {
                        err = cn10k_write_tx_sa_pn(pfvf, txsc, sa_num,
                                                   sw_tx_sa->next_pn);
                        if (err)
                                return err;
                }

                err = cn10k_mcs_link_tx_sa2sc(pfvf, secy, txsc,
                                              sa_num, sw_tx_sa->active);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_del_txsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_txsc *txsc;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        cn10k_mcs_free_txsa(pfvf, txsc->hw_sa_id[sa_num]);
        txsc->sa_bmap &= ~(1 << sa_num);

        return 0;
}

static int cn10k_mdo_add_rxsc(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct cn10k_mcs_rxsc *rxsc;
        struct cn10k_mcs_txsc *txsc;
        int err;

        txsc = cn10k_mcs_get_txsc(cfg, secy);
        if (!txsc)
                return -ENOENT;

        rxsc = cn10k_mcs_create_rxsc(pfvf);
        if (IS_ERR(rxsc))
                return -ENOSPC;

        rxsc->sw_secy = ctx->secy;
        rxsc->sw_rxsc = ctx->rx_sc;
        list_add(&rxsc->entry, &cfg->rxsc_list);

        if (netif_running(secy->netdev)) {
                err = cn10k_mcs_write_rx_flowid(pfvf, rxsc, txsc->hw_secy_id_rx);
                if (err)
                        return err;

                err = cn10k_mcs_write_sc_cam(pfvf, rxsc, txsc->hw_secy_id_rx);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_upd_rxsc(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        bool enable = ctx->rx_sc->active;
        struct cn10k_mcs_rxsc *rxsc;

        rxsc = cn10k_mcs_get_rxsc(cfg, secy, ctx->rx_sc);
        if (!rxsc)
                return -ENOENT;

        if (netif_running(secy->netdev))
                return cn10k_mcs_ena_dis_flowid(pfvf, rxsc->hw_flow_id,
                                                enable, MCS_RX);

        return 0;
}

static int cn10k_mdo_del_rxsc(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct cn10k_mcs_rxsc *rxsc;

        rxsc = cn10k_mcs_get_rxsc(cfg, ctx->secy, ctx->rx_sc);
        if (!rxsc)
                return -ENOENT;

        cn10k_mcs_ena_dis_flowid(pfvf, rxsc->hw_flow_id, false, MCS_RX);
        cn10k_mcs_delete_rxsc(pfvf, rxsc);
        list_del(&rxsc->entry);
        kfree(rxsc);

        return 0;
}

static int cn10k_mdo_add_rxsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_rx_sc *sw_rx_sc = ctx->sa.rx_sa->sc;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_rx_sa *rx_sa = ctx->sa.rx_sa;
        struct macsec_secy *secy = ctx->secy;
        bool sa_in_use = rx_sa->active;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_rxsc *rxsc;
        int err;

        rxsc = cn10k_mcs_get_rxsc(cfg, secy, sw_rx_sc);
        if (!rxsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        if (cn10k_mcs_alloc_rxsa(pfvf, &rxsc->hw_sa_id[sa_num]))
                return -ENOSPC;

        memcpy(&rxsc->sa_key[sa_num], ctx->sa.key, ctx->secy->key_len);
        memcpy(&rxsc->salt[sa_num], rx_sa->key.salt.bytes, MACSEC_SALT_LEN);
        rxsc->ssci[sa_num] = rx_sa->ssci;

        rxsc->sa_bmap |= 1 << sa_num;

        if (netif_running(secy->netdev)) {
                err = cn10k_mcs_write_rx_sa_plcy(pfvf, secy, rxsc,
                                                 sa_num, sa_in_use);
                if (err)
                        return err;

                err = cn10k_mcs_write_rx_sa_pn(pfvf, rxsc, sa_num,
                                               rx_sa->next_pn);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_upd_rxsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_rx_sc *sw_rx_sc = ctx->sa.rx_sa->sc;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_rx_sa *rx_sa = ctx->sa.rx_sa;
        struct macsec_secy *secy = ctx->secy;
        bool sa_in_use = rx_sa->active;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_rxsc *rxsc;
        int err;

        rxsc = cn10k_mcs_get_rxsc(cfg, secy, sw_rx_sc);
        if (!rxsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        if (netif_running(secy->netdev)) {
                err = cn10k_mcs_write_rx_sa_plcy(pfvf, secy, rxsc, sa_num, sa_in_use);
                if (err)
                        return err;

                if (!ctx->sa.update_pn)
                        return 0;

                err = cn10k_mcs_write_rx_sa_pn(pfvf, rxsc, sa_num,
                                               rx_sa->next_pn);
                if (err)
                        return err;
        }

        return 0;
}

static int cn10k_mdo_del_rxsa(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_rx_sc *sw_rx_sc = ctx->sa.rx_sa->sc;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_rxsc *rxsc;

        rxsc = cn10k_mcs_get_rxsc(cfg, ctx->secy, sw_rx_sc);
        if (!rxsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        cn10k_mcs_write_rx_sa_plcy(pfvf, ctx->secy, rxsc, sa_num, false);
        cn10k_mcs_free_rxsa(pfvf, rxsc->hw_sa_id[sa_num]);

        rxsc->sa_bmap &= ~(1 << sa_num);

        return 0;
}

static int cn10k_mdo_get_dev_stats(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct mcs_secy_stats tx_rsp = { 0 }, rx_rsp = { 0 };
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct cn10k_mcs_txsc *txsc;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        cn10k_mcs_secy_stats(pfvf, txsc->hw_secy_id_tx, &tx_rsp, MCS_TX, false);
        ctx->stats.dev_stats->OutPktsUntagged = tx_rsp.pkt_untagged_cnt;
        ctx->stats.dev_stats->OutPktsTooLong = tx_rsp.pkt_toolong_cnt;

        cn10k_mcs_secy_stats(pfvf, txsc->hw_secy_id_rx, &rx_rsp, MCS_RX, true);
        txsc->stats.InPktsBadTag += rx_rsp.pkt_badtag_cnt;
        txsc->stats.InPktsUnknownSCI += rx_rsp.pkt_nosa_cnt;
        txsc->stats.InPktsNoSCI += rx_rsp.pkt_nosaerror_cnt;
        if (secy->validate_frames == MACSEC_VALIDATE_STRICT)
                txsc->stats.InPktsNoTag += rx_rsp.pkt_untaged_cnt;
        else
                txsc->stats.InPktsUntagged += rx_rsp.pkt_untaged_cnt;
        txsc->stats.InPktsOverrun = 0;

        ctx->stats.dev_stats->InPktsNoTag = txsc->stats.InPktsNoTag;
        ctx->stats.dev_stats->InPktsUntagged = txsc->stats.InPktsUntagged;
        ctx->stats.dev_stats->InPktsBadTag = txsc->stats.InPktsBadTag;
        ctx->stats.dev_stats->InPktsUnknownSCI = txsc->stats.InPktsUnknownSCI;
        ctx->stats.dev_stats->InPktsNoSCI = txsc->stats.InPktsNoSCI;
        ctx->stats.dev_stats->InPktsOverrun = txsc->stats.InPktsOverrun;

        return 0;
}

static int cn10k_mdo_get_tx_sc_stats(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct mcs_sc_stats rsp = { 0 };
        struct cn10k_mcs_txsc *txsc;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        cn10k_mcs_sc_stats(pfvf, txsc->hw_sc_id, &rsp, MCS_TX, false);

        ctx->stats.tx_sc_stats->OutPktsProtected = rsp.pkt_protected_cnt;
        ctx->stats.tx_sc_stats->OutPktsEncrypted = rsp.pkt_encrypt_cnt;
        ctx->stats.tx_sc_stats->OutOctetsProtected = rsp.octet_protected_cnt;
        ctx->stats.tx_sc_stats->OutOctetsEncrypted = rsp.octet_encrypt_cnt;

        return 0;
}

static int cn10k_mdo_get_tx_sa_stats(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct mcs_sa_stats rsp = { 0 };
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_txsc *txsc;

        txsc = cn10k_mcs_get_txsc(cfg, ctx->secy);
        if (!txsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        cn10k_mcs_sa_stats(pfvf, txsc->hw_sa_id[sa_num], &rsp, MCS_TX, false);

        ctx->stats.tx_sa_stats->OutPktsProtected = rsp.pkt_protected_cnt;
        ctx->stats.tx_sa_stats->OutPktsEncrypted = rsp.pkt_encrypt_cnt;

        return 0;
}

static int cn10k_mdo_get_rx_sc_stats(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_secy *secy = ctx->secy;
        struct mcs_sc_stats rsp = { 0 };
        struct cn10k_mcs_rxsc *rxsc;

        rxsc = cn10k_mcs_get_rxsc(cfg, secy, ctx->rx_sc);
        if (!rxsc)
                return -ENOENT;

        cn10k_mcs_sc_stats(pfvf, rxsc->hw_sc_id, &rsp, MCS_RX, true);

        rxsc->stats.InOctetsValidated += rsp.octet_validate_cnt;
        rxsc->stats.InOctetsDecrypted += rsp.octet_decrypt_cnt;

        rxsc->stats.InPktsInvalid += rsp.pkt_invalid_cnt;
        rxsc->stats.InPktsNotValid += rsp.pkt_notvalid_cnt;

        if (secy->replay_protect)
                rxsc->stats.InPktsLate += rsp.pkt_late_cnt;
        else
                rxsc->stats.InPktsDelayed += rsp.pkt_late_cnt;

        if (secy->validate_frames == MACSEC_VALIDATE_DISABLED)
                rxsc->stats.InPktsUnchecked += rsp.pkt_unchecked_cnt;
        else
                rxsc->stats.InPktsOK += rsp.pkt_unchecked_cnt;

        ctx->stats.rx_sc_stats->InOctetsValidated = rxsc->stats.InOctetsValidated;
        ctx->stats.rx_sc_stats->InOctetsDecrypted = rxsc->stats.InOctetsDecrypted;
        ctx->stats.rx_sc_stats->InPktsInvalid = rxsc->stats.InPktsInvalid;
        ctx->stats.rx_sc_stats->InPktsNotValid = rxsc->stats.InPktsNotValid;
        ctx->stats.rx_sc_stats->InPktsLate = rxsc->stats.InPktsLate;
        ctx->stats.rx_sc_stats->InPktsDelayed = rxsc->stats.InPktsDelayed;
        ctx->stats.rx_sc_stats->InPktsUnchecked = rxsc->stats.InPktsUnchecked;
        ctx->stats.rx_sc_stats->InPktsOK = rxsc->stats.InPktsOK;

        return 0;
}

static int cn10k_mdo_get_rx_sa_stats(struct macsec_context *ctx)
{
        struct otx2_nic *pfvf = macsec_netdev_priv(ctx->netdev);
        struct macsec_rx_sc *sw_rx_sc = ctx->sa.rx_sa->sc;
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct mcs_sa_stats rsp = { 0 };
        u8 sa_num = ctx->sa.assoc_num;
        struct cn10k_mcs_rxsc *rxsc;

        rxsc = cn10k_mcs_get_rxsc(cfg, ctx->secy, sw_rx_sc);
        if (!rxsc)
                return -ENOENT;

        if (sa_num >= CN10K_MCS_SA_PER_SC)
                return -EOPNOTSUPP;

        cn10k_mcs_sa_stats(pfvf, rxsc->hw_sa_id[sa_num], &rsp, MCS_RX, false);

        ctx->stats.rx_sa_stats->InPktsOK = rsp.pkt_ok_cnt;
        ctx->stats.rx_sa_stats->InPktsInvalid = rsp.pkt_invalid_cnt;
        ctx->stats.rx_sa_stats->InPktsNotValid = rsp.pkt_notvalid_cnt;
        ctx->stats.rx_sa_stats->InPktsNotUsingSA = rsp.pkt_nosaerror_cnt;
        ctx->stats.rx_sa_stats->InPktsUnusedSA = rsp.pkt_nosa_cnt;

        return 0;
}

static const struct macsec_ops cn10k_mcs_ops = {
        .mdo_dev_open = cn10k_mdo_open,
        .mdo_dev_stop = cn10k_mdo_stop,
        .mdo_add_secy = cn10k_mdo_add_secy,
        .mdo_upd_secy = cn10k_mdo_upd_secy,
        .mdo_del_secy = cn10k_mdo_del_secy,
        .mdo_add_rxsc = cn10k_mdo_add_rxsc,
        .mdo_upd_rxsc = cn10k_mdo_upd_rxsc,
        .mdo_del_rxsc = cn10k_mdo_del_rxsc,
        .mdo_add_rxsa = cn10k_mdo_add_rxsa,
        .mdo_upd_rxsa = cn10k_mdo_upd_rxsa,
        .mdo_del_rxsa = cn10k_mdo_del_rxsa,
        .mdo_add_txsa = cn10k_mdo_add_txsa,
        .mdo_upd_txsa = cn10k_mdo_upd_txsa,
        .mdo_del_txsa = cn10k_mdo_del_txsa,
        .mdo_get_dev_stats = cn10k_mdo_get_dev_stats,
        .mdo_get_tx_sc_stats = cn10k_mdo_get_tx_sc_stats,
        .mdo_get_tx_sa_stats = cn10k_mdo_get_tx_sa_stats,
        .mdo_get_rx_sc_stats = cn10k_mdo_get_rx_sc_stats,
        .mdo_get_rx_sa_stats = cn10k_mdo_get_rx_sa_stats,
};

void cn10k_handle_mcs_event(struct otx2_nic *pfvf, struct mcs_intr_info *event)
{
        struct cn10k_mcs_cfg *cfg = pfvf->macsec_cfg;
        struct macsec_tx_sa *sw_tx_sa = NULL;
        struct macsec_secy *secy = NULL;
        struct cn10k_mcs_txsc *txsc;
        u8 an;

        if (!test_bit(CN10K_HW_MACSEC, &pfvf->hw.cap_flag))
                return;

        if (!(event->intr_mask & MCS_CPM_TX_PACKET_XPN_EQ0_INT))
                return;

        /* Find the SecY to which the expired hardware SA is mapped */
        list_for_each_entry(txsc, &cfg->txsc_list, entry) {
                for (an = 0; an < CN10K_MCS_SA_PER_SC; an++)
                        if (txsc->hw_sa_id[an] == event->sa_id) {
                                secy = txsc->sw_secy;
                                sw_tx_sa = rcu_dereference_bh(secy->tx_sc.sa[an]);
                        }
        }

        if (secy && sw_tx_sa)
                macsec_pn_wrapped(secy, sw_tx_sa);
}

int cn10k_mcs_init(struct otx2_nic *pfvf)
{
        struct mbox *mbox = &pfvf->mbox;
        struct cn10k_mcs_cfg *cfg;
        struct mcs_intr_cfg *req;

        if (!test_bit(CN10K_HW_MACSEC, &pfvf->hw.cap_flag))
                return 0;

        cfg = kzalloc_obj(*cfg);
        if (!cfg)
                return -ENOMEM;

        INIT_LIST_HEAD(&cfg->txsc_list);
        INIT_LIST_HEAD(&cfg->rxsc_list);
        pfvf->macsec_cfg = cfg;

        pfvf->netdev->features |= NETIF_F_HW_MACSEC;
        pfvf->netdev->macsec_ops = &cn10k_mcs_ops;

        mutex_lock(&mbox->lock);

        req = otx2_mbox_alloc_msg_mcs_intr_cfg(mbox);
        if (!req)
                goto fail;

        req->intr_mask = MCS_CPM_TX_PACKET_XPN_EQ0_INT;

        if (otx2_sync_mbox_msg(mbox))
                goto fail;

        mutex_unlock(&mbox->lock);

        return 0;
fail:
        dev_err(pfvf->dev, "Cannot notify PN wrapped event\n");
        mutex_unlock(&mbox->lock);
        return 0;
}

void cn10k_mcs_free(struct otx2_nic *pfvf)
{
        if (!test_bit(CN10K_HW_MACSEC, &pfvf->hw.cap_flag))
                return;

        cn10k_mcs_free_rsrc(pfvf, MCS_TX, MCS_RSRC_TYPE_SECY, 0, true);
        cn10k_mcs_free_rsrc(pfvf, MCS_RX, MCS_RSRC_TYPE_SECY, 0, true);
        kfree(pfvf->macsec_cfg);
        pfvf->macsec_cfg = NULL;
}