diff --git a/providers/mlx5/mlx5.c b/providers/mlx5/mlx5.c
index d078548..d76964f 100644
--- a/providers/mlx5/mlx5.c
+++ b/providers/mlx5/mlx5.c
@@ -1028,6 +1028,7 @@ static int mlx5_init_context(struct verbs_device *vdev,
 	v_ctx->modify_cq = mlx5_modify_cq;
 	v_ctx->alloc_td = mlx5_alloc_td;
 	v_ctx->dealloc_td = mlx5_dealloc_td;
+	v_ctx->alloc_parent_domain = mlx5_alloc_parent_domain;
 
 	memset(&device_attr, 0, sizeof(device_attr));
 	if (!mlx5_query_device_ex(ctx, NULL, &device_attr,
diff --git a/providers/mlx5/mlx5.h b/providers/mlx5/mlx5.h
index 4d4c583..ed75409 100644
--- a/providers/mlx5/mlx5.h
+++ b/providers/mlx5/mlx5.h
@@ -329,6 +329,13 @@ struct mlx5_td {
 struct mlx5_pd {
 	struct ibv_pd			ibv_pd;
 	uint32_t			pdn;
+	atomic_int			refcount;
+	struct mlx5_pd			*mprotection_domain;
+};
+
+struct mlx5_parent_domain {
+	struct mlx5_pd mpd;
+	struct mlx5_td *mtd;
 };
 
 enum {
@@ -557,9 +564,27 @@ static inline struct mlx5_context *to_mctx(struct ibv_context *ibctx)
 	return to_mxxx(ctx, context);
 }
 
+/* to_mpd always returns the real mlx5_pd object ie the protection domain. */
 static inline struct mlx5_pd *to_mpd(struct ibv_pd *ibpd)
 {
-	return to_mxxx(pd, pd);
+	struct mlx5_pd *mpd = to_mxxx(pd, pd);
+
+	if (mpd->mprotection_domain)
+		return mpd->mprotection_domain;
+
+	return mpd;
+}
+
+static inline struct mlx5_parent_domain *to_mparent_domain(struct ibv_pd *ibpd)
+{
+	struct mlx5_parent_domain *mparent_domain =
+	    ibpd ? container_of(ibpd, struct mlx5_parent_domain, mpd.ibv_pd) : NULL;
+
+	if (mparent_domain && mparent_domain->mpd.mprotection_domain)
+		return mparent_domain;
+
+	/* Otherwise ibpd isn't a parent_domain */
+	return NULL;
 }
 
 static inline struct mlx5_cq *to_mcq(struct ibv_cq *ibcq)
@@ -767,6 +792,10 @@ int mlx5_post_srq_ops(struct ibv_srq *srq,
 struct ibv_td *mlx5_alloc_td(struct ibv_context *context, struct ibv_td_init_attr *init_attr);
 int mlx5_dealloc_td(struct ibv_td *td);
 
+struct ibv_pd *mlx5_alloc_parent_domain(struct ibv_context *context,
+					struct ibv_parent_domain_init_attr *attr);
+
+
 static inline void *mlx5_find_uidx(struct mlx5_context *ctx, uint32_t uidx)
 {
 	int tind = uidx >> MLX5_UIDX_TABLE_SHIFT;
diff --git a/providers/mlx5/verbs.c b/providers/mlx5/verbs.c
index 43db501..13af0c2 100644
--- a/providers/mlx5/verbs.c
+++ b/providers/mlx5/verbs.c
@@ -148,6 +148,7 @@ struct ibv_pd *mlx5_alloc_pd(struct ibv_context *context)
 		return NULL;
 	}
 
+	atomic_init(&pd->refcount, 1);
 	pd->pdn = resp.pdn;
 
 	return &pd->ibv_pd;
@@ -203,15 +204,73 @@ int mlx5_dealloc_td(struct ibv_td *ib_td)
 	return 0;
 }
 
+struct ibv_pd *
+mlx5_alloc_parent_domain(struct ibv_context *context,
+			 struct ibv_parent_domain_init_attr *attr)
+{
+	struct mlx5_parent_domain *mparent_domain;
+
+	if (ibv_check_alloc_parent_domain(attr))
+		return NULL;
+
+	if (attr->comp_mask) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	mparent_domain = calloc(1, sizeof(*mparent_domain));
+	if (!mparent_domain) {
+		errno = ENOMEM;
+		return NULL;
+	}
+
+	if (attr->td) {
+		mparent_domain->mtd = to_mtd(attr->td);
+		atomic_fetch_add(&mparent_domain->mtd->refcount, 1);
+	}
+
+	mparent_domain->mpd.mprotection_domain = to_mpd(attr->pd);
+	atomic_fetch_add(&mparent_domain->mpd.mprotection_domain->refcount, 1);
+	atomic_init(&mparent_domain->mpd.refcount, 1);
+
+	ibv_initialize_parent_domain(
+	    &mparent_domain->mpd.ibv_pd,
+	    &mparent_domain->mpd.mprotection_domain->ibv_pd);
+
+	return &mparent_domain->mpd.ibv_pd;
+}
+
+static int mlx5_dealloc_parent_domain(struct mlx5_parent_domain *mparent_domain)
+{
+	if (atomic_load(&mparent_domain->mpd.refcount) > 1)
+		return EBUSY;
+
+	atomic_fetch_sub(&mparent_domain->mpd.mprotection_domain->refcount, 1);
+
+	if (mparent_domain->mtd)
+		atomic_fetch_sub(&mparent_domain->mtd->refcount, 1);
+
+	free(mparent_domain);
+	return 0;
+}
+
 int mlx5_free_pd(struct ibv_pd *pd)
 {
 	int ret;
+	struct mlx5_parent_domain *mparent_domain = to_mparent_domain(pd);
+	struct mlx5_pd *mpd = to_mpd(pd);
+
+	if (mparent_domain)
+		return mlx5_dealloc_parent_domain(mparent_domain);
+
+	if (atomic_load(&mpd->refcount) > 1)
+		return EBUSY;
 
 	ret = ibv_cmd_dealloc_pd(pd);
 	if (ret)
 		return ret;
 
-	free(to_mpd(pd));
+	free(mpd);
 	return 0;
 }
 
