feat(realm): call RMI_PDEV ABIs from host realm mgmt

Initial support to invoke RMI PDEV ABIs from host realm management. The
PDEV management testcase host_test_rmi_pdev_calls does PDEV create, get
state, get aux count, communicate, set public key, stop, destroy calls.
These calls internally retrieve device certificate, extract public key,
and establish a secure session with the PCIe device.

Signed-off-by: Arunachalam Ganapathy <arunachalam.ganapathy@arm.com>
Change-Id: If007b5170cbaa932c4f18a01a825a8dc0b752f26
diff --git a/tftf/tests/runtime_services/host_realm_managment/host_crypto_utils.c b/tftf/tests/runtime_services/host_realm_managment/host_crypto_utils.c
new file mode 100644
index 0000000..80834fa
--- /dev/null
+++ b/tftf/tests/runtime_services/host_realm_managment/host_crypto_utils.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string.h>
+#include <stdbool.h>
+
+#ifdef MBEDTLS_CONFIG_FILE
+#include <mbedtls/asn1.h>
+#include <mbedtls/bignum.h>
+#include <mbedtls/ecdh.h>
+#include <mbedtls/memory_buffer_alloc.h>
+#include <mbedtls/x509.h>
+#include <mbedtls/x509_crt.h>
+#endif
+
+#include <host_crypto_utils.h>
+#include <test_helpers.h>
+
+#ifdef MBEDTLS_CONFIG_FILE
+#define HOST_MBEDTLS_HEAP_SIZE	(2U * SZ_4K)
+static unsigned char host_mbedtls_heap[HOST_MBEDTLS_HEAP_SIZE];
+
+static int host_get_leaf_cert_from_cert_chain(uint8_t *cert_chain,
+					      size_t cert_chain_len,
+					      uint8_t **leaf_cert,
+					      size_t *leaf_cert_len)
+{
+	size_t asn1_len;
+	unsigned char *tag_ptr;
+	uint8_t *cert_chain_end;
+	uint8_t *cur_cert;
+	size_t cur_cert_len;
+	int cur_cert_idx;
+	int rc;
+
+	cur_cert = cert_chain;
+	cur_cert_idx = -1;
+	cert_chain_end = cert_chain + cert_chain_len;
+
+	/* Get leaf certificate from cert_chain */
+	while (true) {
+		tag_ptr = cur_cert;
+		rc = mbedtls_asn1_get_tag(&tag_ptr, cert_chain_end, &asn1_len,
+					  MBEDTLS_ASN1_CONSTRUCTED |
+					  MBEDTLS_ASN1_SEQUENCE);
+		if (rc != 0) {
+			break;
+		}
+
+		cur_cert_len = asn1_len + (tag_ptr - cur_cert);
+		if (cur_cert + cur_cert_len > cert_chain_end) {
+			cur_cert_idx = -1;
+			break;
+		}
+
+		cur_cert = cur_cert + cur_cert_len;
+		cur_cert_idx++;
+	}
+
+	if (cur_cert_idx == -1) {
+		return -1;
+	}
+
+	*leaf_cert = cur_cert - cur_cert_len;
+	*leaf_cert_len = cur_cert_len;
+	INFO("leaf_cert_len: 0x%lx\n", *leaf_cert_len);
+
+	return 0;
+}
+
+/*
+ * @In:
+ * cert_chain		- Certificate chain
+ * cert_chain_len	- Length of certificate
+ * public_key_len	- Max length of 'public_key' buffer
+ * metadata_len		- Max length of 'metadata' buffer
+ *
+ * On success below variables are set:
+ * @Out:
+ * public_key		- Public key content
+ * public_key_len	- length of 'public_key'
+ * metadata		- Public key metadata content
+ * metadata_len		- length of 'metadata' if metadata exists, else set to 0
+ * public_key_algo	- Public key algorithm
+ */
+static int _host_get_public_key_from_cert_chain(uint8_t *cert_chain,
+						size_t cert_chain_len,
+						void *public_key,
+						size_t *public_key_len,
+						void *public_key_metadata,
+						size_t *public_key_metadata_len,
+						uint8_t *public_key_algo)
+{
+	uint8_t *leaf_cert;
+	size_t leaf_cert_len;
+	mbedtls_x509_crt crt;
+	mbedtls_pk_type_t pk_type;
+	int rc;
+
+	/* Get leaf cert and its length */
+	rc = host_get_leaf_cert_from_cert_chain(cert_chain, cert_chain_len,
+						&leaf_cert, &leaf_cert_len);
+	if (rc != 0) {
+		return -1;
+	}
+
+	/* Get public key from leaf certificate */
+	mbedtls_x509_crt_init(&crt);
+
+	if (mbedtls_x509_crt_parse_der(&crt, leaf_cert, leaf_cert_len) != 0) {
+		return -1;
+	}
+
+	pk_type = mbedtls_pk_get_type(&crt.pk);
+	if (pk_type != MBEDTLS_PK_ECKEY && pk_type != MBEDTLS_PK_RSA) {
+		rc = -1;
+		goto out_crt_free;
+	}
+
+	if (pk_type == MBEDTLS_PK_ECKEY) {
+		mbedtls_ecp_keypair *ecp;
+		mbedtls_ecp_group grp;
+		mbedtls_ecp_point pt;
+
+		ecp = mbedtls_pk_ec(crt.pk);
+		mbedtls_ecp_group_init(&grp);
+		mbedtls_ecp_point_init(&pt);
+		rc = mbedtls_ecp_export(ecp, &grp, NULL, &pt);
+
+		if (rc != 0 || (grp.id != MBEDTLS_ECP_DP_SECP256R1 &&
+				grp.id != MBEDTLS_ECP_DP_SECP384R1)) {
+			mbedtls_ecp_point_free(&pt);
+			mbedtls_ecp_group_free(&grp);
+			rc = -1;
+			goto out_crt_free;
+		}
+
+		rc = mbedtls_ecp_point_write_binary(&grp, &pt,
+						    MBEDTLS_ECP_PF_UNCOMPRESSED,
+						    public_key_len, public_key,
+						    MBEDTLS_ECP_MAX_PT_LEN);
+
+		if (rc == 0) {
+			if (grp.id == MBEDTLS_ECP_DP_SECP256R1) {
+				*public_key_algo =
+					PUBLIC_KEY_ALGO_ECDSA_ECC_NIST_P256;
+			} else {
+				*public_key_algo =
+					PUBLIC_KEY_ALGO_ECDSA_ECC_NIST_P384;
+			}
+
+			/* No metadata for PK_ECKEY type */
+			*public_key_metadata_len = 0;
+		}
+
+		mbedtls_ecp_point_free(&pt);
+		mbedtls_ecp_group_free(&grp);
+	} else {
+		mbedtls_rsa_context *rsa;
+		mbedtls_mpi N;
+		mbedtls_mpi E;
+		size_t N_sz, E_sz;
+
+		rsa = mbedtls_pk_rsa(crt.pk);
+		mbedtls_mpi_init(&N);
+		mbedtls_mpi_init(&E);
+
+		rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E);
+		N_sz = mbedtls_mpi_size(&N);
+		E_sz = mbedtls_mpi_size(&E);
+		INFO("RSA public key len: %ld, metadata len:%ld\n", N_sz, E_sz);
+
+		/* Supported ALGO type RSASSA_3072 (384 bytes) */
+		if (rc == 0 && N_sz == 384) {
+			rc = mbedtls_mpi_write_binary(&N, public_key, N_sz);
+			rc |= mbedtls_mpi_write_binary(&E, public_key_metadata,
+						       E_sz);
+			if (rc == 0) {
+				*public_key_algo = PUBLIC_KEY_ALGO_RSASSA_3072;
+				*public_key_len = N_sz;
+				*public_key_metadata_len = E_sz;
+			}
+		} else {
+			rc = -1;
+		}
+
+		mbedtls_mpi_free(&N);
+		mbedtls_mpi_free(&E);
+	}
+
+out_crt_free:
+	mbedtls_x509_crt_free(&crt);
+
+	return rc;
+}
+#endif
+
+int host_get_public_key_from_cert_chain(uint8_t *cert_chain,
+					size_t cert_chain_len,
+					void *public_key,
+					size_t *public_key_len,
+					void *public_key_metadata,
+					size_t *public_key_metadata_len,
+					uint8_t *public_key_algo)
+{
+	int rc;
+
+#ifdef MBEDTLS_CONFIG_FILE
+	mbedtls_memory_buffer_alloc_init(host_mbedtls_heap,
+					 sizeof(host_mbedtls_heap));
+
+	rc = _host_get_public_key_from_cert_chain(cert_chain, cert_chain_len,
+						  public_key, public_key_len,
+						  public_key_metadata,
+						  public_key_metadata_len,
+						  public_key_algo);
+#else
+	ERROR("MbedTLS not found.\n");
+	rc = -1;
+#endif
+	return rc;
+}
diff --git a/tftf/tests/runtime_services/host_realm_managment/host_realm_rmi.c b/tftf/tests/runtime_services/host_realm_managment/host_realm_rmi.c
index fdb1d4a..e6b6ad4 100644
--- a/tftf/tests/runtime_services/host_realm_managment/host_realm_rmi.c
+++ b/tftf/tests/runtime_services/host_realm_managment/host_realm_rmi.c
@@ -1299,3 +1299,55 @@
 	*exit_reason = run->exit.exit_reason;
 	return ret;
 }
+
+u_register_t host_rmi_pdev_aux_count(u_register_t pdev_ptr, u_register_t *count)
+{
+	smc_ret_values rets;
+
+	rets = host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_AUX_COUNT, pdev_ptr},
+				2U);
+	*count = rets.ret1;
+	return rets.ret0;
+}
+
+u_register_t host_rmi_pdev_create(u_register_t pdev_ptr, u_register_t params_ptr)
+{
+	return host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_CREATE, pdev_ptr,
+					     params_ptr}, 3U).ret0;
+}
+
+u_register_t host_rmi_pdev_get_state(u_register_t pdev_ptr, u_register_t *state)
+{
+	smc_ret_values rets;
+
+	rets = host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_GET_STATE, pdev_ptr},
+				2U);
+	*state = rets.ret1;
+	return rets.ret0;
+}
+
+u_register_t host_rmi_pdev_communicate(u_register_t pdev_ptr,
+				       u_register_t data_ptr)
+{
+	return host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_COMMUNICATE, pdev_ptr,
+						data_ptr}, 3U).ret0;
+}
+
+u_register_t host_rmi_pdev_set_pubkey(u_register_t pdev_ptr, u_register_t key,
+				      u_register_t len, uint8_t algo)
+{
+	return host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_SET_PUBKEY, pdev_ptr,
+						key, len, algo}, 5U).ret0;
+}
+
+u_register_t host_rmi_pdev_stop(u_register_t pdev_ptr)
+{
+	return host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_STOP, pdev_ptr},
+				2U).ret0;
+}
+
+u_register_t host_rmi_pdev_destroy(u_register_t pdev_ptr)
+{
+	return host_rmi_handler(&(smc_args) {SMC_RMI_PDEV_DESTROY, pdev_ptr},
+				2U).ret0;
+}
diff --git a/tftf/tests/runtime_services/host_realm_managment/host_rmi_pdev.c b/tftf/tests/runtime_services/host_realm_managment/host_rmi_pdev.c
new file mode 100644
index 0000000..c43c374
--- /dev/null
+++ b/tftf/tests/runtime_services/host_realm_managment/host_rmi_pdev.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string.h>
+
+#include <heap/page_alloc.h>
+#include <host_crypto_utils.h>
+#include <host_realm_helper.h>
+#include <host_realm_mem_layout.h>
+#include <host_shared_data.h>
+#include <mmio.h>
+#include <pcie.h>
+#include <pcie_doe.h>
+#include <pcie_spec.h>
+#include <platform.h>
+#include <spdm.h>
+#include <test_helpers.h>
+
+/* SPDM_MAX_CERTIFICATE_CHAIN_SIZE is 64KB */
+#define HOST_PDEV_CERT_LEN_MAX		(64 * 1024)
+
+/*
+ * Measurement max supported is 4KB.
+ * todo: This will be increased if device supports returning more measurements
+ */
+#define HOST_PDEV_MEAS_LEN_MAX		(4 * 1024)
+
+#define DEV_OBJ_CERT			0U
+#define DEV_OBJ_MEASUREMENTS		2U
+#define DEV_OBJ_INTERFACE_REPORT	3U
+
+struct host_tdi {
+	/* PDEV related fields */
+	void *pdev;
+	unsigned long pdev_flags;
+	void *pdev_aux[PDEV_PARAM_AUX_GRANULES_MAX];
+	uint32_t pdev_aux_num;
+	struct rmi_dev_comm_data *dev_comm_data;
+
+	/* Algorithm used to generate device digests */
+	uint8_t pdev_hash_algo;
+
+	/* Certificate, public key fields */
+	uint8_t cert_slot_id;
+	uint8_t *cert_chain;
+	size_t cert_chain_len;
+	void *public_key;
+	size_t public_key_len;
+	void *public_key_metadata;
+	size_t public_key_metadata_len;
+	unsigned char public_key_sig_algo;
+
+	/*
+	 * Fields related to cached device measurements.
+	 * todo: This will be moved to vdev scope
+	 */
+	uint8_t *meas;
+	size_t meas_len;
+
+	/* PCIe details: bdf, DOE, Stream id, IO range */
+	uint32_t bdf;
+	uint32_t doe_cap_base;
+};
+
+static struct host_tdi g_tdi;
+
+static const char * const pdev_state_str[] = {
+	"PDEV_STATE_NEW",
+	"PDEV_STATE_NEEDS_KEY",
+	"PDEV_STATE_HAS_KEY",
+	"PDEV_STATE_READY",
+	"PDEV_STATE_COMMUNICATING",
+	"PDEV_STATE_STOPPED",
+	"RMI_PDEV_STATE_ERROR"
+};
+
+static int host_tdi_pdev_get_state(struct host_tdi *tdi, u_register_t *state)
+{
+	u_register_t ret;
+
+	ret = host_rmi_pdev_get_state((u_register_t)tdi->pdev, state);
+	if (ret != RMI_SUCCESS) {
+		return -1;
+	}
+	return 0;
+}
+
+static bool is_host_tdi_pdev_state(struct host_tdi *tdi, u_register_t exp_state)
+{
+	u_register_t cur_state;
+
+	if (host_tdi_pdev_get_state(tdi, &cur_state) != 0) {
+		return false;
+	}
+
+	if (cur_state != exp_state) {
+		return false;
+	}
+
+	return true;
+}
+
+static int host_tdi_pdev_create(struct host_tdi *tdi)
+{
+	struct rmi_pdev_params *pdev_params;
+	u_register_t ret;
+	uint32_t i;
+
+	pdev_params = (struct rmi_pdev_params *)page_alloc(PAGE_SIZE);
+	memset(pdev_params, 0, GRANULE_SIZE);
+
+	pdev_params->flags = tdi->pdev_flags;
+	pdev_params->cert_id = tdi->cert_slot_id;
+	pdev_params->pdev_id = tdi->bdf;
+	pdev_params->num_aux = tdi->pdev_aux_num;
+	pdev_params->hash_algo = tdi->pdev_hash_algo;
+	for (i = 0; i < tdi->pdev_aux_num; i++) {
+		pdev_params->aux[i] = (uintptr_t)tdi->pdev_aux[i];
+	}
+
+	ret = host_rmi_pdev_create((u_register_t)tdi->pdev,
+				   (u_register_t)pdev_params);
+	if (ret != RMI_SUCCESS) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int host_tdi_pdev_set_pubkey(struct host_tdi *tdi)
+{
+	u_register_t ret;
+
+	ret = host_rmi_pdev_set_pubkey((u_register_t)tdi->pdev,
+				       (u_register_t)tdi->public_key,
+				       (u_register_t)tdi->public_key_len,
+				       (u_register_t)tdi->public_key_sig_algo);
+	if (ret != RMI_SUCCESS) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int host_tdi_pdev_stop(struct host_tdi *tdi)
+{
+	u_register_t ret;
+
+	ret = host_rmi_pdev_stop((u_register_t)tdi->pdev);
+	if (ret != RMI_SUCCESS) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int host_tdi_pdev_destroy(struct host_tdi *tdi)
+{
+	u_register_t ret;
+
+	ret = host_rmi_pdev_destroy((u_register_t)tdi->pdev);
+	if (ret != RMI_SUCCESS) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int host_tdi_pdev_cache_device_object(struct host_tdi *tdi,
+					     uint8_t obj_type,
+					     const uint8_t *obj_buf,
+					     size_t obj_len)
+{
+	int rc = -1;
+
+	if (obj_type == DEV_OBJ_CERT) {
+		if ((tdi->cert_chain_len + obj_len) > HOST_PDEV_CERT_LEN_MAX) {
+			return -1;
+		}
+
+		INFO("%s: cache_cert: offset: 0x%lx, len: 0x%lx\n",
+		     __func__, tdi->cert_chain_len, obj_len);
+
+		memcpy((void *)(tdi->cert_chain + tdi->cert_chain_len),
+		       obj_buf, obj_len);
+		tdi->cert_chain_len += obj_len;
+		rc = 0;
+	} else if (obj_type == DEV_OBJ_MEASUREMENTS) {
+		if ((tdi->meas_len + obj_len) > HOST_PDEV_MEAS_LEN_MAX) {
+			return -1;
+		}
+
+		INFO("%s: cache_meas: offset: 0x%lx, len: 0x%lx\n",
+		     __func__, tdi->meas_len, obj_len);
+
+		memcpy((void *)(tdi->meas + tdi->meas_len), obj_buf, obj_len);
+		tdi->meas_len += obj_len;
+		rc = 0;
+	}
+
+	return rc;
+}
+
+/* Call RMI PDEV communicate until the target state is reached */
+static int host_tdi_pdev_communicate(struct host_tdi *tdi,
+				     unsigned char target_state)
+{
+	int rc;
+	u_register_t state;
+	u_register_t ret;
+	struct rmi_dev_comm_enter *dev_comm_enter;
+	struct rmi_dev_comm_exit *dev_comm_exit;
+	size_t resp_len;
+
+	dev_comm_enter = &tdi->dev_comm_data->enter;
+	dev_comm_exit = &tdi->dev_comm_data->exit;
+
+	dev_comm_enter->status = RMI_DEV_COMM_ENTER_STATUS_NONE;
+	dev_comm_enter->resp_len = 0;
+
+	if (host_tdi_pdev_get_state(tdi, &state) != 0) {
+		return -1;
+	}
+
+	do {
+		ret = host_rmi_pdev_communicate((u_register_t)tdi->pdev,
+					(u_register_t)tdi->dev_comm_data);
+		if (ret != RMI_SUCCESS) {
+			INFO("rmi_pdev_communicate failed\n");
+			rc = -1;
+			break;
+		}
+
+		/*
+		 * If cache is set, then response buffer has the device object
+		 * to be cached.
+		 */
+		if (EXTRACT(RMI_DEV_COMM_EXIT_FLAGS_CACHE,
+			    dev_comm_exit->flags)) {
+			uint8_t *obj_buf;
+			uint8_t obj_type;
+
+			if (dev_comm_exit->cache_len == 0 ||
+			    (dev_comm_exit->cache_offset +
+			     dev_comm_exit->cache_len) >
+			    GRANULE_SIZE) {
+				INFO("Invalid cache offset/length\n");
+				rc = -1;
+				break;
+			}
+
+			if (state == RMI_PDEV_STATE_NEW) {
+				obj_type = DEV_OBJ_CERT;
+			} else if (state == RMI_PDEV_STATE_HAS_KEY) {
+				/* todo: replace with RMI_PDEV_STATE_READY */
+				obj_type = DEV_OBJ_MEASUREMENTS;
+			} else {
+				rc = -1;
+				break;
+			}
+
+			obj_buf = (uint8_t *)dev_comm_enter->resp_addr +
+				dev_comm_exit->cache_offset;
+			rc = host_tdi_pdev_cache_device_object(tdi, obj_type,
+							       obj_buf,
+						       dev_comm_exit->cache_len);
+			if (rc != 0) {
+				INFO("host_pdev_cache_device_object failed\n");
+				rc = -1;
+				break;
+			}
+		}
+
+		/* Send request to spdm responder */
+		if (EXTRACT(RMI_DEV_COMM_EXIT_FLAGS_SEND,
+			    dev_comm_exit->flags)) {
+			uint32_t doe_header;
+
+			/* todo: validate DevCommExit flags */
+			if (dev_comm_exit->protocol ==
+			    RMI_DEV_COMM_PROTOCOL_SPDM) {
+				doe_header = DOE_HEADER_1;
+			} else if (dev_comm_exit->protocol ==
+				   RMI_DEV_COMM_PROTOCOL_SECURE_SPDM) {
+				doe_header = DOE_HEADER_2;
+			} else {
+				INFO("Invalid dev_comm_exit.protocol\n");
+				rc = -1;
+				break;
+			}
+
+			rc = pcie_doe_communicate(doe_header, tdi->bdf, tdi->doe_cap_base,
+					  (void *)dev_comm_enter->req_addr,
+						  dev_comm_exit->req_len,
+					(void *)dev_comm_enter->resp_addr,
+						  &resp_len);
+
+			/*
+			 * Set IoEnter args for next pdev_communicate. Upon
+			 * success or error call pdev_communicate
+			 */
+			if (rc == 0) {
+				dev_comm_enter->status =
+					RMI_DEV_COMM_ENTER_STATUS_SUCCESS;
+				dev_comm_enter->resp_len = resp_len;
+			} else {
+				dev_comm_enter->status =
+					RMI_DEV_COMM_ENTER_STATUS_ERROR;
+				dev_comm_enter->resp_len = 0;
+			}
+		}
+
+		rc = host_tdi_pdev_get_state(tdi, &state);
+		if (rc != 0) {
+			break;
+		}
+	} while ((state != target_state) && (state != RMI_PDEV_STATE_ERROR));
+
+	return rc;
+}
+
+/*
+ * Invoke RMI handler to transition PDEV state to 'to_state'
+ */
+static int host_tdi_pdev_transition(struct host_tdi *tdi, unsigned char to_state)
+{
+	int rc;
+
+	switch (to_state) {
+	case RMI_PDEV_STATE_NEW:
+		rc = host_tdi_pdev_create(tdi);
+		break;
+	case RMI_PDEV_STATE_NEEDS_KEY:
+		rc = host_tdi_pdev_communicate(tdi, RMI_PDEV_STATE_NEEDS_KEY);
+		break;
+	case RMI_PDEV_STATE_HAS_KEY:
+		rc = host_tdi_pdev_set_pubkey(tdi);
+		break;
+	case RMI_PDEV_STATE_READY:
+		rc = host_tdi_pdev_communicate(tdi, RMI_PDEV_STATE_READY);
+		break;
+	case RMI_PDEV_STATE_STOPPING:
+		rc = host_tdi_pdev_stop(tdi);
+		break;
+	case RMI_PDEV_STATE_STOPPED:
+		rc = host_tdi_pdev_communicate(tdi, RMI_PDEV_STATE_STOPPED);
+		break;
+	default:
+		rc = -1;
+	}
+
+	if (rc != 0) {
+		INFO("RMI command failed\n");
+		return -1;
+	}
+
+	if (!is_host_tdi_pdev_state(tdi, to_state)) {
+		ERROR("PDEV state not [%s]\n", pdev_state_str[to_state]);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Allocate granules needed for a PDEV object like device communication data,
+ * response buffer, PDEV AUX granules and memory required to store cert_chain
+ */
+static int host_tdi_pdev_setup(struct host_tdi *tdi)
+{
+	u_register_t ret, count;
+	int i;
+
+	memset(tdi, 0, sizeof(struct host_tdi));
+
+	/* Allocate granule for PDEV and delegate */
+	tdi->pdev = page_alloc(PAGE_SIZE);
+	memset(tdi->pdev, 0, GRANULE_SIZE);
+	ret = host_rmi_granule_delegate((u_register_t)tdi->pdev);
+	if (ret != RMI_SUCCESS) {
+		ERROR("PDEV delegate failed 0x%lx\n", ret);
+		return -1;
+	}
+
+	/* Set flags as IO coherent device protected by end to end IDE. */
+	tdi->pdev_flags = INPLACE(RMI_PDEV_FLAGS_PROT_CONFIG,
+				  RMI_PDEV_IOCOH_E2E_IDE);
+
+	/* Get num of aux granules required for this PDEV */
+	ret = host_rmi_pdev_aux_count(tdi->pdev_flags, &count);
+	if (ret != RMI_SUCCESS) {
+		ERROR("host_rmi_pdev_aux_count() failed 0x%lx\n", ret);
+		return -1;
+	}
+	tdi->pdev_aux_num = count;
+
+	/* Allocate aux granules for PDEV and delegate */
+	INFO("PDEV create requires %u aux pages\n", tdi->pdev_aux_num);
+	for (i = 0; i < tdi->pdev_aux_num; i++) {
+		tdi->pdev_aux[i] = page_alloc(PAGE_SIZE);
+		ret = host_rmi_granule_delegate((u_register_t)tdi->pdev_aux[i]);
+		if (ret != RMI_SUCCESS) {
+			ERROR("Aux granule delegate failed 0x%lx\n", ret);
+			return -1;
+		}
+	}
+
+	/* Allocate dev_comm_data and send/recv buffer for Dev communication */
+	tdi->dev_comm_data = (struct rmi_dev_comm_data *)page_alloc(PAGE_SIZE);
+	memset(tdi->dev_comm_data, 0, sizeof(struct rmi_dev_comm_data));
+	tdi->dev_comm_data->enter.req_addr = (unsigned long)
+		page_alloc(PAGE_SIZE);
+	tdi->dev_comm_data->enter.resp_addr = (unsigned long)
+		page_alloc(PAGE_SIZE);
+
+	/* Allocate buffer to cache device certificate */
+	tdi->cert_slot_id = 0;
+	tdi->cert_chain = (uint8_t *)page_alloc(HOST_PDEV_CERT_LEN_MAX);
+	tdi->cert_chain_len = 0;
+	if (tdi->cert_chain == NULL) {
+		return -1;
+	}
+
+	/* Allocate buffer to store extracted public key */
+	tdi->public_key = (void *)page_alloc(PAGE_SIZE);
+	if (tdi->public_key == NULL) {
+		return -1;
+	}
+	tdi->public_key_len = PAGE_SIZE;
+
+	/* Allocate buffer to store public key metadata */
+	tdi->public_key_metadata = (void *)page_alloc(PAGE_SIZE);
+	if (tdi->public_key_metadata == NULL) {
+		return -1;
+	}
+	tdi->public_key_metadata_len = PAGE_SIZE;
+
+	/* Allocate buffer to cache device measurements */
+	tdi->meas = (uint8_t *)page_alloc(HOST_PDEV_MEAS_LEN_MAX);
+	tdi->meas_len = 0;
+	if (tdi->meas == NULL) {
+		return -1;
+	}
+
+	/* Set algorithm to use for device digests */
+	tdi->pdev_hash_algo = RMI_HASH_SHA_512;
+
+	return 0;
+}
+
+/*
+ * Stop PDEV and ternimate secure session and call PDEV destroy
+ */
+static int host_tdi_pdev_reclaim(struct host_tdi *tdi)
+{
+	int rc;
+
+	/* Move the device to STOPPING state */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_STOPPING);
+	if (rc != 0) {
+		INFO("PDEV transition: to PDEV_STATE_STOPPING failed\n");
+		return -1;
+	}
+
+	/* Do pdev_communicate to terminate secure session */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_STOPPED);
+	if (rc != 0) {
+		INFO("PDEV transition: to PDEV_STATE_STOPPED failed\n");
+		return -1;
+	}
+
+	rc = host_tdi_pdev_destroy(tdi);
+	if (rc != 0) {
+		INFO("PDEV transition: to STATE_NULL failed\n");
+		return -1;
+	}
+
+	/* Undelegate all the delegated pages */
+	for (int i = 0; i < tdi->pdev_aux_num; i++) {
+		host_rmi_granule_undelegate((u_register_t)tdi->pdev_aux[i]);
+	}
+	host_rmi_granule_undelegate((u_register_t)tdi->pdev);
+
+	return rc;
+}
+
+/*
+ * This invokes various RMI calls related to PDEV management that does
+ * PDEV create/communicate/set_key/abort/stop/destroy on a device.
+ */
+test_result_t host_test_rmi_pdev_calls(void)
+{
+	u_register_t rmi_feat_reg0;
+	uint32_t pdev_bdf, doe_cap_base;
+	struct host_tdi *tdi;
+	uint8_t public_key_algo;
+	int ret, rc;
+
+	CHECK_DA_SUPPORT_IN_RMI(rmi_feat_reg0);
+	SKIP_TEST_IF_DOE_NOT_SUPPORTED(pdev_bdf, doe_cap_base);
+
+	/* Initialize Host NS heap memory */
+	ret = page_pool_init((u_register_t)PAGE_POOL_BASE,
+				(u_register_t)PAGE_POOL_MAX_SIZE);
+	if (ret != HEAP_INIT_SUCCESS) {
+		ERROR("Failed to init heap pool %d\n", ret);
+		return TEST_RESULT_FAIL;
+	}
+
+	tdi = &g_tdi;
+
+	/* Allocate granules. Skip DA ABIs if host_pdev_setup fails */
+	rc = host_tdi_pdev_setup(tdi);
+	if (rc == -1) {
+		INFO("host_pdev_setup failed. skipping DA ABIs...\n");
+		return TEST_RESULT_SKIPPED;
+	}
+
+	/* todo: move to tdi_pdev_setup */
+	tdi->bdf = pdev_bdf;
+	tdi->doe_cap_base = doe_cap_base;
+
+	/* Call rmi_pdev_create to transition PDEV to STATE_NEW */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_NEW);
+	if (rc != 0) {
+		ERROR("PDEV transition: NULL -> STATE_NEW failed\n");
+		return TEST_RESULT_FAIL;
+	}
+
+	/* Call rmi_pdev_communicate to transition PDEV to NEEDS_KEY */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_NEEDS_KEY);
+	if (rc != 0) {
+		ERROR("PDEV transition: PDEV_NEW -> PDEV_NEEDS_KEY failed\n");
+		return TEST_RESULT_FAIL;
+	}
+
+	/* Get public key. Verifying cert_chain not done by host but by Realm? */
+	rc = host_get_public_key_from_cert_chain(tdi->cert_chain,
+						 tdi->cert_chain_len,
+						 tdi->public_key,
+						 &tdi->public_key_len,
+						 tdi->public_key_metadata,
+						 &tdi->public_key_metadata_len,
+						 &public_key_algo);
+	if (rc != 0) {
+		ERROR("Get public key failed\n");
+		return TEST_RESULT_FAIL;
+	}
+
+	if (public_key_algo == PUBLIC_KEY_ALGO_ECDSA_ECC_NIST_P256) {
+		tdi->public_key_sig_algo = RMI_SIGNATURE_ALGORITHM_ECDSA_P256;
+	} else if (public_key_algo == PUBLIC_KEY_ALGO_ECDSA_ECC_NIST_P384) {
+		tdi->public_key_sig_algo = RMI_SIGNATURE_ALGORITHM_ECDSA_P384;
+	} else {
+		tdi->public_key_sig_algo = RMI_SIGNATURE_ALGORITHM_RSASSA_3072;
+	}
+	INFO("DEV public key len/sig_algo: %ld/%d\n", tdi->public_key_len,
+	     tdi->public_key_sig_algo);
+
+	/* Call rmi_pdev_set_key transition PDEV to HAS_KEY */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_HAS_KEY);
+	if (rc != 0) {
+		INFO("PDEV transition: PDEV_NEEDS_KEY -> PDEV_HAS_KEY failed\n");
+		return TEST_RESULT_FAIL;
+	}
+
+	/* Call rmi_pdev_comminucate to transition PDEV to READY state */
+	rc = host_tdi_pdev_transition(tdi, RMI_PDEV_STATE_READY);
+	if (rc != 0) {
+		INFO("PDEV transition: PDEV_HAS_KEY -> PDEV_READY failed\n");
+		return TEST_RESULT_FAIL;
+	}
+
+	host_tdi_pdev_reclaim(tdi);
+
+	return TEST_RESULT_SUCCESS;
+}
diff --git a/tftf/tests/tests-realm-payload.mk b/tftf/tests/tests-realm-payload.mk
index 4da8e3e..b6833e1 100644
--- a/tftf/tests/tests-realm-payload.mk
+++ b/tftf/tests/tests-realm-payload.mk
@@ -25,6 +25,8 @@
 		host_realm_helper.c					\
 		host_shared_data.c					\
 		rmi_delegate_tests.c					\
+		host_rmi_pdev.c						\
+		host_crypto_utils.c					\
 	)
 
 TESTS_SOURCES	+=							\
@@ -56,4 +58,7 @@
 		pcie.c		\
 		pcie_doe.c	\
 	)
+
+include lib/ext_mbedtls/mbedtls.mk
+
 endif
diff --git a/tftf/tests/tests-realm-payload.xml b/tftf/tests/tests-realm-payload.xml
index d95c474..da88701 100644
--- a/tftf/tests/tests-realm-payload.xml
+++ b/tftf/tests/tests-realm-payload.xml
@@ -137,5 +137,8 @@
 	  function="doe_discovery_test" />
 	  <testcase name="SPDM Get Version"
 	  function="spdm_version_test" />
+	  <!-- Invoke RMI calls related to PDEV management -->
+	  <testcase name="Invoke RMI PDEV calls "
+	  function="host_test_rmi_pdev_calls" />
   </testsuite>
 </testsuites>