diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 42da7191..a3ea4f6d 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -249,6 +249,7 @@ __LIB__libodp_linux_la_SOURCES = \
 			   odp_impl.c \
 			   odp_ipsec.c \
 			   odp_ipsec_events.c \
+			   odp_ipsec_sad.c \
 			   odp_name_table.c \
 			   odp_packet.c \
 			   odp_packet_flags.c \
diff --git a/platform/linux-generic/include/odp_internal.h b/platform/linux-generic/include/odp_internal.h
index f916631b..8725dec8 100644
--- a/platform/linux-generic/include/odp_internal.h
+++ b/platform/linux-generic/include/odp_internal.h
@@ -75,6 +75,7 @@ enum init_stage {
 	TRAFFIC_MNGR_INIT,
 	NAME_TABLE_INIT,
 	IPSEC_EVENTS_INIT,
+	IPSEC_SAD_INIT,
 	MODULES_INIT,
 	ALL_INIT      /* All init stages completed */
 };
@@ -134,6 +135,9 @@ int _odp_ishm_init_local(void);
 int _odp_ishm_term_global(void);
 int _odp_ishm_term_local(void);
 
+int _odp_ipsec_sad_init_global(void);
+int _odp_ipsec_sad_term_global(void);
+
 int _odp_ipsec_events_init_global(void);
 int _odp_ipsec_events_term_global(void);
 
diff --git a/platform/linux-generic/include/odp_ipsec_internal.h b/platform/linux-generic/include/odp_ipsec_internal.h
index b31f048f..64ef8ab7 100644
--- a/platform/linux-generic/include/odp_ipsec_internal.h
+++ b/platform/linux-generic/include/odp_ipsec_internal.h
@@ -20,7 +20,9 @@ extern "C" {
 #include <odp/api/std_types.h>
 #include <odp/api/plat/strong_types.h>
 
+#include <odp/api/byteorder.h>
 #include <odp/api/ipsec.h>
+#include <odp/api/ticketlock.h>
 
 /** @ingroup odp_ipsec
  *  @{
@@ -31,6 +33,8 @@ typedef ODP_HANDLE_T(ipsec_status_t);
 #define ODP_IPSEC_STATUS_INVALID \
 	_odp_cast_scalar(ipsec_status_t, 0xffffffff)
 
+typedef struct ipsec_sa_s ipsec_sa_t;
+
 /**
  * @internal Get ipsec_status handle from event
  *
@@ -73,6 +77,111 @@ int _odp_ipsec_status_send(odp_queue_t queue,
 			   int result,
 			   odp_ipsec_warn_t warn);
 
+#define IPSEC_MAX_IV_LEN	32   /**< Maximum IV length in bytes */
+
+#define IPSEC_MAX_SALT_LEN	4    /**< Maximum salt length in bytes */
+
+/**
+ * Maximum number of available SAs
+ */
+#define ODP_CONFIG_IPSEC_SAS	8
+
+struct ipsec_sa_s {
+	odp_atomic_u32_t state ODP_ALIGNED_CACHE;
+
+	uint32_t	ipsec_sa_idx;
+	odp_ipsec_sa_t	ipsec_sa_hdl;
+
+	odp_ipsec_protocol_t proto;
+	uint32_t	spi;
+
+	odp_ipsec_mode_t mode;
+
+	/* Limits */
+	uint64_t soft_limit_bytes;
+	uint64_t soft_limit_packets;
+	uint64_t hard_limit_bytes;
+	uint64_t hard_limit_packets;
+
+	/* Statistics for soft/hard expiration */
+	odp_atomic_u64_t bytes;
+	odp_atomic_u64_t packets;
+
+	odp_crypto_session_t session;
+	void		*context;
+	odp_queue_t	queue;
+
+	uint32_t	icv_len;
+	uint32_t	esp_iv_len;
+	uint32_t	esp_block_len;
+
+	uint8_t		salt[IPSEC_MAX_SALT_LEN];
+	uint32_t	salt_length;
+
+	unsigned	dec_ttl : 1;
+	unsigned	copy_dscp : 1;
+	unsigned	copy_df : 1;
+
+	union {
+		struct {
+			odp_ipsec_lookup_mode_t lookup_mode;
+			odp_u32be_t	lookup_dst_ip;
+		} in;
+
+		struct {
+			odp_u32be_t	tun_src_ip;
+			odp_u32be_t	tun_dst_ip;
+
+			/* 32-bit from which low 16 are used */
+			odp_atomic_u32_t tun_hdr_id;
+			odp_atomic_u32_t seq;
+
+			uint8_t		tun_ttl;
+			uint8_t		tun_dscp;
+			uint8_t		tun_df;
+		} out;
+	};
+};
+
+/**
+ * IPSEC Security Association (SA) lookup parameters
+ */
+typedef struct odp_ipsec_sa_lookup_s {
+	/** IPSEC protocol: ESP or AH */
+	odp_ipsec_protocol_t proto;
+
+	/** SPI value */
+	uint32_t spi;
+
+	/* FIXME: IPv4 vs IPv6 */
+
+	/** IP destination address (NETWORK ENDIAN) */
+	void    *dst_addr;
+} ipsec_sa_lookup_t;
+
+/**
+ * Obtain SA reference
+ */
+ipsec_sa_t *_odp_ipsec_sa_use(odp_ipsec_sa_t sa);
+
+/**
+ * Release SA reference
+ */
+void _odp_ipsec_sa_unuse(ipsec_sa_t *ipsec_sa);
+
+/**
+ * Lookup SA corresponding to inbound packet pkt
+ */
+ipsec_sa_t *_odp_ipsec_sa_lookup(const ipsec_sa_lookup_t *lookup);
+
+/**
+ * Update SA usage statistics, filling respective status for the packet.
+ *
+ * @retval <0 if hard limits were breached
+ */
+int _odp_ipsec_sa_update_stats(ipsec_sa_t *ipsec_sa, uint32_t len,
+			       odp_ipsec_op_status_t *status);
+
 /**
  * @}
  */
diff --git a/platform/linux-generic/odp_init.c b/platform/linux-generic/odp_init.c
index 22bcbdc8..6e57c6e3 100644
--- a/platform/linux-generic/odp_init.c
+++ b/platform/linux-generic/odp_init.c
@@ -226,6 +226,12 @@ int odp_init_global(odp_instance_t *instance,
 	}
 	stage = IPSEC_EVENTS_INIT;
 
+	if (_odp_ipsec_sad_init_global()) {
+		ODP_ERR("ODP IPsec SAD init failed.\n");
+		goto init_failed;
+	}
+	stage = IPSEC_SAD_INIT;
+
 	if (_odp_modules_init_global()) {
 		ODP_ERR("ODP modules init failed\n");
 		goto init_failed;
@@ -256,6 +262,13 @@ int _odp_term_global(enum init_stage stage)
 	switch (stage) {
 	case ALL_INIT:
 	case MODULES_INIT:
+	case IPSEC_SAD_INIT:
+		if (_odp_ipsec_sad_term_global()) {
+			ODP_ERR("ODP IPsec SAD term failed.\n");
+			rc = -1;
+		}
+		/* Fall through */
+
 	case IPSEC_EVENTS_INIT:
 		if (_odp_ipsec_events_term_global()) {
 			ODP_ERR("ODP IPsec events term failed.\n");
diff --git a/platform/linux-generic/odp_ipsec.c b/platform/linux-generic/odp_ipsec.c
index f2757628..d0ca027c 100644
--- a/platform/linux-generic/odp_ipsec.c
+++ b/platform/linux-generic/odp_ipsec.c
@@ -49,32 +49,6 @@ int odp_ipsec_config(const odp_ipsec_config_t *config)
 	return -1;
 }
 
-void odp_ipsec_sa_param_init(odp_ipsec_sa_param_t *param)
-{
-	memset(param, 0, sizeof(odp_ipsec_sa_param_t));
-}
-
-odp_ipsec_sa_t odp_ipsec_sa_create(const odp_ipsec_sa_param_t *param)
-{
-	(void)param;
-
-	return ODP_IPSEC_SA_INVALID;
-}
-
-int odp_ipsec_sa_disable(odp_ipsec_sa_t sa)
-{
-	(void)sa;
-
-	return -1;
-}
-
-int odp_ipsec_sa_destroy(odp_ipsec_sa_t sa)
-{
-	(void)sa;
-
-	return -1;
-}
-
 int odp_ipsec_in(const odp_packet_t pkt_in[], int num_in,
 		 odp_packet_t pkt_out[], int *num_out,
 		 const odp_ipsec_in_param_t *param)
@@ -141,21 +115,6 @@ int odp_ipsec_result(odp_ipsec_packet_result_t *result, odp_packet_t packet)
 	return -1;
 }
 
-int odp_ipsec_mtu_update(odp_ipsec_sa_t sa, uint32_t mtu)
-{
-	(void)sa;
-	(void)mtu;
-
-	return -1;
-}
-
-void *odp_ipsec_sa_context(odp_ipsec_sa_t sa)
-{
-	(void)sa;
-
-	return NULL;
-}
-
 odp_packet_t odp_ipsec_packet_from_event(odp_event_t ev)
 {
 	(void)ev;
@@ -169,8 +128,3 @@ odp_event_t odp_ipsec_packet_to_event(odp_packet_t pkt)
 
 	return ODP_EVENT_INVALID;
 }
-
-uint64_t odp_ipsec_sa_to_u64(odp_ipsec_sa_t sa)
-{
-	return _odp_pri(sa);
-}
diff --git a/platform/linux-generic/odp_ipsec_sad.c b/platform/linux-generic/odp_ipsec_sad.c
new file mode 100644
index 00000000..34da960e
--- /dev/null
+++ b/platform/linux-generic/odp_ipsec_sad.c
@@ -0,0 +1,507 @@
+/* Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+#include <odp/api/atomic.h>
+#include <odp/api/ipsec.h>
+#include <odp/api/random.h>
+#include <odp/api/shared_memory.h>
+
+#include <odp_debug_internal.h>
+#include <odp_ipsec_internal.h>
+
+#include <string.h>
+
+#define IPSEC_SA_STATE_DISABLE	0x40000000
+#define IPSEC_SA_STATE_FREE	0xc0000000 /* This includes disable !!! */
+
+typedef struct ipsec_sa_table_t {
+	ipsec_sa_t ipsec_sa[ODP_CONFIG_IPSEC_SAS];
+	odp_shm_t shm;
+} ipsec_sa_table_t;
+
+static ipsec_sa_table_t *ipsec_sa_tbl;
+
+static inline
+ipsec_sa_t *ipsec_sa_entry(uint32_t ipsec_sa_idx)
+{
+	return &ipsec_sa_tbl->ipsec_sa[ipsec_sa_idx];
+}
+
+static inline
+ipsec_sa_t *ipsec_sa_entry_from_hdl(odp_ipsec_sa_t ipsec_sa_hdl)
+{
+	return ipsec_sa_entry(_odp_typeval(ipsec_sa_hdl));
+}
+
+static inline
+odp_ipsec_sa_t ipsec_sa_index_to_handle(uint32_t ipsec_sa_idx)
+{
+	return _odp_cast_scalar(odp_ipsec_sa_t, ipsec_sa_idx);
+}
+
+int _odp_ipsec_sad_init_global(void)
+{
+	odp_shm_t shm;
+	unsigned i;
+
+	shm = odp_shm_reserve("ipsec_sa_table",
+			      sizeof(ipsec_sa_table_t),
+			      ODP_CACHE_LINE_SIZE, 0);
+
+	ipsec_sa_tbl = odp_shm_addr(shm);
+	if (ipsec_sa_tbl == NULL)
+		return -1;
+
+	memset(ipsec_sa_tbl, 0, sizeof(ipsec_sa_table_t));
+	ipsec_sa_tbl->shm = shm;
+
+	for (i = 0; i < ODP_CONFIG_IPSEC_SAS; i++) {
+		ipsec_sa_t *ipsec_sa = ipsec_sa_entry(i);
+
+		ipsec_sa->ipsec_sa_hdl = ipsec_sa_index_to_handle(i);
+		ipsec_sa->ipsec_sa_idx = i;
+		odp_atomic_init_u32(&ipsec_sa->state, IPSEC_SA_STATE_FREE);
+		odp_atomic_init_u64(&ipsec_sa->bytes, 0);
+		odp_atomic_init_u64(&ipsec_sa->packets, 0);
+	}
+
+	return 0;
+}
+
+int _odp_ipsec_sad_term_global(void)
+{
+	int i;
+	ipsec_sa_t *ipsec_sa;
+	int ret = 0;
+	int rc = 0;
+
+	for (i = 0; i < ODP_CONFIG_IPSEC_SAS; i++) {
+		ipsec_sa = ipsec_sa_entry(i);
+
+		if (odp_atomic_load_u32(&ipsec_sa->state) !=
+		    IPSEC_SA_STATE_FREE) {
+			ODP_ERR("Not destroyed ipsec_sa: %u\n",
+				ipsec_sa->ipsec_sa_idx);
+			rc = -1;
+		}
+		odp_atomic_store_u32(&ipsec_sa->state, IPSEC_SA_STATE_FREE);
+	}
+
+	ret = odp_shm_free(ipsec_sa_tbl->shm);
+	if (ret < 0) {
+		ODP_ERR("shm free failed");
+		rc = -1;
+	}
+
+	return rc;
+}
+
+static
+ipsec_sa_t *ipsec_sa_reserve(void)
+{
+	int i;
+	ipsec_sa_t *ipsec_sa;
+
+	for (i = 0; i < ODP_CONFIG_IPSEC_SAS; i++) {
+		uint32_t state = IPSEC_SA_STATE_FREE;
+
+		ipsec_sa = ipsec_sa_entry(i);
+
+		if (odp_atomic_cas_acq_u32(&ipsec_sa->state, &state, 0))
+			return ipsec_sa;
+	}
+
+	return NULL;
+}
+
+static
+void ipsec_sa_release(ipsec_sa_t *ipsec_sa)
+{
+	odp_atomic_store_rel_u32(&ipsec_sa->state, IPSEC_SA_STATE_FREE);
+}
+
+static
+int ipsec_sa_lock(ipsec_sa_t *ipsec_sa)
+{
+	int cas = 0;
+	uint32_t state = odp_atomic_load_u32(&ipsec_sa->state);
+
+	while (0 == cas) {
+		/*
+		 * This can be called from lookup path, so we really need this
+		 * check
+		 */
+		if (state & IPSEC_SA_STATE_DISABLE)
+			return -1;
+
+		cas = odp_atomic_cas_acq_u32(&ipsec_sa->state, &state,
+					     state + 1);
+	}
+
+	return 0;
+}
+
+/* Do not call directly, use _odp_ipsec_sa_unuse */
+static
+odp_bool_t ipsec_sa_unlock(ipsec_sa_t *ipsec_sa)
+{
+	int cas = 0;
+	uint32_t state = odp_atomic_load_u32(&ipsec_sa->state);
+
+	while (0 == cas)
+		cas = odp_atomic_cas_rel_u32(&ipsec_sa->state, &state,
+					     state - 1);
+
+	return state == IPSEC_SA_STATE_DISABLE;
+}
+
+ipsec_sa_t *_odp_ipsec_sa_use(odp_ipsec_sa_t sa)
+{
+	ipsec_sa_t *ipsec_sa;
+
+	ODP_ASSERT(ODP_IPSEC_SA_INVALID != sa);
+
+	ipsec_sa = ipsec_sa_entry_from_hdl(sa);
+
+	if (ipsec_sa_lock(ipsec_sa) < 0)
+		return NULL;
+
+	return ipsec_sa;
+}
+
+void _odp_ipsec_sa_unuse(ipsec_sa_t *ipsec_sa)
+{
+	odp_queue_t queue;
+	odp_ipsec_sa_t sa;
+	odp_ipsec_warn_t warn = { .all = 0 };
+
+	ODP_ASSERT(NULL != ipsec_sa);
+
+	queue = ipsec_sa->queue;
+	sa = ipsec_sa->ipsec_sa_hdl;
+
+	if (ipsec_sa_unlock(ipsec_sa) && ODP_QUEUE_INVALID != queue)
+		_odp_ipsec_status_send(queue,
+				       ODP_IPSEC_STATUS_SA_DISABLE,
+				       sa, 0, warn);
+}
+
+void odp_ipsec_sa_param_init(odp_ipsec_sa_param_t *param)
+{
+	memset(param, 0, sizeof(odp_ipsec_sa_param_t));
+	param->dest_queue = ODP_QUEUE_INVALID;
+}
+
+odp_ipsec_sa_t odp_ipsec_sa_create(const odp_ipsec_sa_param_t *param)
+{
+	ipsec_sa_t *ipsec_sa;
+	odp_crypto_session_param_t crypto_param;
+	odp_crypto_ses_create_err_t ses_create_rc;
+
+	ipsec_sa = ipsec_sa_reserve();
+	if (NULL == ipsec_sa) {
+		ODP_ERR("No more free SA\n");
+		return ODP_IPSEC_SA_INVALID;
+	}
+
+	ipsec_sa->proto = param->proto;
+	ipsec_sa->spi = param->spi;
+	ipsec_sa->context = param->context;
+	ipsec_sa->queue = param->dest_queue;
+	ipsec_sa->mode = param->mode;
+	if (ODP_IPSEC_DIR_INBOUND == param->dir) {
+		ipsec_sa->in.lookup_mode = param->inbound.lookup_mode;
+		if (ODP_IPSEC_LOOKUP_DSTADDR_SPI == ipsec_sa->in.lookup_mode)
+			memcpy(&ipsec_sa->in.lookup_dst_ip,
+			       param->inbound.lookup_param.dst_addr,
+			       sizeof(ipsec_sa->in.lookup_dst_ip));
+
+	} else {
+		odp_atomic_store_u32(&ipsec_sa->out.seq, 1);
+	}
+	ipsec_sa->dec_ttl = param->opt.dec_ttl;
+	ipsec_sa->copy_dscp = param->opt.copy_dscp;
+	ipsec_sa->copy_df = param->opt.copy_df;
+
+	odp_atomic_store_u64(&ipsec_sa->bytes, 0);
+	odp_atomic_store_u64(&ipsec_sa->packets, 0);
+	ipsec_sa->soft_limit_bytes = param->lifetime.soft_limit.bytes;
+	ipsec_sa->soft_limit_packets = param->lifetime.soft_limit.packets;
+	ipsec_sa->hard_limit_bytes = param->lifetime.hard_limit.bytes;
+	ipsec_sa->hard_limit_packets = param->lifetime.hard_limit.packets;
+
+	if (ODP_IPSEC_MODE_TUNNEL == ipsec_sa->mode &&
+	    ODP_IPSEC_DIR_OUTBOUND == param->dir) {
+		if (param->outbound.tunnel.type != ODP_IPSEC_TUNNEL_IPV4) {
+			ipsec_sa_release(ipsec_sa);
+
+			return ODP_IPSEC_SA_INVALID;
+		}
+		memcpy(&ipsec_sa->out.tun_src_ip,
+		       param->outbound.tunnel.ipv4.src_addr,
+		       sizeof(ipsec_sa->out.tun_src_ip));
+		memcpy(&ipsec_sa->out.tun_dst_ip,
+		       param->outbound.tunnel.ipv4.dst_addr,
+		       sizeof(ipsec_sa->out.tun_dst_ip));
+		odp_atomic_init_u32(&ipsec_sa->out.tun_hdr_id, 0);
+		ipsec_sa->out.tun_ttl = param->outbound.tunnel.ipv4.ttl;
+		ipsec_sa->out.tun_dscp = param->outbound.tunnel.ipv4.dscp;
+		ipsec_sa->out.tun_df = param->outbound.tunnel.ipv4.df;
+	}
+
+	odp_crypto_session_param_init(&crypto_param);
+
+	/* Setup parameters and call crypto library to create session */
+	crypto_param.op = (ODP_IPSEC_DIR_INBOUND == param->dir) ?
+			ODP_CRYPTO_OP_DECODE :
+			ODP_CRYPTO_OP_ENCODE;
+	crypto_param.auth_cipher_text = 1;
+
+	crypto_param.op_mode   = ODP_CRYPTO_SYNC;
+	crypto_param.compl_queue = ODP_QUEUE_INVALID;
+	crypto_param.output_pool = ODP_POOL_INVALID;
+
+	crypto_param.cipher_alg = param->crypto.cipher_alg;
+	crypto_param.cipher_key = param->crypto.cipher_key;
+	crypto_param.auth_alg = param->crypto.auth_alg;
+	crypto_param.auth_key = param->crypto.auth_key;
+
+	switch (crypto_param.auth_alg) {
+	case ODP_AUTH_ALG_NULL:
+		ipsec_sa->icv_len = 0;
+		break;
+#if ODP_DEPRECATED_API
+	case ODP_AUTH_ALG_MD5_96:
+#endif
+	case ODP_AUTH_ALG_MD5_HMAC:
+		ipsec_sa->icv_len = 12;
+		break;
+	case ODP_AUTH_ALG_SHA1_HMAC:
+		ipsec_sa->icv_len = 12;
+		break;
+#if ODP_DEPRECATED_API
+	case ODP_AUTH_ALG_SHA256_128:
+#endif
+	case ODP_AUTH_ALG_SHA256_HMAC:
+		ipsec_sa->icv_len = 16;
+		break;
+	case ODP_AUTH_ALG_SHA512_HMAC:
+		ipsec_sa->icv_len = 32;
+		break;
+#if ODP_DEPRECATED_API
+	case ODP_AUTH_ALG_AES128_GCM:
+#endif
+	case ODP_AUTH_ALG_AES_GCM:
+		ipsec_sa->icv_len = 16;
+		break;
+	default:
+		return ODP_IPSEC_SA_INVALID;
+	}
+
+	switch (crypto_param.cipher_alg) {
+	case ODP_CIPHER_ALG_NULL:
+		ipsec_sa->esp_iv_len = 0;
+		ipsec_sa->esp_block_len = 1;
+		break;
+	case ODP_CIPHER_ALG_DES:
+	case ODP_CIPHER_ALG_3DES_CBC:
+		ipsec_sa->esp_iv_len = 8;
+		ipsec_sa->esp_block_len = 8;
+		break;
+#if ODP_DEPRECATED_API
+	case ODP_CIPHER_ALG_AES128_CBC:
+#endif
+	case ODP_CIPHER_ALG_AES_CBC:
+		ipsec_sa->esp_iv_len = 16;
+		ipsec_sa->esp_block_len = 16;
+		break;
+#if ODP_DEPRECATED_API
+	case ODP_CIPHER_ALG_AES128_GCM:
+#endif
+	case ODP_CIPHER_ALG_AES_GCM:
+		ipsec_sa->esp_iv_len = 8;
+		ipsec_sa->esp_block_len = 16;
+		crypto_param.iv.length = 12;
+		break;
+	default:
+		return ODP_IPSEC_SA_INVALID;
+	}
+
+	crypto_param.auth_digest_len = ipsec_sa->icv_len;
+
+	if (param->crypto.cipher_key_extra.length) {
+		if (param->crypto.cipher_key_extra.length >
+		    IPSEC_MAX_SALT_LEN)
+			goto error;
+
+		ipsec_sa->salt_length = param->crypto.cipher_key_extra.length;
+		memcpy(ipsec_sa->salt,
+		       param->crypto.cipher_key_extra.data,
+		       param->crypto.cipher_key_extra.length);
+	} else {
+		ipsec_sa->salt_length = 0;
+	}
+
+	if (odp_crypto_session_create(&crypto_param, &ipsec_sa->session,
+				      &ses_create_rc))
+		goto error;
+
+	return ipsec_sa->ipsec_sa_hdl;
+
+error:
+	ipsec_sa_release(ipsec_sa);
+
+	return ODP_IPSEC_SA_INVALID;
+}
+
+int odp_ipsec_sa_disable(odp_ipsec_sa_t sa)
+{
+	ipsec_sa_t *ipsec_sa = ipsec_sa_entry_from_hdl(sa);
+	uint32_t state;
+	int cas = 0;
+
+	/* This is a custom rwlock implementation. It is not possible to use
+	 * original rwlock, because there is no way to test if current code is
+	 * the last reader when disable operation is pending. */
+	state = odp_atomic_load_u32(&ipsec_sa->state);
+
+	while (0 == cas) {
+		if (state & IPSEC_SA_STATE_DISABLE)
+			return -1;
+
+		cas = odp_atomic_cas_acq_u32(&ipsec_sa->state, &state,
+					     state | IPSEC_SA_STATE_DISABLE);
+	}
+
+	if (ODP_QUEUE_INVALID != ipsec_sa->queue) {
+		odp_ipsec_warn_t warn = { .all = 0 };
+
+		/*
+		 * If there were not active state when we disabled SA,
+		 * send the event.
+		 */
+		if (0 == state)
+			_odp_ipsec_status_send(ipsec_sa->queue,
+					       ODP_IPSEC_STATUS_SA_DISABLE,
+					       ipsec_sa->ipsec_sa_hdl,
+					       0, warn);
+
+		return 0;
+	}
+
+	while (IPSEC_SA_STATE_DISABLE != state) {
+		odp_cpu_pause();
+		state = odp_atomic_load_u32(&ipsec_sa->state);
+	}
+
+	return 0;
+}
+
+int odp_ipsec_sa_destroy(odp_ipsec_sa_t sa)
+{
+	ipsec_sa_t *ipsec_sa = ipsec_sa_entry_from_hdl(sa);
+	int rc = 0;
+	uint32_t state = odp_atomic_load_u32(&ipsec_sa->state);
+
+	if (IPSEC_SA_STATE_DISABLE != state) {
+		ODP_ERR("Distroying not disabled ipsec_sa: %u\n",
+			ipsec_sa->ipsec_sa_idx);
+		return -1;
+	}
+
+	if (odp_crypto_session_destroy(ipsec_sa->session) < 0) {
+		ODP_ERR("Error destroying crypto session for ipsec_sa: %u\n",
+			ipsec_sa->ipsec_sa_idx);
+		rc = -1;
+	}
+
+	ipsec_sa_release(ipsec_sa);
+
+	return rc;
+}
+
+void *odp_ipsec_sa_context(odp_ipsec_sa_t sa)
+{
+	ipsec_sa_t *ipsec_sa = ipsec_sa_entry_from_hdl(sa);
+
+	return ipsec_sa->context;
+}
+
+uint64_t odp_ipsec_sa_to_u64(odp_ipsec_sa_t sa)
+{
+	return _odp_pri(sa);
+}
+
+int odp_ipsec_mtu_update(odp_ipsec_sa_t sa, uint32_t mtu)
+{
+	(void)sa;
+	(void)mtu;
+
+	return -1;
+}
+
+ipsec_sa_t *_odp_ipsec_sa_lookup(const ipsec_sa_lookup_t *lookup)
+{
+	(void)lookup;
+
+	int i;
+	ipsec_sa_t *ipsec_sa;
+	ipsec_sa_t *best = NULL;
+
+	for (i = 0; i < ODP_CONFIG_IPSEC_SAS; i++) {
+		ipsec_sa = ipsec_sa_entry(i);
+
+		if (ipsec_sa_lock(ipsec_sa) < 0)
+			continue;
+
+		if (ODP_IPSEC_LOOKUP_DSTADDR_SPI == ipsec_sa->in.lookup_mode &&
+		    lookup->proto == ipsec_sa->proto &&
+		    lookup->spi == ipsec_sa->spi &&
+		    !memcmp(lookup->dst_addr, &ipsec_sa->in.lookup_dst_ip,
+			    sizeof(ipsec_sa->in.lookup_dst_ip))) {
+			if (NULL != best)
+				_odp_ipsec_sa_unuse(best);
+			return ipsec_sa;
+		} else if (ODP_IPSEC_LOOKUP_SPI == ipsec_sa->in.lookup_mode &&
+				lookup->proto == ipsec_sa->proto &&
+				lookup->spi == ipsec_sa->spi) {
+			best = ipsec_sa;
+		} else {
+			_odp_ipsec_sa_unuse(ipsec_sa);
+		}
+	}
+
+	return best;
+}
+
+int _odp_ipsec_sa_update_stats(ipsec_sa_t *ipsec_sa, uint32_t len,
+			       odp_ipsec_op_status_t *status)
+{
+	uint64_t bytes = odp_atomic_fetch_add_u64(&ipsec_sa->bytes, len) + len;
+	uint64_t packets = odp_atomic_fetch_add_u64(&ipsec_sa->packets, 1) + 1;
+	int rc = 0;
+
+	if (ipsec_sa->soft_limit_bytes > 0 &&
+	    bytes > ipsec_sa->soft_limit_bytes)
+		status->warn.soft_exp_bytes = 1;
+
+	if (ipsec_sa->soft_limit_packets > 0 &&
+	    packets > ipsec_sa->soft_limit_packets)
+		status->warn.soft_exp_packets = 1;
+
+	if (ipsec_sa->hard_limit_bytes > 0 &&
+	    bytes > ipsec_sa->hard_limit_bytes) {
+		status->error.hard_exp_bytes = 1;
+		rc = -1;
+	}
+	if (ipsec_sa->hard_limit_packets > 0 &&
+	    packets > ipsec_sa->hard_limit_packets) {
+		status->error.hard_exp_packets = 1;
+		rc = -1;
+	}
+
+	return rc;
+}
