[net-next PATCH v1 15/15] octeontx2-pf: ipsec: Add XFRM state and policy hooks for inbound flows

Tanmay Jagdale posted 15 patches 9 months, 1 week ago
[net-next PATCH v1 15/15] octeontx2-pf: ipsec: Add XFRM state and policy hooks for inbound flows
Posted by Tanmay Jagdale 9 months, 1 week ago
Add XFRM state hook for inbound flows and configure the following:
  - Install an NPC rule to classify the 1st pass IPsec packets and
    direct them to the dedicated RQ
  - Allocate a free entry from the SA table and populate it with the
    SA context details based on xfrm state data.
  - Create a mapping of the SPI value to the SA table index. This is
    used by NIXRX to calculate the exact SA context  pointer address
    based on the SPI in the packet.
  - Prepare the CPT SA context to decrypt buffer in place and the
    write it the CPT hardware via LMT operation.
  - When the XFRM state is deleted, clear this SA in CPT hardware.

Also add XFRM Policy hooks to allow successful offload of inbound
PACKET_MODE.

Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
---
 .../marvell/octeontx2/nic/cn10k_ipsec.c       | 449 ++++++++++++++++--
 1 file changed, 419 insertions(+), 30 deletions(-)

diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
index bebf5cdedee4..6441598c7e0f 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
@@ -448,7 +448,7 @@ static int cn10k_inb_alloc_mcam_entry(struct otx2_nic *pfvf,
 	return err;
 }
 
-static int cn10k_inb_install_flow(struct otx2_nic *pfvf, struct xfrm_state *x,
+static int cn10k_inb_install_flow(struct otx2_nic *pfvf,
 				  struct cn10k_inb_sw_ctx_info *inb_ctx_info)
 {
 	struct npc_install_flow_req *req;
@@ -463,14 +463,14 @@ static int cn10k_inb_install_flow(struct otx2_nic *pfvf, struct xfrm_state *x,
 	}
 
 	req->entry = inb_ctx_info->npc_mcam_entry;
-	req->features |= BIT(NPC_IPPROTO_ESP) | BIT(NPC_IPSEC_SPI) | BIT(NPC_DMAC);
+	req->features |= BIT(NPC_IPPROTO_ESP) | BIT(NPC_IPSEC_SPI);
 	req->intf = NIX_INTF_RX;
 	req->index = pfvf->ipsec.inb_ipsec_rq;
 	req->match_id = 0xfeed;
 	req->channel = pfvf->hw.rx_chan_base;
 	req->op = NIX_RX_ACTIONOP_UCAST_IPSEC;
 	req->set_cntr = 1;
-	req->packet.spi = x->id.spi;
+	req->packet.spi = inb_ctx_info->spi;
 	req->mask.spi = 0xffffffff;
 
 	/* Send message to AF */
@@ -993,7 +993,7 @@ static int cn10k_inb_cpt_init(struct net_device *netdev)
 	 */
 	list_for_each_entry(inb_ctx_info, &pfvf->ipsec.inb_sw_ctx_list, list) {
 		cn10k_inb_ena_dis_flow(pfvf, inb_ctx_info, false);
-		cn10k_inb_install_flow(pfvf, inb_ctx_info->x_state, inb_ctx_info);
+		cn10k_inb_install_flow(pfvf, inb_ctx_info);
 		cn10k_inb_install_spi_to_sa_match_entry(pfvf,
 							inb_ctx_info->x_state,
 							inb_ctx_info);
@@ -1035,6 +1035,19 @@ static int cn10k_outb_cpt_clean(struct otx2_nic *pf)
 	return ret;
 }
 
+static u32 cn10k_inb_alloc_sa(struct otx2_nic *pf, struct xfrm_state *x)
+{
+	u32 sa_index = 0;
+
+	sa_index = find_first_zero_bit(pf->ipsec.inb_sa_table, CN10K_IPSEC_INB_MAX_SA);
+	if (sa_index >= CN10K_IPSEC_INB_MAX_SA)
+		return sa_index;
+
+	set_bit(sa_index, pf->ipsec.inb_sa_table);
+
+	return sa_index;
+}
+
 static void cn10k_cpt_inst_flush(struct otx2_nic *pf, struct cpt_inst_s *inst,
 				 u64 size)
 {
@@ -1149,6 +1162,137 @@ static int cn10k_outb_write_sa(struct otx2_nic *pf, struct qmem *sa_info)
 	return ret;
 }
 
+static int cn10k_inb_write_sa(struct otx2_nic *pf,
+			      struct xfrm_state *x,
+			      struct cn10k_inb_sw_ctx_info *inb_ctx_info)
+{
+	dma_addr_t res_iova, dptr_iova, sa_iova;
+	struct cn10k_rx_sa_s *sa_dptr, *sa_cptr;
+	struct cpt_inst_s inst;
+	u32 sa_size, off;
+	struct cpt_res_s *res;
+	u64 reg_val;
+	int ret;
+
+	res = dma_alloc_coherent(pf->dev, sizeof(struct cpt_res_s),
+				 &res_iova, GFP_ATOMIC);
+	if (!res)
+		return -ENOMEM;
+
+	sa_cptr = inb_ctx_info->sa_entry;
+	sa_iova = inb_ctx_info->sa_iova;
+	sa_size = sizeof(struct cn10k_rx_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;
+	}
+
+	for (off = 0; off < (sa_size / 8); off++)
+		*((u64 *)sa_dptr + off) = cpu_to_be64(*((u64 *)sa_cptr + off));
+
+	memset(&inst, 0, sizeof(struct cpt_inst_s));
+
+	res->compcode = 0;
+	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;
+
+	/* Re-use Outbound CPT LF to install Ingress SAs as well because
+	 * the driver does not own the ingress CPT LF.
+	 */
+	pf->ipsec.io_addr = (__force u64)otx2_get_regaddr(pf, CN10K_CPT_LF_NQX(0));
+	cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s));
+	dmb(sy);
+
+	ret = cn10k_wait_for_cpt_respose(pf, res);
+	if (ret)
+		goto out;
+
+	/* Trigger CTX flush to write dirty data back to DRAM */
+	reg_val = FIELD_PREP(GENMASK_ULL(45, 0), sa_iova >> 7);
+	otx2_write64(pf, CN10K_CPT_LF_CTX_FLUSH, reg_val);
+
+out:
+	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 void cn10k_xfrm_inb_prepare_sa(struct otx2_nic *pf, struct xfrm_state *x,
+				      struct cn10k_inb_sw_ctx_info *inb_ctx_info)
+{
+	struct cn10k_rx_sa_s *sa_entry = inb_ctx_info->sa_entry;
+	int key_len = (x->aead->alg_key_len + 7) / 8;
+	u8 *key = x->aead->alg_key;
+	u32 sa_size = sizeof(struct cn10k_rx_sa_s);
+	u64 *tmp_key;
+	u32 *tmp_salt;
+	int idx;
+
+	memset(sa_entry, 0, sizeof(struct cn10k_rx_sa_s));
+
+	/* Disable ESN for now */
+	sa_entry->esn_en = 0;
+
+	/* HW context offset is word-31 */
+	sa_entry->hw_ctx_off = 31;
+	sa_entry->pkind = NPC_RX_CPT_HDR_PKIND;
+	sa_entry->eth_ovrwr = 1;
+	sa_entry->pkt_output = 1;
+	sa_entry->pkt_format = 1;
+	sa_entry->orig_pkt_free = 0;
+	/* context push size is up to word 31 */
+	sa_entry->ctx_push_size = 31 + 1;
+	/* context size, 128 Byte aligned up */
+	sa_entry->ctx_size = (sa_size / OTX2_ALIGN)  & 0xF;
+
+	sa_entry->cookie = inb_ctx_info->sa_index;
+
+	/* 1 word (??) prepanded to context header size */
+	sa_entry->ctx_hdr_size = 1;
+	/* Mark SA entry valid */
+	sa_entry->aop_valid = 1;
+
+	sa_entry->sa_dir = 0;			/* Inbound */
+	sa_entry->ipsec_protocol = 1;		/* ESP */
+	/* Default to Transport Mode */
+	if (x->props.mode == XFRM_MODE_TUNNEL)
+		sa_entry->ipsec_mode = 1;	/* Tunnel Mode */
+
+	sa_entry->et_ovrwr_ddr_en = 1;
+	sa_entry->enc_type = 5;			/* AES-GCM only */
+	sa_entry->aes_key_len = 1;		/* AES key length 128 */
+	sa_entry->l2_l3_hdr_on_error = 1;
+	sa_entry->spi = cpu_to_be32(x->id.spi);
+
+	/* Last 4 bytes are salt */
+	key_len -= 4;
+	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] = be64_to_cpu(tmp_key[idx]);
+
+	memcpy(&sa_entry->iv_gcm_salt, key + key_len, 4);
+	tmp_salt = (u32 *)&sa_entry->iv_gcm_salt;
+	*tmp_salt = be32_to_cpu(*tmp_salt);
+
+	/* Write SA context data to memory before enabling */
+	wmb();
+
+	/* Enable SA */
+	sa_entry->sa_valid = 1;
+}
+
 static int cn10k_ipsec_get_hw_ctx_offset(void)
 {
 	/* Offset on Hardware-context offset in word */
@@ -1256,11 +1400,6 @@ static int cn10k_ipsec_validate_state(struct xfrm_state *x,
 				   "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,
@@ -1272,11 +1411,6 @@ static int cn10k_ipsec_validate_state(struct xfrm_state *x,
 				   "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");
@@ -1316,8 +1450,96 @@ static int cn10k_ipsec_validate_state(struct xfrm_state *x,
 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;
+	struct net_device *netdev = x->xso.dev;
+	struct cn10k_inb_sw_ctx_info *inb_ctx_info = NULL, *inb_ctx;
+	bool enable_rule = false;
+	struct otx2_nic *pf;
+	u64 *sa_offset_ptr;
+	u32 sa_index = 0;
+	int err = 0;
+
+	pf = netdev_priv(netdev);
+
+	/* If XFRM policy was added before state, then the inb_ctx_info instance
+	 * would be allocated there.
+	 */
+	list_for_each_entry(inb_ctx, &pf->ipsec.inb_sw_ctx_list, list) {
+		if (inb_ctx->spi == x->id.spi) {
+			inb_ctx_info = inb_ctx;
+			enable_rule = true;
+			break;
+		}
+	}
+
+	if (!inb_ctx_info) {
+		/* Allocate a structure to track SA related info in driver */
+		inb_ctx_info = devm_kzalloc(pf->dev, sizeof(*inb_ctx_info), GFP_KERNEL);
+		if (!inb_ctx_info)
+			return -ENOMEM;
+
+		/* Stash pointer in the xfrm offload handle */
+		x->xso.offload_handle = (unsigned long)inb_ctx_info;
+	}
+
+	sa_index = cn10k_inb_alloc_sa(pf, x);
+	if (sa_index >= CN10K_IPSEC_INB_MAX_SA) {
+		netdev_err(netdev, "Failed to find free entry in SA Table\n");
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	/* Fill in information for bookkeeping */
+	inb_ctx_info->sa_index = sa_index;
+	inb_ctx_info->spi = x->id.spi;
+	inb_ctx_info->sa_entry = pf->ipsec.inb_sa->base +
+				 (sa_index * pf->ipsec.sa_tbl_entry_sz);
+	inb_ctx_info->sa_iova = pf->ipsec.inb_sa->iova +
+				(sa_index * pf->ipsec.sa_tbl_entry_sz);
+	inb_ctx_info->x_state = x;
+
+	/* Store XFRM state pointer in SA context at an offset of 1KB.
+	 * It will be later used in the rcv_pkt_handler to associate
+	 * an skb with XFRM state.
+	 */
+	sa_offset_ptr = pf->ipsec.inb_sa->base +
+		 (sa_index * pf->ipsec.sa_tbl_entry_sz) + 1024;
+	*sa_offset_ptr = (u64)x;
+
+	err = cn10k_inb_install_spi_to_sa_match_entry(pf, x, inb_ctx_info);
+	if (err) {
+		netdev_err(netdev, "Failed to install Inbound IPSec exact match entry\n");
+		goto err_out;
+	}
+
+	cn10k_xfrm_inb_prepare_sa(pf, x, inb_ctx_info);
+
+	netdev_dbg(netdev, "inb_ctx_info: sa_index:%d spi:0x%x mcam_entry:%d"
+		   " hash_index:0x%x way:0x%x\n",
+		   inb_ctx_info->sa_index, inb_ctx_info->spi,
+		   inb_ctx_info->npc_mcam_entry, inb_ctx_info->hash_index,
+		   inb_ctx_info->way);
+
+	err = cn10k_inb_write_sa(pf, x, inb_ctx_info);
+	if (err)
+		netdev_err(netdev, "Error writing inbound SA\n");
+
+	/* Enable NPC rule if policy was already installed */
+	if (enable_rule) {
+		err = cn10k_inb_ena_dis_flow(pf, inb_ctx_info, false);
+		if (err)
+			netdev_err(netdev, "Failed to enable rule\n");
+	} else {
+		/* All set, add ctx_info to the list */
+		list_add_tail(&inb_ctx_info->list, &pf->ipsec.inb_sw_ctx_list);
+	}
+
+	cn10k_cpt_device_set_available(pf);
+	return err;
+
+err_out:
+	x->xso.offload_handle = 0;
+	devm_kfree(pf->dev, inb_ctx_info);
+	return err;
 }
 
 static int cn10k_ipsec_outb_add_state(struct xfrm_state *x,
@@ -1329,10 +1551,6 @@ static int cn10k_ipsec_outb_add_state(struct xfrm_state *x,
 	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);
@@ -1360,10 +1578,52 @@ static int cn10k_ipsec_outb_add_state(struct xfrm_state *x,
 static int cn10k_ipsec_add_state(struct xfrm_state *x,
 				 struct netlink_ext_ack *extack)
 {
+	int err;
+
+	err = cn10k_ipsec_validate_state(x, extack);
+	if (err)
+		return err;
+
 	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);
+
+	return err;
+}
+
+static void cn10k_ipsec_inb_del_state(struct otx2_nic *pf, struct xfrm_state *x)
+{
+	struct cn10k_inb_sw_ctx_info *inb_ctx_info;
+	struct cn10k_rx_sa_s *sa_entry;
+	struct net_device *netdev = x->xso.dev;
+	int err = 0;
+
+	/* 1. Find SPI to SA entry */
+	inb_ctx_info = (struct cn10k_inb_sw_ctx_info *)x->xso.offload_handle;
+
+	if (inb_ctx_info->spi != be32_to_cpu(x->id.spi)) {
+		netdev_err(netdev, "SPI Mismatch (ctx) 0x%x != 0x%x (xfrm)\n",
+			   inb_ctx_info->spi, be32_to_cpu(x->id.spi));
+		return;
+	}
+
+	/* 2. Delete SA in CPT HW */
+	sa_entry = inb_ctx_info->sa_entry;
+	memset(sa_entry, 0, sizeof(struct cn10k_rx_sa_s));
+
+	sa_entry->ctx_push_size = 31 + 1;
+	sa_entry->ctx_size = (sizeof(struct cn10k_rx_sa_s) / OTX2_ALIGN) & 0xF;
+	sa_entry->aop_valid = 1;
+
+	if (cn10k_cpt_device_set_inuse(pf)) {
+		err = cn10k_inb_write_sa(pf, x, inb_ctx_info);
+		if (err)
+			netdev_err(netdev, "Error (%d) deleting INB SA\n", err);
+		cn10k_cpt_device_set_available(pf);
+	}
+
+	x->xso.offload_handle = 0;
 }
 
 static void cn10k_ipsec_del_state(struct xfrm_state *x)
@@ -1374,11 +1634,11 @@ static void cn10k_ipsec_del_state(struct xfrm_state *x)
 	struct otx2_nic *pf;
 	int err;
 
-	if (x->xso.dir == XFRM_DEV_OFFLOAD_IN)
-		return;
-
 	pf = netdev_priv(netdev);
 
+	if (x->xso.dir == XFRM_DEV_OFFLOAD_IN)
+		return cn10k_ipsec_inb_del_state(pf, x);
+
 	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));
@@ -1397,13 +1657,112 @@ static void cn10k_ipsec_del_state(struct xfrm_state *x)
 	/* 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);
+	pf->ipsec.outb_sa_count--;
+	queue_work(pf->ipsec.sa_workq, &pf->ipsec.sa_work);
+}
+
+static int cn10k_ipsec_policy_add(struct xfrm_policy *x,
+				  struct netlink_ext_ack *extack)
+{
+	struct cn10k_inb_sw_ctx_info *inb_ctx_info = NULL, *inb_ctx;
+	struct net_device *netdev = x->xdo.dev;
+	struct otx2_nic *pf;
+	int ret = 0;
+	bool disable_rule = true;
+
+	if (x->xdo.dir != XFRM_DEV_OFFLOAD_IN) {
+		netdev_err(netdev, "ERR: Can only offload Inbound policies\n");
+		ret = -EINVAL;
+	}
+
+	if (x->xdo.type != XFRM_DEV_OFFLOAD_PACKET) {
+		netdev_err(netdev, "ERR: Only Packet mode supported\n");
+		ret = -EINVAL;
+	}
+
+	pf = netdev_priv(netdev);
+
+	/* If XFRM state was added before policy, then the inb_ctx_info instance
+	 * would be allocated there.
+	 */
+	list_for_each_entry(inb_ctx, &pf->ipsec.inb_sw_ctx_list, list) {
+		if (inb_ctx->spi == x->xfrm_vec[0].id.spi) {
+			inb_ctx_info = inb_ctx;
+			disable_rule = false;
+			break;
+		}
+	}
+
+	if (!inb_ctx_info) {
+		/* Allocate a structure to track SA related info in driver */
+		inb_ctx_info = devm_kzalloc(pf->dev, sizeof(*inb_ctx_info), GFP_KERNEL);
+		if (!inb_ctx_info)
+			return -ENOMEM;
+
+		inb_ctx_info->spi = x->xfrm_vec[0].id.spi;
+	}
+
+	ret = cn10k_inb_alloc_mcam_entry(pf, inb_ctx_info);
+	if (ret) {
+		netdev_err(netdev, "Failed to allocate MCAM entry for Inbound IPSec flow\n");
+		goto err_out;
+	}
+
+	ret = cn10k_inb_install_flow(pf, inb_ctx_info);
+	if (ret) {
+		netdev_err(netdev, "Failed to install Inbound IPSec flow\n");
+		goto err_out;
+	}
+
+	/* Leave rule in a disabled state until xfrm_state add is completed */
+	if (disable_rule) {
+		ret = cn10k_inb_ena_dis_flow(pf, inb_ctx_info, true);
+		if (ret)
+			netdev_err(netdev, "Failed to disable rule\n");
+
+		/* All set, add ctx_info to the list */
+		list_add_tail(&inb_ctx_info->list, &pf->ipsec.inb_sw_ctx_list);
+	}
+
+	/* Stash pointer in the xfrm offload handle */
+	x->xdo.offload_handle = (unsigned long)inb_ctx_info;
+
+err_out:
+	return ret;
+}
+
+static void cn10k_ipsec_policy_delete(struct xfrm_policy *x)
+{
+	struct cn10k_inb_sw_ctx_info *inb_ctx_info;
+	struct net_device *netdev = x->xdo.dev;
+	struct otx2_nic *pf;
+
+	if (!x->xdo.offload_handle)
+		return;
+
+	pf = netdev_priv(netdev);
+	inb_ctx_info = (struct cn10k_inb_sw_ctx_info *)x->xdo.offload_handle;
+
+	/* Schedule a workqueue to free NPC rule and SPI-to-SA match table
+	 * entry because they are freed via a mailbox call which can sleep
+	 * and the delete policy routine from XFRM stack is called in an
+	 * atomic context.
+	 */
+	inb_ctx_info->delete_npc_and_match_entry = true;
+	queue_work(pf->ipsec.sa_workq, &pf->ipsec.sa_work);
+}
+
+static void cn10k_ipsec_policy_free(struct xfrm_policy *x)
+{
+	return;
 }
 
 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_policy_add	= cn10k_ipsec_policy_add,
+	.xdo_dev_policy_delete	= cn10k_ipsec_policy_delete,
+	.xdo_dev_policy_free	= cn10k_ipsec_policy_free,
 };
 
 static void cn10k_ipsec_sa_wq_handler(struct work_struct *work)
@@ -1411,12 +1770,42 @@ 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);
+	struct cn10k_inb_sw_ctx_info *inb_ctx_info, *tmp;
+	int err;
+
+	list_for_each_entry_safe(inb_ctx_info, tmp, &pf->ipsec.inb_sw_ctx_list,
+				 list) {
+		if (!inb_ctx_info->delete_npc_and_match_entry)
+			continue;
+
+		/* 3. Delete all the associated NPC rules associated */
+		err = cn10k_inb_delete_flow(pf, inb_ctx_info);
+		if (err) {
+			netdev_err(pf->netdev, "Failed to free UCAST_IPSEC entry %d\n",
+				   inb_ctx_info->npc_mcam_entry);
+		}
+
+		/* 4. Remove SPI_TO_SA exact match entry */
+		err = cn10k_inb_delete_spi_to_sa_match_entry(pf, inb_ctx_info);
+		if (err)
+			netdev_err(pf->netdev, "Failed to delete spi_to_sa_match_entry\n");
+
+		inb_ctx_info->delete_npc_and_match_entry = false;
+
+		/* 5. Finally clear the entry from the SA Table */
+		clear_bit(inb_ctx_info->sa_index, pf->ipsec.inb_sa_table);
 
-	/* Disable static branch when no more SA enabled */
-	static_branch_disable(&cn10k_ipsec_sa_enabled);
-	rtnl_lock();
-	netdev_update_features(pf->netdev);
-	rtnl_unlock();
+		/* 6. Free the inb_ctx_info */
+		list_del(&inb_ctx_info->list);
+		devm_kfree(pf->dev, inb_ctx_info);
+	}
+
+	if (list_empty(&pf->ipsec.inb_sw_ctx_list) && !pf->ipsec.outb_sa_count) {
+		static_branch_disable(&cn10k_ipsec_sa_enabled);
+		rtnl_lock();
+		netdev_update_features(pf->netdev);
+		rtnl_unlock();
+	}
 }
 
 static int cn10k_ipsec_configure_cpt_bpid(struct otx2_nic *pfvf)
-- 
2.43.0
Re: [net-next PATCH v1 15/15] octeontx2-pf: ipsec: Add XFRM state and policy hooks for inbound flows
Posted by Simon Horman 9 months, 1 week ago
On Fri, May 02, 2025 at 06:49:56PM +0530, Tanmay Jagdale wrote:
> Add XFRM state hook for inbound flows and configure the following:
>   - Install an NPC rule to classify the 1st pass IPsec packets and
>     direct them to the dedicated RQ
>   - Allocate a free entry from the SA table and populate it with the
>     SA context details based on xfrm state data.
>   - Create a mapping of the SPI value to the SA table index. This is
>     used by NIXRX to calculate the exact SA context  pointer address
>     based on the SPI in the packet.
>   - Prepare the CPT SA context to decrypt buffer in place and the
>     write it the CPT hardware via LMT operation.
>   - When the XFRM state is deleted, clear this SA in CPT hardware.
> 
> Also add XFRM Policy hooks to allow successful offload of inbound
> PACKET_MODE.
> 
> Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
> ---
>  .../marvell/octeontx2/nic/cn10k_ipsec.c       | 449 ++++++++++++++++--
>  1 file changed, 419 insertions(+), 30 deletions(-)
> 
> diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
> index bebf5cdedee4..6441598c7e0f 100644
> --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
> +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c
> @@ -448,7 +448,7 @@ static int cn10k_inb_alloc_mcam_entry(struct otx2_nic *pfvf,
>  	return err;
>  }
>  
> -static int cn10k_inb_install_flow(struct otx2_nic *pfvf, struct xfrm_state *x,
> +static int cn10k_inb_install_flow(struct otx2_nic *pfvf,
>  				  struct cn10k_inb_sw_ctx_info *inb_ctx_info)
>  {
>  	struct npc_install_flow_req *req;
> @@ -463,14 +463,14 @@ static int cn10k_inb_install_flow(struct otx2_nic *pfvf, struct xfrm_state *x,
>  	}
>  
>  	req->entry = inb_ctx_info->npc_mcam_entry;
> -	req->features |= BIT(NPC_IPPROTO_ESP) | BIT(NPC_IPSEC_SPI) | BIT(NPC_DMAC);
> +	req->features |= BIT(NPC_IPPROTO_ESP) | BIT(NPC_IPSEC_SPI);
>  	req->intf = NIX_INTF_RX;
>  	req->index = pfvf->ipsec.inb_ipsec_rq;
>  	req->match_id = 0xfeed;
>  	req->channel = pfvf->hw.rx_chan_base;
>  	req->op = NIX_RX_ACTIONOP_UCAST_IPSEC;
>  	req->set_cntr = 1;
> -	req->packet.spi = x->id.spi;
> +	req->packet.spi = inb_ctx_info->spi;

I think this should be:

	req->packet.spi = cpu_to_be32(inb_ctx_info->spi);

Flagged by Sparse.

Please also take a look at other Sparse warnings added by this patch (set).

>  	req->mask.spi = 0xffffffff;
>  
>  	/* Send message to AF */

...

> +static int cn10k_inb_write_sa(struct otx2_nic *pf,
> +			      struct xfrm_state *x,
> +			      struct cn10k_inb_sw_ctx_info *inb_ctx_info)
> +{
> +	dma_addr_t res_iova, dptr_iova, sa_iova;
> +	struct cn10k_rx_sa_s *sa_dptr, *sa_cptr;
> +	struct cpt_inst_s inst;
> +	u32 sa_size, off;
> +	struct cpt_res_s *res;
> +	u64 reg_val;
> +	int ret;
> +
> +	res = dma_alloc_coherent(pf->dev, sizeof(struct cpt_res_s),
> +				 &res_iova, GFP_ATOMIC);
> +	if (!res)
> +		return -ENOMEM;
> +
> +	sa_cptr = inb_ctx_info->sa_entry;
> +	sa_iova = inb_ctx_info->sa_iova;
> +	sa_size = sizeof(struct cn10k_rx_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;
> +	}
> +
> +	for (off = 0; off < (sa_size / 8); off++)
> +		*((u64 *)sa_dptr + off) = cpu_to_be64(*((u64 *)sa_cptr + off));
> +
> +	memset(&inst, 0, sizeof(struct cpt_inst_s));
> +
> +	res->compcode = 0;
> +	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;
> +
> +	/* Re-use Outbound CPT LF to install Ingress SAs as well because
> +	 * the driver does not own the ingress CPT LF.
> +	 */
> +	pf->ipsec.io_addr = (__force u64)otx2_get_regaddr(pf, CN10K_CPT_LF_NQX(0));

I suspect this indicates that io_addr should have an __iomem annotation.
And users should be updated accordingly.

> +	cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s));
> +	dmb(sy);
> +
> +	ret = cn10k_wait_for_cpt_respose(pf, res);
> +	if (ret)
> +		goto out;
> +
> +	/* Trigger CTX flush to write dirty data back to DRAM */
> +	reg_val = FIELD_PREP(GENMASK_ULL(45, 0), sa_iova >> 7);
> +	otx2_write64(pf, CN10K_CPT_LF_CTX_FLUSH, reg_val);
> +
> +out:
> +	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 void cn10k_xfrm_inb_prepare_sa(struct otx2_nic *pf, struct xfrm_state *x,
> +				      struct cn10k_inb_sw_ctx_info *inb_ctx_info)
> +{
> +	struct cn10k_rx_sa_s *sa_entry = inb_ctx_info->sa_entry;
> +	int key_len = (x->aead->alg_key_len + 7) / 8;
> +	u8 *key = x->aead->alg_key;
> +	u32 sa_size = sizeof(struct cn10k_rx_sa_s);
> +	u64 *tmp_key;
> +	u32 *tmp_salt;
> +	int idx;
> +
> +	memset(sa_entry, 0, sizeof(struct cn10k_rx_sa_s));
> +
> +	/* Disable ESN for now */
> +	sa_entry->esn_en = 0;
> +
> +	/* HW context offset is word-31 */
> +	sa_entry->hw_ctx_off = 31;
> +	sa_entry->pkind = NPC_RX_CPT_HDR_PKIND;
> +	sa_entry->eth_ovrwr = 1;
> +	sa_entry->pkt_output = 1;
> +	sa_entry->pkt_format = 1;
> +	sa_entry->orig_pkt_free = 0;
> +	/* context push size is up to word 31 */
> +	sa_entry->ctx_push_size = 31 + 1;
> +	/* context size, 128 Byte aligned up */
> +	sa_entry->ctx_size = (sa_size / OTX2_ALIGN)  & 0xF;
> +
> +	sa_entry->cookie = inb_ctx_info->sa_index;
> +
> +	/* 1 word (??) prepanded to context header size */
> +	sa_entry->ctx_hdr_size = 1;
> +	/* Mark SA entry valid */
> +	sa_entry->aop_valid = 1;
> +
> +	sa_entry->sa_dir = 0;			/* Inbound */
> +	sa_entry->ipsec_protocol = 1;		/* ESP */
> +	/* Default to Transport Mode */
> +	if (x->props.mode == XFRM_MODE_TUNNEL)
> +		sa_entry->ipsec_mode = 1;	/* Tunnel Mode */
> +
> +	sa_entry->et_ovrwr_ddr_en = 1;
> +	sa_entry->enc_type = 5;			/* AES-GCM only */
> +	sa_entry->aes_key_len = 1;		/* AES key length 128 */
> +	sa_entry->l2_l3_hdr_on_error = 1;
> +	sa_entry->spi = cpu_to_be32(x->id.spi);
> +
> +	/* Last 4 bytes are salt */
> +	key_len -= 4;
> +	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] = be64_to_cpu(tmp_key[idx]);
> +
> +	memcpy(&sa_entry->iv_gcm_salt, key + key_len, 4);
> +	tmp_salt = (u32 *)&sa_entry->iv_gcm_salt;
> +	*tmp_salt = be32_to_cpu(*tmp_salt);

Maybe I messed it up, but this seems clearer to me:

	void *key = x->aead->alg_key;

	...

	sa_entry->iv_gcm_salt = be32_to_cpup(key + key_len);


> +
> +	/* Write SA context data to memory before enabling */
> +	wmb();
> +
> +	/* Enable SA */
> +	sa_entry->sa_valid = 1;
> +}
> +
>  static int cn10k_ipsec_get_hw_ctx_offset(void)
>  {
>  	/* Offset on Hardware-context offset in word */

...

> @@ -1316,8 +1450,96 @@ static int cn10k_ipsec_validate_state(struct xfrm_state *x,
>  static int cn10k_ipsec_inb_add_state(struct xfrm_state *x,
>  				     struct netlink_ext_ack *extack)
>  {

...

> +	netdev_dbg(netdev, "inb_ctx_info: sa_index:%d spi:0x%x mcam_entry:%d"
> +		   " hash_index:0x%x way:0x%x\n",

Please don't split strings. It makes searching for them more difficult.
This is an exception to the 80 column line length rule.
Although you may want to consider making the string shorter.

...
Re: [net-next PATCH v1 15/15] octeontx2-pf: ipsec: Add XFRM state and policy hooks for inbound flows
Posted by kernel test robot 9 months, 1 week ago
Hi Tanmay,

kernel test robot noticed the following build errors:

[auto build test ERROR on net-next/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Tanmay-Jagdale/crypto-octeontx2-Share-engine-group-info-with-AF-driver/20250502-213203
base:   net-next/main
patch link:    https://lore.kernel.org/r/20250502132005.611698-16-tanmay%40marvell.com
patch subject: [net-next PATCH v1 15/15] octeontx2-pf: ipsec: Add XFRM state and policy hooks for inbound flows
config: loongarch-allmodconfig (https://download.01.org/0day-ci/archive/20250507/202505071416.T1HY7g1G-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250507/202505071416.T1HY7g1G-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202505071416.T1HY7g1G-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c: In function 'cn10k_ipsec_npa_refill_inb_ipsecq':
   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c:868:27: warning: variable 'qset' set but not used [-Wunused-but-set-variable]
     868 |         struct otx2_qset *qset = NULL;
         |                           ^~~~
   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c: In function 'cn10k_inb_cpt_init':
   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c:932:15: warning: variable 'ptr' set but not used [-Wunused-but-set-variable]
     932 |         void *ptr;
         |               ^~~
   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c: In function 'cn10k_inb_write_sa':
>> drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c:1214:9: error: implicit declaration of function 'dmb'; did you mean 'rmb'? [-Wimplicit-function-declaration]
    1214 |         dmb(sy);
         |         ^~~
         |         rmb
>> drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c:1214:13: error: 'sy' undeclared (first use in this function); did you mean 's8'?
    1214 |         dmb(sy);
         |             ^~
         |             s8
   drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c:1214:13: note: each undeclared identifier is reported only once for each function it appears in


vim +1214 drivers/net/ethernet/marvell/octeontx2/nic/cn10k_ipsec.c

  1164	
  1165	static int cn10k_inb_write_sa(struct otx2_nic *pf,
  1166				      struct xfrm_state *x,
  1167				      struct cn10k_inb_sw_ctx_info *inb_ctx_info)
  1168	{
  1169		dma_addr_t res_iova, dptr_iova, sa_iova;
  1170		struct cn10k_rx_sa_s *sa_dptr, *sa_cptr;
  1171		struct cpt_inst_s inst;
  1172		u32 sa_size, off;
  1173		struct cpt_res_s *res;
  1174		u64 reg_val;
  1175		int ret;
  1176	
  1177		res = dma_alloc_coherent(pf->dev, sizeof(struct cpt_res_s),
  1178					 &res_iova, GFP_ATOMIC);
  1179		if (!res)
  1180			return -ENOMEM;
  1181	
  1182		sa_cptr = inb_ctx_info->sa_entry;
  1183		sa_iova = inb_ctx_info->sa_iova;
  1184		sa_size = sizeof(struct cn10k_rx_sa_s);
  1185	
  1186		sa_dptr = dma_alloc_coherent(pf->dev, sa_size, &dptr_iova, GFP_ATOMIC);
  1187		if (!sa_dptr) {
  1188			dma_free_coherent(pf->dev, sizeof(struct cpt_res_s), res,
  1189					  res_iova);
  1190			return -ENOMEM;
  1191		}
  1192	
  1193		for (off = 0; off < (sa_size / 8); off++)
  1194			*((u64 *)sa_dptr + off) = cpu_to_be64(*((u64 *)sa_cptr + off));
  1195	
  1196		memset(&inst, 0, sizeof(struct cpt_inst_s));
  1197	
  1198		res->compcode = 0;
  1199		inst.res_addr = res_iova;
  1200		inst.dptr = (u64)dptr_iova;
  1201		inst.param2 = sa_size >> 3;
  1202		inst.dlen = sa_size;
  1203		inst.opcode_major = CN10K_IPSEC_MAJOR_OP_WRITE_SA;
  1204		inst.opcode_minor = CN10K_IPSEC_MINOR_OP_WRITE_SA;
  1205		inst.cptr = sa_iova;
  1206		inst.ctx_val = 1;
  1207		inst.egrp = CN10K_DEF_CPT_IPSEC_EGRP;
  1208	
  1209		/* Re-use Outbound CPT LF to install Ingress SAs as well because
  1210		 * the driver does not own the ingress CPT LF.
  1211		 */
  1212		pf->ipsec.io_addr = (__force u64)otx2_get_regaddr(pf, CN10K_CPT_LF_NQX(0));
  1213		cn10k_cpt_inst_flush(pf, &inst, sizeof(struct cpt_inst_s));
> 1214		dmb(sy);
  1215	
  1216		ret = cn10k_wait_for_cpt_respose(pf, res);
  1217		if (ret)
  1218			goto out;
  1219	
  1220		/* Trigger CTX flush to write dirty data back to DRAM */
  1221		reg_val = FIELD_PREP(GENMASK_ULL(45, 0), sa_iova >> 7);
  1222		otx2_write64(pf, CN10K_CPT_LF_CTX_FLUSH, reg_val);
  1223	
  1224	out:
  1225		dma_free_coherent(pf->dev, sa_size, sa_dptr, dptr_iova);
  1226		dma_free_coherent(pf->dev, sizeof(struct cpt_res_s), res, res_iova);
  1227		return ret;
  1228	}
  1229	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki