feat(ff-a): Implement FFA_MEM_PERM_GET/SET
This patch implements the new ABI from the FF-A v1.1 Beta0 spec. These
ABIs are expected to be used by S-EL0 use cases such as StandaloneMM or
other S-EL0 applications such as trusted firmware services or any other
S-EL0 FF-A partition.
Signed-off-by: Raghu Krishnamurthy <raghu.ncstate@gmail.com>
Change-Id: I6a57420ce5821993671362280a66c5c4f190ffbc
diff --git a/src/api.c b/src/api.c
index d3df341..47cae90 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2923,3 +2923,188 @@
return api_ffa_notification_info_get_success_return(
ids, ids_count, lists_sizes, lists_count, list_is_full);
}
+
+struct ffa_value api_ffa_mem_perm_get(vaddr_t base_addr, struct vcpu *current)
+{
+ struct vm_locked vm_locked;
+ struct ffa_value ret = ffa_error(FFA_INVALID_PARAMETERS);
+ bool mode_ret = false;
+ uint32_t mode = 0;
+
+ if (!plat_ffa_is_mem_perm_get_valid(current)) {
+ return ffa_error(FFA_NOT_SUPPORTED);
+ }
+
+ if (!(current->vm->el0_partition)) {
+ return ffa_error(FFA_DENIED);
+ }
+
+ vm_locked = vm_lock(current->vm);
+
+ /*
+ * mm_get_mode is used to check if the given base_addr page is already
+ * mapped. If the page is unmapped, return error. If the page is mapped
+ * appropriate attributes are returned to the caller. Note that
+ * mm_get_mode returns true if the address is in the valid VA range as
+ * supported by the architecture and MMU configurations, as opposed to
+ * whether a page is mapped or not. For a page to be known as mapped,
+ * the API must return true AND the returned mode must not have
+ * MM_MODE_INVALID set.
+ */
+ mode_ret = mm_get_mode(&vm_locked.vm->ptable, base_addr,
+ va_add(base_addr, PAGE_SIZE), &mode);
+ if (!mode_ret || (mode & MM_MODE_INVALID)) {
+ ret = ffa_error(FFA_INVALID_PARAMETERS);
+ goto out;
+ }
+
+ /* No memory should be marked RWX */
+ CHECK((mode & (MM_MODE_R | MM_MODE_W | MM_MODE_X)) !=
+ (MM_MODE_R | MM_MODE_W | MM_MODE_X));
+
+ /*
+ * S-EL0 partitions are expected to have all their pages marked as
+ * non-global.
+ */
+ CHECK((mode & (MM_MODE_NG | MM_MODE_USER)) ==
+ (MM_MODE_NG | MM_MODE_USER));
+
+ if (mode & MM_MODE_W) {
+ /* No memory should be writeable but not readable. */
+ CHECK(mode & MM_MODE_R);
+ ret = (struct ffa_value){.func = FFA_SUCCESS_32,
+ .arg2 = (uint32_t)(FFA_MEM_PERM_RW)};
+ } else if (mode & MM_MODE_R) {
+ ret = (struct ffa_value){.func = FFA_SUCCESS_32,
+ .arg2 = (uint32_t)(FFA_MEM_PERM_RX)};
+ if (!(mode & MM_MODE_X)) {
+ ret.arg2 = (uint32_t)(FFA_MEM_PERM_RO);
+ }
+ }
+out:
+ vm_unlock(&vm_locked);
+ return ret;
+}
+
+struct ffa_value api_ffa_mem_perm_set(vaddr_t base_addr, uint32_t page_count,
+ uint32_t mem_perm, struct vcpu *current)
+{
+ struct vm_locked vm_locked;
+ struct ffa_value ret;
+ bool mode_ret = false;
+ uint32_t original_mode;
+ uint32_t new_mode;
+ struct mpool local_page_pool;
+
+ if (!plat_ffa_is_mem_perm_set_valid(current)) {
+ return ffa_error(FFA_NOT_SUPPORTED);
+ }
+
+ if (!(current->vm->el0_partition)) {
+ return ffa_error(FFA_DENIED);
+ }
+
+ if (!is_aligned(va_addr(base_addr), PAGE_SIZE)) {
+ return ffa_error(FFA_INVALID_PARAMETERS);
+ }
+
+ if ((mem_perm != FFA_MEM_PERM_RW) && (mem_perm != FFA_MEM_PERM_RO) &&
+ (mem_perm != FFA_MEM_PERM_RX)) {
+ return ffa_error(FFA_INVALID_PARAMETERS);
+ }
+
+ /*
+ * Create a local pool so any freed memory can't be used by another
+ * thread. This is to ensure the original mapping can be restored if any
+ * stage of the process fails.
+ */
+ mpool_init_with_fallback(&local_page_pool, &api_page_pool);
+
+ vm_locked = vm_lock(current->vm);
+
+ /*
+ * All regions accessible by the partition are mapped during boot. If we
+ * cannot get a successful translation for the page range, the request
+ * to change permissions is rejected.
+ * mm_get_mode is used to check if the given address range is already
+ * mapped. If the range is unmapped, return error. If the range is
+ * mapped appropriate attributes are returned to the caller. Note that
+ * mm_get_mode returns true if the address is in the valid VA range as
+ * supported by the architecture and MMU configurations, as opposed to
+ * whether a page is mapped or not. For a page to be known as mapped,
+ * the API must return true AND the returned mode must not have
+ * MM_MODE_INVALID set.
+ */
+
+ mode_ret = mm_get_mode(&vm_locked.vm->ptable, base_addr,
+ va_add(base_addr, page_count * PAGE_SIZE),
+ &original_mode);
+ if (!mode_ret || (original_mode & MM_MODE_INVALID)) {
+ ret = ffa_error(FFA_INVALID_PARAMETERS);
+ goto out;
+ }
+
+ /* Device memory cannot be marked as executable */
+ if ((original_mode & MM_MODE_D) && (mem_perm == FFA_MEM_PERM_RX)) {
+ ret = ffa_error(FFA_INVALID_PARAMETERS);
+ goto out;
+ }
+
+ new_mode = MM_MODE_USER | MM_MODE_NG;
+
+ if (mem_perm == FFA_MEM_PERM_RW) {
+ new_mode |= MM_MODE_R | MM_MODE_W;
+ } else if (mem_perm == FFA_MEM_PERM_RX) {
+ new_mode |= MM_MODE_R | MM_MODE_X;
+ } else if (mem_perm == FFA_MEM_PERM_RO) {
+ new_mode |= MM_MODE_R;
+ }
+
+ /*
+ * Safe to re-map memory, since we know the requested permissions are
+ * valid, and the memory requested to be re-mapped is also valid.
+ */
+ if (!mm_identity_prepare(
+ &vm_locked.vm->ptable, pa_from_va(base_addr),
+ pa_from_va(va_add(base_addr, page_count * PAGE_SIZE)),
+ new_mode, &local_page_pool)) {
+ /*
+ * Defrag the table into the local page pool.
+ * mm_identity_prepare could have allocated or freed pages to
+ * split blocks or tables etc.
+ */
+ mm_stage1_defrag(&vm_locked.vm->ptable, &local_page_pool);
+
+ /*
+ * Guaranteed to succeed mapping with old mode since the mapping
+ * with old mode already existed and we have a local page pool
+ * that should have sufficient memory to go back to the original
+ * state.
+ */
+ CHECK(mm_identity_prepare(
+ &vm_locked.vm->ptable, pa_from_va(base_addr),
+ pa_from_va(va_add(base_addr, page_count * PAGE_SIZE)),
+ original_mode, &local_page_pool));
+ mm_identity_commit(
+ &vm_locked.vm->ptable, pa_from_va(base_addr),
+ pa_from_va(va_add(base_addr, page_count * PAGE_SIZE)),
+ original_mode, &local_page_pool);
+
+ mm_stage1_defrag(&vm_locked.vm->ptable, &api_page_pool);
+ ret = ffa_error(FFA_NO_MEMORY);
+ goto out;
+ }
+
+ mm_identity_commit(
+ &vm_locked.vm->ptable, pa_from_va(base_addr),
+ pa_from_va(va_add(base_addr, page_count * PAGE_SIZE)), new_mode,
+ &local_page_pool);
+
+ ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+
+out:
+ mpool_fini(&local_page_pool);
+ vm_unlock(&vm_locked);
+
+ return ret;
+}
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index fff1ea2..587cf95 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -615,6 +615,15 @@
ffa_notifications_bitmap(args->arg3, args->arg4), false,
current);
return true;
+ case FFA_MEM_PERM_SET_32:
+ case FFA_MEM_PERM_SET_64:
+ *args = api_ffa_mem_perm_set(va_init(args->arg1), args->arg2,
+ args->arg3, current);
+ return true;
+ case FFA_MEM_PERM_GET_32:
+ case FFA_MEM_PERM_GET_64:
+ *args = api_ffa_mem_perm_get(va_init(args->arg1), current);
+ return true;
case FFA_NOTIFICATION_SET_32:
*args = api_ffa_notification_set(
ffa_sender(*args), ffa_receiver(*args), args->arg2,
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index 44d5a70..392eb17 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -232,3 +232,15 @@
return false;
}
+
+bool plat_ffa_is_mem_perm_get_valid(const struct vcpu *current)
+{
+ (void)current;
+ return false;
+}
+
+bool plat_ffa_is_mem_perm_set_valid(const struct vcpu *current)
+{
+ (void)current;
+ return false;
+}
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index 38e2217..010a982 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -16,6 +16,7 @@
#include "hf/vm.h"
#include "smc.h"
+#include "sysregs.h"
static bool ffa_tee_enabled;
@@ -390,3 +391,15 @@
return false;
}
+
+bool plat_ffa_is_mem_perm_get_valid(const struct vcpu *current)
+{
+ (void)current;
+ return has_vhe_support();
+}
+
+bool plat_ffa_is_mem_perm_set_valid(const struct vcpu *current)
+{
+ (void)current;
+ return has_vhe_support();
+}
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 6eae955..c533b04 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -16,6 +16,7 @@
#include "hf/vm.h"
#include "smc.h"
+#include "sysregs.h"
/** Other world SVE context (accessed from other_world_loop). */
struct sve_context_t sve_context[MAX_CPUS];
@@ -542,3 +543,15 @@
return info_get_state == FULL;
}
+
+bool plat_ffa_is_mem_perm_get_valid(const struct vcpu *current)
+{
+ /* FFA_MEM_PERM_SET/GET is only valid before SPs are initialized */
+ return has_vhe_support() && (current->vm->initialized == false);
+}
+
+bool plat_ffa_is_mem_perm_set_valid(const struct vcpu *current)
+{
+ /* FFA_MEM_PERM_SET/GET is only valid before SPs are initialized */
+ return has_vhe_support() && (current->vm->initialized == false);
+}
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 5dbdc34..a5d8536 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -216,3 +216,15 @@
return false;
}
+
+bool plat_ffa_is_mem_perm_get_valid(const struct vcpu *current)
+{
+ (void)current;
+ return false;
+}
+
+bool plat_ffa_is_mem_perm_set_valid(const struct vcpu *current)
+{
+ (void)current;
+ return false;
+}