// SPDX-License-Identifier: GPL-2.0 /* Marvell IPSEC offload driver * * Copyright (C) 2024 Marvell. */ #include #include #include #include #include #include "otx2_common.h" #include "otx2_struct.h" #include "cn10k_ipsec.h" static bool is_dev_support_ipsec_offload(struct pci_dev *pdev) { return is_dev_cn10ka_b0(pdev) || is_dev_cn10kb(pdev); } static bool cn10k_cpt_device_set_inuse(struct otx2_nic *pf) { enum cn10k_cpt_hw_state_e state; while (true) { state = atomic_cmpxchg(&pf->ipsec.cpt_state, CN10K_CPT_HW_AVAILABLE, CN10K_CPT_HW_IN_USE); if (state == CN10K_CPT_HW_AVAILABLE) return true; if (state == CN10K_CPT_HW_UNAVAILABLE) return false; mdelay(1); } } static void cn10k_cpt_device_set_available(struct otx2_nic *pf) { atomic_set(&pf->ipsec.cpt_state, CN10K_CPT_HW_AVAILABLE); } static void cn10k_cpt_device_set_unavailable(struct otx2_nic *pf) { atomic_set(&pf->ipsec.cpt_state, CN10K_CPT_HW_UNAVAILABLE); } static int cn10k_outb_cptlf_attach(struct otx2_nic *pf) { struct rsrc_attach *attach; int ret = -ENOMEM; mutex_lock(&pf->mbox.lock); /* Get memory to put this msg */ attach = otx2_mbox_alloc_msg_attach_resources(&pf->mbox); if (!attach) goto unlock; attach->cptlfs = true; attach->modify = true; /* Send attach request to AF */ ret = otx2_sync_mbox_msg(&pf->mbox); unlock: mutex_unlock(&pf->mbox.lock); return ret; } static int cn10k_outb_cptlf_detach(struct otx2_nic *pf) { struct rsrc_detach *detach; int ret = -ENOMEM; mutex_lock(&pf->mbox.lock); detach = otx2_mbox_alloc_msg_detach_resources(&pf->mbox); if (!detach) goto unlock; detach->partial = true; detach->cptlfs = true; /* Send detach request to AF */ ret = otx2_sync_mbox_msg(&pf->mbox); unlock: mutex_unlock(&pf->mbox.lock); return ret; } static int cn10k_outb_cptlf_alloc(struct otx2_nic *pf) { struct cpt_lf_alloc_req_msg *req; int ret = -ENOMEM; mutex_lock(&pf->mbox.lock); req = otx2_mbox_alloc_msg_cpt_lf_alloc(&pf->mbox); if (!req) goto unlock; /* PF function */ req->nix_pf_func = pf->pcifunc; /* Enable SE-IE Engine Group */ req->eng_grpmsk = 1 << CN10K_DEF_CPT_IPSEC_EGRP; ret = otx2_sync_mbox_msg(&pf->mbox); unlock: mutex_unlock(&pf->mbox.lock); return ret; } static void cn10k_outb_cptlf_free(struct otx2_nic *pf) { mutex_lock(&pf->mbox.lock); otx2_mbox_alloc_msg_cpt_lf_free(&pf->mbox); otx2_sync_mbox_msg(&pf->mbox); mutex_unlock(&pf->mbox.lock); } static int cn10k_outb_cptlf_config(struct otx2_nic *pf) { struct cpt_inline_ipsec_cfg_msg *req; int ret = -ENOMEM; mutex_lock(&pf->mbox.lock); req = otx2_mbox_alloc_msg_cpt_inline_ipsec_cfg(&pf->mbox); if (!req) goto unlock; req->dir = CPT_INLINE_OUTBOUND; req->enable = 1; req->nix_pf_func = pf->pcifunc; ret = otx2_sync_mbox_msg(&pf->mbox); unlock: mutex_unlock(&pf->mbox.lock); return ret; } static void cn10k_outb_cptlf_iq_enable(struct otx2_nic *pf) { u64 reg_val; /* Set Execution Enable of instruction queue */ reg_val = otx2_read64(pf, CN10K_CPT_LF_INPROG); reg_val |= BIT_ULL(16); otx2_write64(pf, CN10K_CPT_LF_INPROG, reg_val); /* Set iqueue's enqueuing */ reg_val = otx2_read64(pf, CN10K_CPT_LF_CTL); reg_val |= BIT_ULL(0); otx2_write64(pf, CN10K_CPT_LF_CTL, reg_val); } static void cn10k_outb_cptlf_iq_disable(struct otx2_nic *pf) { u32 inflight, grb_cnt, gwb_cnt; u32 nq_ptr, dq_ptr; int timeout = 20; u64 reg_val; int cnt; /* Disable instructions enqueuing */ otx2_write64(pf, CN10K_CPT_LF_CTL, 0ull); /* Wait for instruction queue to become empty. * CPT_LF_INPROG.INFLIGHT count is zero */ do { reg_val = otx2_read64(pf, CN10K_CPT_LF_INPROG); inflight = FIELD_GET(CPT_LF_INPROG_INFLIGHT, reg_val); if (!inflight) break; usleep_range(10000, 20000); if (timeout-- < 0) { netdev_err(pf->netdev, "Timeout to cleanup CPT IQ\n"); break; } } while (1); /* Disable executions in the LF's queue, * the queue should be empty at this point */ reg_val &= ~BIT_ULL(16); otx2_write64(pf, CN10K_CPT_LF_INPROG, reg_val); /* Wait for instruction queue to become empty */ cnt = 0; do { reg_val = otx2_read64(pf, CN10K_CPT_LF_INPROG); if (reg_val & BIT_ULL(31)) cnt = 0; else cnt++; reg_val = otx2_read64(pf, CN10K_CPT_LF_Q_GRP_PTR); nq_ptr = FIELD_GET(CPT_LF_Q_GRP_PTR_DQ_PTR, reg_val); dq_ptr = FIELD_GET(CPT_LF_Q_GRP_PTR_DQ_PTR, reg_val); } while ((cnt < 10) && (nq_ptr != dq_ptr)); cnt = 0; do { reg_val = otx2_read64(pf, CN10K_CPT_LF_INPROG); inflight = FIELD_GET(CPT_LF_INPROG_INFLIGHT, reg_val); grb_cnt = FIELD_GET(CPT_LF_INPROG_GRB_CNT, reg_val); gwb_cnt = FIELD_GET(CPT_LF_INPROG_GWB_CNT, reg_val); if (inflight == 0 && gwb_cnt < 40 && (grb_cnt == 0 || grb_cnt == 40)) cnt++; else cnt = 0; } while (cnt < 10); } /* Allocate memory for CPT outbound Instruction queue. * Instruction queue memory format is: * ----------------------------- * | Instruction Group memory | * | (CPT_LF_Q_SIZE[SIZE_DIV40] | * | x 16 Bytes) | * | | * ----------------------------- <-- CPT_LF_Q_BASE[ADDR] * | Flow Control (128 Bytes) | * | | * ----------------------------- * | Instruction Memory | * | (CPT_LF_Q_SIZE[SIZE_DIV40] | * | × 40 × 64 bytes) | * | | * ----------------------------- */ static int cn10k_outb_cptlf_iq_alloc(struct otx2_nic *pf) { struct cn10k_cpt_inst_queue *iq = &pf->ipsec.iq; iq->size = CN10K_CPT_INST_QLEN_BYTES + CN10K_CPT_Q_FC_LEN + CN10K_CPT_INST_GRP_QLEN_BYTES + OTX2_ALIGN; iq->real_vaddr = dma_alloc_coherent(pf->dev, iq->size, &iq->real_dma_addr, GFP_KERNEL); if (!iq->real_vaddr) return -ENOMEM; /* iq->vaddr/dma_addr points to Flow Control location */ iq->vaddr = iq->real_vaddr + CN10K_CPT_INST_GRP_QLEN_BYTES; iq->dma_addr = iq->real_dma_addr + CN10K_CPT_INST_GRP_QLEN_BYTES; /* Align pointers */ iq->vaddr = PTR_ALIGN(iq->vaddr, OTX2_ALIGN); iq->dma_addr = PTR_ALIGN(iq->dma_addr, OTX2_ALIGN); return 0; } static void cn10k_outb_cptlf_iq_free(struct otx2_nic *pf) { struct cn10k_cpt_inst_queue *iq = &pf->ipsec.iq; if (iq->real_vaddr) dma_free_coherent(pf->dev, iq->size, iq->real_vaddr, iq->real_dma_addr); iq->real_vaddr = NULL; iq->vaddr = NULL; } static int cn10k_outb_cptlf_iq_init(struct otx2_nic *pf) { u64 reg_val; int ret; /* Allocate Memory for CPT IQ */ ret = cn10k_outb_cptlf_iq_alloc(pf); if (ret) return ret; /* Disable IQ */ cn10k_outb_cptlf_iq_disable(pf); /* Set IQ base address */ otx2_write64(pf, CN10K_CPT_LF_Q_BASE, pf->ipsec.iq.dma_addr); /* Set IQ size */ reg_val = FIELD_PREP(CPT_LF_Q_SIZE_DIV40, CN10K_CPT_SIZE_DIV40 + CN10K_CPT_EXTRA_SIZE_DIV40); otx2_write64(pf, CN10K_CPT_LF_Q_SIZE, reg_val); return 0; } static int cn10k_outb_cptlf_init(struct otx2_nic *pf) { int ret; /* Initialize CPTLF Instruction Queue (IQ) */ ret = cn10k_outb_cptlf_iq_init(pf); if (ret) return ret; /* Configure CPTLF for outbound ipsec offload */ ret = cn10k_outb_cptlf_config(pf); if (ret) goto iq_clean; /* Enable CPTLF IQ */ cn10k_outb_cptlf_iq_enable(pf); return 0; iq_clean: cn10k_outb_cptlf_iq_free(pf); return ret; } static int cn10k_outb_cpt_init(struct net_device *netdev) { struct otx2_nic *pf = netdev_priv(netdev); int ret; /* Attach a CPT LF for outbound ipsec offload */ ret = cn10k_outb_cptlf_attach(pf); if (ret) return ret; /* Allocate a CPT LF for outbound ipsec offload */ ret = cn10k_outb_cptlf_alloc(pf); if (ret) goto detach; /* Initialize the CPTLF for outbound ipsec offload */ ret = cn10k_outb_cptlf_init(pf); if (ret) goto lf_free; pf->ipsec.io_addr = (__force u64)otx2_get_regaddr(pf, CN10K_CPT_LF_NQX(0)); /* Set ipsec offload enabled for this device */ pf->flags |= OTX2_FLAG_IPSEC_OFFLOAD_ENABLED; cn10k_cpt_device_set_available(pf); return 0; lf_free: cn10k_outb_cptlf_free(pf); detach: cn10k_outb_cptlf_detach(pf); return ret; } static int cn10k_outb_cpt_clean(struct otx2_nic *pf) { int ret; if (!cn10k_cpt_device_set_inuse(pf)) { netdev_err(pf->netdev, "CPT LF device unavailable\n"); return -ENODEV; } /* Set ipsec offload disabled for this device */ pf->flags &= ~OTX2_FLAG_IPSEC_OFFLOAD_ENABLED; /* Disable CPTLF Instruction Queue (IQ) */ cn10k_outb_cptlf_iq_disable(pf); /* Set IQ base address and size to 0 */ otx2_write64(pf, CN10K_CPT_LF_Q_BASE, 0); otx2_write64(pf, CN10K_CPT_LF_Q_SIZE, 0); /* Free CPTLF IQ */ cn10k_outb_cptlf_iq_free(pf); /* Free and detach CPT LF */ cn10k_outb_cptlf_free(pf); ret = cn10k_outb_cptlf_detach(pf); if (ret) netdev_err(pf->netdev, "Failed to detach CPT LF\n"); cn10k_cpt_device_set_unavailable(pf); return ret; } static void cn10k_cpt_inst_flush(struct otx2_nic *pf, struct cpt_inst_s *inst, u64 size) { struct otx2_lmt_info *lmt_info; u64 val = 0, tar_addr = 0; lmt_info = per_cpu_ptr(pf->hw.lmt_info, smp_processor_id()); /* FIXME: val[0:10] LMT_ID. * [12:15] no of LMTST - 1 in the burst. * [19:63] data size of each LMTST in the burst except first. */ val = (lmt_info->lmt_id & 0x7FF); /* Target address for LMTST flush tells HW how many 128bit * words are present. * tar_addr[6:4] size of first LMTST - 1 in units of 128b. */ tar_addr |= pf->ipsec.io_addr | (((size / 16) - 1) & 0x7) << 4; dma_wmb(); memcpy((u64 *)lmt_info->lmt_addr, inst, size); cn10k_lmt_flush(val, tar_addr); } static int cn10k_wait_for_cpt_respose(struct otx2_nic *pf, struct cpt_res_s *res) { unsigned long timeout = jiffies + msecs_to_jiffies(100); u64 *completion_ptr = (u64 *)res; do { if (time_after(jiffies, timeout)) { netdev_err(pf->netdev, "CPT response timeout\n"); return -EBUSY; } } while ((READ_ONCE(*completion_ptr) & CN10K_CPT_COMP_E_MASK) == CN10K_CPT_COMP_E_NOTDONE); if (!(res->compcode == CN10K_CPT_COMP_E_GOOD || res->compcode == CN10K_CPT_COMP_E_WARN) || res->uc_compcode) { netdev_err(pf->netdev, "compcode=%x doneint=%x\n", res->compcode, res->doneint); netdev_err(pf->netdev, "uc_compcode=%x uc_info=%llx esn=%llx\n", res->uc_compcode, (u64)res->uc_info, res->esn); } return 0; } static int cn10k_outb_write_sa(struct otx2_nic *pf, struct qmem *sa_info) { dma_addr_t res_iova, dptr_iova, sa_iova; struct cn10k_tx_sa_s *sa_dptr; struct cpt_inst_s inst = {}; struct cpt_res_s *res; u32 sa_size, off; u64 *sptr, *dptr; u64 reg_val; int ret; sa_iova = sa_info->iova; if (!sa_iova) return -EINVAL; res = dma_alloc_coherent(pf->dev, sizeof(struct cpt_res_s), &res_iova, GFP_ATOMIC); if (!res) return -ENOMEM; sa_size = sizeof(struct cn10k_tx_sa_s); sa_dptr = dma_alloc_coherent(pf->dev, sa_size, &dptr_iova, GFP_ATOMIC); if (!sa_dptr) { dma_free_coherent(pf->dev, sizeof(struct cpt_res_s), res, res_iova); return -ENOMEM; } sptr = (__force u64 *)sa_info->base; dptr = (__force u64 *)sa_dptr; for (off = 0; off < (sa_size / 8); off++) *(dptr + off) = (__force u64)cpu_to_be64(*(sptr + off)); res->compcode = CN10K_CPT_COMP_E_NOTDONE; inst.res_addr = res_iova; inst.dptr = (u64)dptr_iova; inst.param2 = sa_size >> 3; inst.dlen = sa_size; inst.opcode_major = CN10K_IPSEC_MAJOR_OP_WRITE_SA; inst.opcode_minor = CN10K_IPSEC_MINOR_OP_WRITE_SA; inst.cptr = sa_iova; inst.ctx_val = 1; inst.egrp = CN10K_DEF_CPT_IPSEC_EGRP; /* Check if CPT-LF available */ if (!cn10k_cpt_device_set_inuse(pf)) { ret = -ENODEV; goto free_mem; } cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s)); dma_wmb(); ret = cn10k_wait_for_cpt_respose(pf, res); if (ret) goto set_available; /* Trigger CTX flush to write dirty data back to DRAM */ reg_val = FIELD_PREP(CPT_LF_CTX_FLUSH, sa_iova >> 7); otx2_write64(pf, CN10K_CPT_LF_CTX_FLUSH, reg_val); set_available: cn10k_cpt_device_set_available(pf); free_mem: dma_free_coherent(pf->dev, sa_size, sa_dptr, dptr_iova); dma_free_coherent(pf->dev, sizeof(struct cpt_res_s), res, res_iova); return ret; } static int cn10k_ipsec_get_hw_ctx_offset(void) { /* Offset on Hardware-context offset in word */ return (offsetof(struct cn10k_tx_sa_s, hw_ctx) / sizeof(u64)) & 0x7F; } static int cn10k_ipsec_get_ctx_push_size(void) { /* Context push size is round up and in multiple of 8 Byte */ return (roundup(offsetof(struct cn10k_tx_sa_s, hw_ctx), 8) / 8) & 0x7F; } static int cn10k_ipsec_get_aes_key_len(int key_len) { /* key_len is aes key length in bytes */ switch (key_len) { case 16: return CN10K_IPSEC_SA_AES_KEY_LEN_128; case 24: return CN10K_IPSEC_SA_AES_KEY_LEN_192; default: return CN10K_IPSEC_SA_AES_KEY_LEN_256; } } static void cn10k_outb_prepare_sa(struct xfrm_state *x, struct cn10k_tx_sa_s *sa_entry) { int key_len = (x->aead->alg_key_len + 7) / 8; struct net_device *netdev = x->xso.dev; u8 *key = x->aead->alg_key; struct otx2_nic *pf; u32 *tmp_salt; u64 *tmp_key; int idx; memset(sa_entry, 0, sizeof(struct cn10k_tx_sa_s)); /* context size, 128 Byte aligned up */ pf = netdev_priv(netdev); sa_entry->ctx_size = (pf->ipsec.sa_size / OTX2_ALIGN) & 0xF; sa_entry->hw_ctx_off = cn10k_ipsec_get_hw_ctx_offset(); sa_entry->ctx_push_size = cn10k_ipsec_get_ctx_push_size(); /* Ucode to skip two words of CPT_CTX_HW_S */ sa_entry->ctx_hdr_size = 1; /* Allow Atomic operation (AOP) */ sa_entry->aop_valid = 1; /* Outbound, ESP TRANSPORT/TUNNEL Mode, AES-GCM with */ sa_entry->sa_dir = CN10K_IPSEC_SA_DIR_OUTB; sa_entry->ipsec_protocol = CN10K_IPSEC_SA_IPSEC_PROTO_ESP; sa_entry->enc_type = CN10K_IPSEC_SA_ENCAP_TYPE_AES_GCM; sa_entry->iv_src = CN10K_IPSEC_SA_IV_SRC_PACKET; if (x->props.mode == XFRM_MODE_TUNNEL) sa_entry->ipsec_mode = CN10K_IPSEC_SA_IPSEC_MODE_TUNNEL; else sa_entry->ipsec_mode = CN10K_IPSEC_SA_IPSEC_MODE_TRANSPORT; /* Last 4 bytes are salt */ key_len -= 4; sa_entry->aes_key_len = cn10k_ipsec_get_aes_key_len(key_len); memcpy(sa_entry->cipher_key, key, key_len); tmp_key = (u64 *)sa_entry->cipher_key; for (idx = 0; idx < key_len / 8; idx++) tmp_key[idx] = (__force u64)cpu_to_be64(tmp_key[idx]); memcpy(&sa_entry->iv_gcm_salt, key + key_len, 4); tmp_salt = (u32 *)&sa_entry->iv_gcm_salt; *tmp_salt = (__force u32)cpu_to_be32(*tmp_salt); /* Write SA context data to memory before enabling */ wmb(); /* Enable SA */ sa_entry->sa_valid = 1; } static int cn10k_ipsec_validate_state(struct xfrm_state *x, struct netlink_ext_ack *extack) { if (x->props.aalgo != SADB_AALG_NONE) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload authenticated xfrm states"); return -EINVAL; } if (x->props.ealgo != SADB_X_EALG_AES_GCM_ICV16) { NL_SET_ERR_MSG_MOD(extack, "Only AES-GCM-ICV16 xfrm state may be offloaded"); return -EINVAL; } if (x->props.calgo != SADB_X_CALG_NONE) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload compressed xfrm states"); return -EINVAL; } if (x->props.flags & XFRM_STATE_ESN) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload ESN xfrm states"); return -EINVAL; } if (x->props.family != AF_INET && x->props.family != AF_INET6) { NL_SET_ERR_MSG_MOD(extack, "Only IPv4/v6 xfrm states may be offloaded"); return -EINVAL; } if (x->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload other than crypto-mode"); return -EINVAL; } if (x->props.mode != XFRM_MODE_TRANSPORT && x->props.mode != XFRM_MODE_TUNNEL) { NL_SET_ERR_MSG_MOD(extack, "Only tunnel/transport xfrm states may be offloaded"); return -EINVAL; } if (x->id.proto != IPPROTO_ESP) { NL_SET_ERR_MSG_MOD(extack, "Only ESP xfrm state may be offloaded"); return -EINVAL; } if (x->encap) { NL_SET_ERR_MSG_MOD(extack, "Encapsulated xfrm state may not be offloaded"); return -EINVAL; } if (!x->aead) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states without aead"); return -EINVAL; } if (x->aead->alg_icv_len != 128) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states with AEAD ICV length other than 128bit"); return -EINVAL; } if (x->aead->alg_key_len != 128 + 32 && x->aead->alg_key_len != 192 + 32 && x->aead->alg_key_len != 256 + 32) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states with AEAD key length other than 128/192/256bit"); return -EINVAL; } if (x->tfcpad) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states with tfc padding"); return -EINVAL; } if (!x->geniv) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states without geniv"); return -EINVAL; } if (strcmp(x->geniv, "seqiv")) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload xfrm states with geniv other than seqiv"); return -EINVAL; } return 0; } static int cn10k_ipsec_inb_add_state(struct xfrm_state *x, struct netlink_ext_ack *extack) { NL_SET_ERR_MSG_MOD(extack, "xfrm inbound offload not supported"); return -EOPNOTSUPP; } static int cn10k_ipsec_outb_add_state(struct xfrm_state *x, struct netlink_ext_ack *extack) { struct net_device *netdev = x->xso.dev; struct cn10k_tx_sa_s *sa_entry; struct qmem *sa_info; struct otx2_nic *pf; int err; err = cn10k_ipsec_validate_state(x, extack); if (err) return err; pf = netdev_priv(netdev); err = qmem_alloc(pf->dev, &sa_info, pf->ipsec.sa_size, OTX2_ALIGN); if (err) return err; sa_entry = (struct cn10k_tx_sa_s *)sa_info->base; cn10k_outb_prepare_sa(x, sa_entry); err = cn10k_outb_write_sa(pf, sa_info); if (err) { NL_SET_ERR_MSG_MOD(extack, "Error writing outbound SA"); qmem_free(pf->dev, sa_info); return err; } x->xso.offload_handle = (unsigned long)sa_info; /* Enable static branch when first SA setup */ if (!pf->ipsec.outb_sa_count) static_branch_enable(&cn10k_ipsec_sa_enabled); pf->ipsec.outb_sa_count++; return 0; } static int cn10k_ipsec_add_state(struct xfrm_state *x, struct netlink_ext_ack *extack) { if (x->xso.dir == XFRM_DEV_OFFLOAD_IN) return cn10k_ipsec_inb_add_state(x, extack); else return cn10k_ipsec_outb_add_state(x, extack); } static void cn10k_ipsec_del_state(struct xfrm_state *x) { struct net_device *netdev = x->xso.dev; struct cn10k_tx_sa_s *sa_entry; struct qmem *sa_info; struct otx2_nic *pf; int err; if (x->xso.dir == XFRM_DEV_OFFLOAD_IN) return; pf = netdev_priv(netdev); sa_info = (struct qmem *)x->xso.offload_handle; sa_entry = (struct cn10k_tx_sa_s *)sa_info->base; memset(sa_entry, 0, sizeof(struct cn10k_tx_sa_s)); /* Disable SA in CPT h/w */ sa_entry->ctx_push_size = cn10k_ipsec_get_ctx_push_size(); sa_entry->ctx_size = (pf->ipsec.sa_size / OTX2_ALIGN) & 0xF; sa_entry->aop_valid = 1; err = cn10k_outb_write_sa(pf, sa_info); if (err) netdev_err(netdev, "Error (%d) deleting SA\n", err); x->xso.offload_handle = 0; qmem_free(pf->dev, sa_info); /* If no more SA's then update netdev feature for potential change * in NETIF_F_HW_ESP. */ if (!--pf->ipsec.outb_sa_count) queue_work(pf->ipsec.sa_workq, &pf->ipsec.sa_work); } static bool cn10k_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *x) { if (x->props.family == AF_INET) { /* Offload with IPv4 options is not supported yet */ if (ip_hdr(skb)->ihl > 5) return false; } else { /* Offload with IPv6 extension headers is not support yet */ if (ipv6_ext_hdr(ipv6_hdr(skb)->nexthdr)) return false; } return true; } static const struct xfrmdev_ops cn10k_ipsec_xfrmdev_ops = { .xdo_dev_state_add = cn10k_ipsec_add_state, .xdo_dev_state_delete = cn10k_ipsec_del_state, .xdo_dev_offload_ok = cn10k_ipsec_offload_ok, }; static void cn10k_ipsec_sa_wq_handler(struct work_struct *work) { struct cn10k_ipsec *ipsec = container_of(work, struct cn10k_ipsec, sa_work); struct otx2_nic *pf = container_of(ipsec, struct otx2_nic, ipsec); /* Disable static branch when no more SA enabled */ static_branch_disable(&cn10k_ipsec_sa_enabled); rtnl_lock(); netdev_update_features(pf->netdev); rtnl_unlock(); } int cn10k_ipsec_ethtool_init(struct net_device *netdev, bool enable) { struct otx2_nic *pf = netdev_priv(netdev); /* IPsec offload supported on cn10k */ if (!is_dev_support_ipsec_offload(pf->pdev)) return -EOPNOTSUPP; /* Initialize CPT for outbound ipsec offload */ if (enable) return cn10k_outb_cpt_init(netdev); /* Don't do CPT cleanup if SA installed */ if (pf->ipsec.outb_sa_count) { netdev_err(pf->netdev, "SA installed on this device\n"); return -EBUSY; } return cn10k_outb_cpt_clean(pf); } int cn10k_ipsec_init(struct net_device *netdev) { struct otx2_nic *pf = netdev_priv(netdev); u32 sa_size; if (!is_dev_support_ipsec_offload(pf->pdev)) return 0; /* Each SA entry size is 128 Byte round up in size */ sa_size = sizeof(struct cn10k_tx_sa_s) % OTX2_ALIGN ? (sizeof(struct cn10k_tx_sa_s) / OTX2_ALIGN + 1) * OTX2_ALIGN : sizeof(struct cn10k_tx_sa_s); pf->ipsec.sa_size = sa_size; INIT_WORK(&pf->ipsec.sa_work, cn10k_ipsec_sa_wq_handler); pf->ipsec.sa_workq = alloc_workqueue("cn10k_ipsec_sa_workq", 0, 0); if (!pf->ipsec.sa_workq) { netdev_err(pf->netdev, "SA alloc workqueue failed\n"); return -ENOMEM; } /* Set xfrm device ops */ netdev->xfrmdev_ops = &cn10k_ipsec_xfrmdev_ops; netdev->hw_features |= NETIF_F_HW_ESP; netdev->hw_enc_features |= NETIF_F_HW_ESP; cn10k_cpt_device_set_unavailable(pf); return 0; } EXPORT_SYMBOL(cn10k_ipsec_init); void cn10k_ipsec_clean(struct otx2_nic *pf) { if (!is_dev_support_ipsec_offload(pf->pdev)) return; if (!(pf->flags & OTX2_FLAG_IPSEC_OFFLOAD_ENABLED)) return; if (pf->ipsec.sa_workq) { destroy_workqueue(pf->ipsec.sa_workq); pf->ipsec.sa_workq = NULL; } cn10k_outb_cpt_clean(pf); } EXPORT_SYMBOL(cn10k_ipsec_clean); static u16 cn10k_ipsec_get_ip_data_len(struct xfrm_state *x, struct sk_buff *skb) { struct ipv6hdr *ipv6h; struct iphdr *iph; u8 *src; src = (u8 *)skb->data + ETH_HLEN; if (x->props.family == AF_INET) { iph = (struct iphdr *)src; return ntohs(iph->tot_len); } ipv6h = (struct ipv6hdr *)src; return ntohs(ipv6h->payload_len) + sizeof(struct ipv6hdr); } /* Prepare CPT and NIX SQE scatter/gather subdescriptor structure. * SG of NIX and CPT are same in size. * Layout of a NIX SQE and CPT SG entry: * ----------------------------- * | CPT Scatter Gather | * | (SQE SIZE) | * | | * ----------------------------- * | NIX SQE | * | (SQE SIZE) | * | | * ----------------------------- */ bool otx2_sqe_add_sg_ipsec(struct otx2_nic *pfvf, struct otx2_snd_queue *sq, struct sk_buff *skb, int num_segs, int *offset) { struct cpt_sg_s *cpt_sg = NULL; struct nix_sqe_sg_s *sg = NULL; u64 dma_addr, *iova = NULL; u64 *cpt_iova = NULL; u16 *sg_lens = NULL; int seg, len; sq->sg[sq->head].num_segs = 0; cpt_sg = (struct cpt_sg_s *)(sq->sqe_base - sq->sqe_size); for (seg = 0; seg < num_segs; seg++) { if ((seg % MAX_SEGS_PER_SG) == 0) { sg = (struct nix_sqe_sg_s *)(sq->sqe_base + *offset); sg->ld_type = NIX_SEND_LDTYPE_LDD; sg->subdc = NIX_SUBDC_SG; sg->segs = 0; sg_lens = (void *)sg; iova = (void *)sg + sizeof(*sg); /* Next subdc always starts at a 16byte boundary. * So if sg->segs is whether 2 or 3, offset += 16bytes. */ if ((num_segs - seg) >= (MAX_SEGS_PER_SG - 1)) *offset += sizeof(*sg) + (3 * sizeof(u64)); else *offset += sizeof(*sg) + sizeof(u64); cpt_sg += (seg / MAX_SEGS_PER_SG) * 4; cpt_iova = (void *)cpt_sg + sizeof(*cpt_sg); } dma_addr = otx2_dma_map_skb_frag(pfvf, skb, seg, &len); if (dma_mapping_error(pfvf->dev, dma_addr)) return false; sg_lens[seg % MAX_SEGS_PER_SG] = len; sg->segs++; *iova++ = dma_addr; *cpt_iova++ = dma_addr; /* Save DMA mapping info for later unmapping */ sq->sg[sq->head].dma_addr[seg] = dma_addr; sq->sg[sq->head].size[seg] = len; sq->sg[sq->head].num_segs++; *cpt_sg = *(struct cpt_sg_s *)sg; cpt_sg->rsvd_63_50 = 0; } sq->sg[sq->head].skb = (u64)skb; return true; } static u16 cn10k_ipsec_get_param1(u8 iv_offset) { u16 param1_val; /* Set Crypto mode, disable L3/L4 checksum */ param1_val = CN10K_IPSEC_INST_PARAM1_DIS_L4_CSUM | CN10K_IPSEC_INST_PARAM1_DIS_L3_CSUM; param1_val |= (u16)iv_offset << CN10K_IPSEC_INST_PARAM1_IV_OFFSET_SHIFT; return param1_val; } bool cn10k_ipsec_transmit(struct otx2_nic *pf, struct netdev_queue *txq, struct otx2_snd_queue *sq, struct sk_buff *skb, int num_segs, int size) { struct cpt_inst_s inst; struct cpt_res_s *res; struct xfrm_state *x; struct qmem *sa_info; dma_addr_t dptr_iova; struct sec_path *sp; u8 encap_offset; u8 auth_offset; u8 gthr_size; u8 iv_offset; u16 dlen; /* Check for IPSEC offload enabled */ if (!(pf->flags & OTX2_FLAG_IPSEC_OFFLOAD_ENABLED)) goto drop; sp = skb_sec_path(skb); if (unlikely(!sp->len)) goto drop; x = xfrm_input_state(skb); if (unlikely(!x)) goto drop; if (x->props.mode != XFRM_MODE_TRANSPORT && x->props.mode != XFRM_MODE_TUNNEL) goto drop; dlen = cn10k_ipsec_get_ip_data_len(x, skb); if (dlen == 0 && netif_msg_tx_err(pf)) { netdev_err(pf->netdev, "Invalid IP header, ip-length zero\n"); goto drop; } /* Check for valid SA context */ sa_info = (struct qmem *)x->xso.offload_handle; if (!sa_info) goto drop; memset(&inst, 0, sizeof(struct cpt_inst_s)); /* Get authentication offset */ if (x->props.family == AF_INET) auth_offset = sizeof(struct iphdr); else auth_offset = sizeof(struct ipv6hdr); /* IV offset is after ESP header */ iv_offset = auth_offset + sizeof(struct ip_esp_hdr); /* Encap will start after IV */ encap_offset = iv_offset + GCM_RFC4106_IV_SIZE; /* CPT Instruction word-1 */ res = (struct cpt_res_s *)(sq->cpt_resp->base + (64 * sq->head)); res->compcode = 0; inst.res_addr = sq->cpt_resp->iova + (64 * sq->head); /* CPT Instruction word-2 */ inst.rvu_pf_func = pf->pcifunc; /* CPT Instruction word-3: * Set QORD to force CPT_RES_S write completion */ inst.qord = 1; /* CPT Instruction word-4 */ /* inst.dlen should not include ICV length */ inst.dlen = dlen + ETH_HLEN - (x->aead->alg_icv_len / 8); inst.opcode_major = CN10K_IPSEC_MAJOR_OP_OUTB_IPSEC; inst.param1 = cn10k_ipsec_get_param1(iv_offset); inst.param2 = encap_offset << CN10K_IPSEC_INST_PARAM2_ENC_DATA_OFFSET_SHIFT; inst.param2 |= (u16)auth_offset << CN10K_IPSEC_INST_PARAM2_AUTH_DATA_OFFSET_SHIFT; /* CPT Instruction word-5 */ gthr_size = num_segs / MAX_SEGS_PER_SG; gthr_size = (num_segs % MAX_SEGS_PER_SG) ? gthr_size + 1 : gthr_size; gthr_size &= 0xF; dptr_iova = (sq->sqe_ring->iova + (sq->head * (sq->sqe_size * 2))); inst.dptr = dptr_iova | ((u64)gthr_size << 60); /* CPT Instruction word-6 */ inst.rptr = inst.dptr; /* CPT Instruction word-7 */ inst.cptr = sa_info->iova; inst.ctx_val = 1; inst.egrp = CN10K_DEF_CPT_IPSEC_EGRP; /* CPT Instruction word-0 */ inst.nixtxl = (size / 16) - 1; inst.dat_offset = ETH_HLEN; inst.nixtx_offset = sq->sqe_size; netdev_tx_sent_queue(txq, skb->len); /* Finally Flush the CPT instruction */ sq->head++; sq->head &= (sq->sqe_cnt - 1); cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s)); return true; drop: dev_kfree_skb_any(skb); return false; }