feat(notifications): bind and unbind

Handle bind and unbind interfaces. The bind interface is intended for a
given receiver to enable a sender to set the specified notifications.
The unbind interface revokes the sender's ability to set the specified
notifications.
Bindings are tracked per notification in the receiver's vm structure.
This patch adds handling to FFA_NOTIFICATION_BIND and
FFA_NOTIFICATION_UNBIND, functions to validate the bindings,
as well as to make any bindings updates.

Change-Id: I7c70fe4e1285d6f58738d0229568666ce2075d12
Signed-off-by: J-Alves <joao.alves@arm.com>
diff --git a/src/api.c b/src/api.c
index 94cd8a6..9d0a37b 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2578,3 +2578,85 @@
 
 	return plat_ffa_notifications_bitmap_destroy(vm_id);
 }
+
+struct ffa_value api_ffa_notification_update_bindings(
+	ffa_vm_id_t sender_vm_id, ffa_vm_id_t receiver_vm_id, uint32_t flags,
+	ffa_notifications_bitmap_t notifications, bool is_bind,
+	struct vcpu *current)
+{
+	struct ffa_value ret = {.func = FFA_SUCCESS_32};
+	struct vm_locked receiver_locked;
+	const bool is_per_vcpu = (flags & FFA_NOTIFICATION_FLAG_PER_VCPU) != 0U;
+	const ffa_vm_id_t id_to_update =
+		is_bind ? sender_vm_id : HF_INVALID_VM_ID;
+	const ffa_vm_id_t id_to_validate =
+		is_bind ? HF_INVALID_VM_ID : sender_vm_id;
+
+	if (!plat_ffa_is_notifications_bind_valid(current, sender_vm_id,
+						  receiver_vm_id)) {
+		dlog_verbose("Invalid use of notifications bind interface.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	if (notifications == 0U) {
+		dlog_verbose("No notifications have been specified.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/**
+	 * This check assumes receiver is the current VM, and has been enforced
+	 * by 'plat_ffa_is_notifications_bind_valid'.
+	 */
+	receiver_locked = plat_ffa_vm_find_locked(receiver_vm_id);
+
+	if (receiver_locked.vm == NULL) {
+		dlog_verbose("Receiver doesn't exist!\n");
+		return ffa_error(FFA_DENIED);
+	}
+
+	if (!vm_are_notifications_enabled(receiver_locked)) {
+		dlog_verbose("Notifications are not enabled.\n");
+		ret = ffa_error(FFA_NOT_SUPPORTED);
+		goto out;
+	}
+
+	if (is_bind && vm_id_is_current_world(sender_vm_id) &&
+	    vm_find(sender_vm_id) == NULL) {
+		dlog_verbose("Sender VM does not exist!\n");
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	}
+
+	/*
+	 * Can't bind/unbind notifications if at least one is bound to a
+	 * different sender.
+	 */
+	if (!vm_notifications_validate_bound_sender(
+		    receiver_locked, plat_ffa_is_vm_id(sender_vm_id),
+		    id_to_validate, notifications)) {
+		dlog_verbose("Notifications are bound to other sender.\n");
+		ret = ffa_error(FFA_DENIED);
+		goto out;
+	}
+
+	/**
+	 * Check if there is a pending notification within those specified in
+	 * the bitmap.
+	 */
+	if (vm_are_notifications_pending(receiver_locked,
+					 plat_ffa_is_vm_id(sender_vm_id),
+					 notifications)) {
+		dlog_verbose("Notifications within '%x' pending.\n",
+			     notifications);
+		ret = ffa_error(FFA_DENIED);
+		goto out;
+	}
+
+	vm_notifications_update_bindings(
+		receiver_locked, plat_ffa_is_vm_id(sender_vm_id), id_to_update,
+		notifications, is_per_vcpu && is_bind);
+
+out:
+	vm_unlock(&receiver_locked);
+	return ret;
+}
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 26dd59c..880d07f 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -603,6 +603,18 @@
 		*args = api_ffa_notification_bitmap_destroy(
 			(ffa_vm_id_t)args->arg1, current);
 		return true;
+	case FFA_NOTIFICATION_BIND_32:
+		*args = api_ffa_notification_update_bindings(
+			ffa_sender(*args), ffa_receiver(*args), args->arg2,
+			ffa_notifications_bitmap(args->arg3, args->arg4), true,
+			current);
+		return true;
+	case FFA_NOTIFICATION_UNBIND_32:
+		*args = api_ffa_notification_update_bindings(
+			ffa_sender(*args), ffa_receiver(*args), 0,
+			ffa_notifications_bitmap(args->arg3, args->arg4), false,
+			current);
+		return true;
 	}
 
 	return false;
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index d699dfe..9a43f72 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -95,8 +95,14 @@
 	return false;
 }
 
-void plat_ffa_vm_init(void)
+bool plat_ffa_is_notifications_bind_valid(struct vcpu *current,
+					  ffa_vm_id_t sender_id,
+					  ffa_vm_id_t receiver_id)
 {
+	(void)current;
+	(void)sender_id;
+	(void)receiver_id;
+	return false;
 }
 
 struct ffa_value plat_ffa_notifications_bitmap_create(
@@ -114,3 +120,15 @@
 
 	return ffa_error(FFA_NOT_SUPPORTED);
 }
+
+struct vm_locked plat_ffa_vm_find_locked(ffa_vm_id_t vm_id)
+{
+	(void)vm_id;
+	return (struct vm_locked){.vm = NULL};
+}
+
+bool plat_ffa_is_vm_id(ffa_vm_id_t vm_id)
+{
+	(void)vm_id;
+	return false;
+}
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index b65e434..de23811 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -203,8 +203,13 @@
 	return false;
 }
 
-void plat_ffa_vm_init(void)
+bool plat_ffa_is_notifications_bind_valid(struct vcpu *current,
+					  ffa_vm_id_t sender_id,
+					  ffa_vm_id_t receiver_id)
 {
+	ffa_vm_id_t current_vm_id = current->vm->id;
+	/** If Hafnium is hypervisor, receiver needs to be current vm. */
+	return sender_id != receiver_id && current_vm_id == receiver_id;
 }
 
 struct ffa_value plat_ffa_notifications_bitmap_create(
@@ -224,3 +229,17 @@
 
 	return ffa_error(FFA_NOT_SUPPORTED);
 }
+
+struct vm_locked plat_ffa_vm_find_locked(ffa_vm_id_t vm_id)
+{
+	if (vm_id_is_current_world(vm_id) || vm_id == HF_OTHER_WORLD_ID) {
+		return vm_find_locked(vm_id);
+	}
+
+	return (struct vm_locked){.vm = NULL};
+}
+
+bool plat_ffa_is_vm_id(ffa_vm_id_t vm_id)
+{
+	return vm_id_is_current_world(vm_id);
+}
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index b92fcec..6358797 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -160,6 +160,25 @@
 	       !vm_id_is_current_world(vm_id);
 }
 
+bool plat_ffa_is_notifications_bind_valid(struct vcpu *current,
+					  ffa_vm_id_t sender_id,
+					  ffa_vm_id_t receiver_id)
+{
+	ffa_vm_id_t current_vm_id = current->vm->id;
+
+	/**
+	 * SPMC:
+	 * - If bind call from SP, receiver's ID must be same as current VM ID.
+	 * - If bind call from NWd, current VM ID must be same as Hypervisor ID,
+	 * receiver's ID must be from NWd, and sender's ID from SWd.
+	 */
+	return sender_id != receiver_id &&
+	       (current_vm_id == receiver_id ||
+		(current_vm_id == HF_HYPERVISOR_VM_ID &&
+		 !vm_id_is_current_world(receiver_id) &&
+		 vm_id_is_current_world(sender_id)));
+}
+
 ffa_memory_handle_t plat_ffa_memory_handle_make(uint64_t index)
 {
 	return (index & ~FFA_MEMORY_HANDLE_ALLOCATOR_MASK) |
@@ -235,6 +254,10 @@
 {
 	struct vm_locked to_ret_locked;
 
+	if (vm_id_is_current_world(vm_id) || vm_id == HF_OTHER_WORLD_ID) {
+		return vm_find_locked(vm_id);
+	}
+
 	struct nwd_vms_locked nwd_vms_locked = nwd_vms_lock();
 
 	to_ret_locked = plat_ffa_nwd_vm_find_locked(nwd_vms_locked, vm_id);
@@ -366,3 +389,8 @@
 	vm_unlock(&to_destroy_locked);
 	return ret;
 }
+
+bool plat_ffa_is_vm_id(ffa_vm_id_t vm_id)
+{
+	return !vm_id_is_current_world(vm_id);
+}
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index 5a5a13b..7b61f0b 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -10,6 +10,7 @@
 
 #include "hf/ffa_internal.h"
 #include "hf/vcpu.h"
+#include "hf/vm.h"
 
 ffa_vm_id_t arch_ffa_spmc_id_get(void)
 {
@@ -64,6 +65,16 @@
 	return false;
 }
 
+bool plat_ffa_is_notifications_bind_valid(struct vcpu *current,
+					  ffa_vm_id_t sender_id,
+					  ffa_vm_id_t receiver_id)
+{
+	(void)current;
+	(void)sender_id;
+	(void)receiver_id;
+	return false;
+}
+
 ffa_partition_properties_t plat_ffa_partition_properties(
 	ffa_vm_id_t current_id, const struct vm *target)
 {
@@ -102,3 +113,15 @@
 
 	return ffa_error(FFA_NOT_SUPPORTED);
 }
+
+struct vm_locked plat_ffa_vm_find_locked(ffa_vm_id_t vm_id)
+{
+	(void)vm_id;
+	return (struct vm_locked){.vm = NULL};
+}
+
+bool plat_ffa_is_vm_id(ffa_vm_id_t vm_id)
+{
+	(void)vm_id;
+	return false;
+}
diff --git a/src/vm.c b/src/vm.c
index ff74763..a1568c0 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -433,3 +433,96 @@
 	/* Check if there are global pending notifications */
 	return (to_check->global.pending & notifications) != 0U;
 }
+
+bool vm_are_notifications_enabled(struct vm_locked vm_locked)
+{
+	return vm_locked.vm->notifications.enabled == true;
+}
+
+static bool vm_is_notification_bit_set(ffa_notifications_bitmap_t notifications,
+				       uint32_t i)
+{
+	return (notifications & FFA_NOTIFICATION_MASK(i)) != 0U;
+}
+
+static struct notifications *vm_get_notifications(struct vm_locked vm_locked,
+						  bool is_from_vm)
+{
+	return is_from_vm ? &vm_locked.vm->notifications.from_vm
+			  : &vm_locked.vm->notifications.from_sp;
+}
+
+/**
+ * Checks that all provided notifications are bound to the specified sender, and
+ * are per VCPU or global, as specified.
+ */
+bool vm_notifications_validate_binding(struct vm_locked vm_locked,
+				       bool is_from_vm, ffa_vm_id_t sender_id,
+				       ffa_notifications_bitmap_t notifications,
+				       bool is_per_vcpu)
+{
+	return vm_notifications_validate_bound_sender(
+		       vm_locked, is_from_vm, sender_id, notifications) &&
+	       vm_notifications_validate_per_vcpu(vm_locked, is_from_vm,
+						  is_per_vcpu, notifications);
+}
+
+/**
+ * Update binds information in notification structure for the specified
+ * notifications.
+ */
+void vm_notifications_update_bindings(struct vm_locked vm_locked,
+				      bool is_from_vm, ffa_vm_id_t sender_id,
+				      ffa_notifications_bitmap_t notifications,
+				      bool is_per_vcpu)
+{
+	CHECK(vm_locked.vm != NULL);
+	struct notifications *to_update =
+		vm_get_notifications(vm_locked, is_from_vm);
+
+	for (uint32_t i = 0; i < MAX_FFA_NOTIFICATIONS; i++) {
+		if (vm_is_notification_bit_set(notifications, i)) {
+			to_update->bindings_sender_id[i] = sender_id;
+		}
+	}
+
+	/*
+	 * Set notifications if they are per VCPU, else clear them as they are
+	 * global.
+	 */
+	if (is_per_vcpu) {
+		to_update->bindings_per_vcpu |= notifications;
+	} else {
+		to_update->bindings_per_vcpu &= ~notifications;
+	}
+}
+
+bool vm_notifications_validate_bound_sender(
+	struct vm_locked vm_locked, bool is_from_vm, ffa_vm_id_t sender_id,
+	ffa_notifications_bitmap_t notifications)
+{
+	CHECK(vm_locked.vm != NULL);
+	struct notifications *to_check =
+		vm_get_notifications(vm_locked, is_from_vm);
+
+	for (uint32_t i = 0; i < MAX_FFA_NOTIFICATIONS; i++) {
+		if (vm_is_notification_bit_set(notifications, i) &&
+		    to_check->bindings_sender_id[i] != sender_id) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool vm_notifications_validate_per_vcpu(struct vm_locked vm_locked,
+					bool is_from_vm, bool is_per_vcpu,
+					ffa_notifications_bitmap_t notif)
+{
+	CHECK(vm_locked.vm != NULL);
+	struct notifications *to_check =
+		vm_get_notifications(vm_locked, is_from_vm);
+
+	return is_per_vcpu ? (~to_check->bindings_per_vcpu & notif) == 0U
+			   : (to_check->bindings_per_vcpu & notif) == 0U;
+}