feat(notifications): notifications set and get

Handle FF-A calls FFA_NOTIFICATION_SET and FFA_NOTIFICATION_GET.
The former is used for a sender to signal a notification to the
receiver; the latter is for the receiver to get whichever notifications
there are pending.

Change-Id: I7e9db94201d0d78ceecd599cd350eeb37a8cb1f8
Signed-off-by: J-Alves <joao.alves@arm.com>
diff --git a/src/api.c b/src/api.c
index ddbde0f..e5b7bb6 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2063,7 +2063,7 @@
 	} else if (vm_id_is_current_world(receiver_vm_id)) {
 		/*
 		 * It is expected the receiver_vm_id to be from an SP, otherwise
-		 * 'arch_other_world_is_direct_response_valid' should have
+		 * 'plat_ffa_is_direct_response_valid' should have
 		 * made function return error before getting to this point.
 		 */
 		*next = api_switch_to_vm(current, to_ret,
@@ -2661,3 +2661,165 @@
 	vm_unlock(&receiver_locked);
 	return ret;
 }
+
+struct ffa_value api_ffa_notification_set(
+	ffa_vm_id_t sender_vm_id, ffa_vm_id_t receiver_vm_id, uint32_t flags,
+	ffa_notifications_bitmap_t notifications, struct vcpu *current)
+{
+	struct ffa_value ret;
+	struct vm_locked receiver_locked;
+
+	/*
+	 * Check if is per-vCPU or global, and extracting vCPU ID according
+	 * to table 17.19 of the FF-A v1.1 Beta 0 spec.
+	 */
+	bool is_per_vcpu = (flags & FFA_NOTIFICATION_FLAG_PER_VCPU) != 0U;
+	ffa_vcpu_index_t vcpu_id = (uint16_t)(flags >> 16);
+
+	/*
+	 * TODO: cater for the delay_schedule_receiver flag when dealing with
+	 * schedule receiver interrupt.
+	 */
+
+	if (!plat_ffa_is_notification_set_valid(current, sender_vm_id,
+						receiver_vm_id)) {
+		dlog_verbose("Invalid use of notifications set 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_notification_set_valid'.
+	 */
+	receiver_locked = plat_ffa_vm_find_locked(receiver_vm_id);
+
+	if (receiver_locked.vm == NULL) {
+		dlog_verbose("Receiver ID is not valid.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/*
+	 * TODO: Forward Hypervisor's call to SWd if setting SP's notifications
+	 * from VMs.
+	 */
+
+	if (!vm_are_notifications_enabled(receiver_locked)) {
+		dlog_verbose("Receiver's notifications not enabled.\n");
+		ret = ffa_error(FFA_DENIED);
+		goto out;
+	}
+
+	/*
+	 * If notifications are not bound to the sender, they wouldn't be
+	 * enabled either for the receiver.
+	 */
+	if (!vm_notifications_validate_binding(
+		    receiver_locked, plat_ffa_is_vm_id(sender_vm_id),
+		    sender_vm_id, notifications, is_per_vcpu)) {
+		dlog_verbose("Notifications bindings not valid.\n");
+		ret = ffa_error(FFA_DENIED);
+		goto out;
+	}
+
+	if (is_per_vcpu && vcpu_id >= receiver_locked.vm->vcpu_count) {
+		dlog_verbose("Invalid VCPU ID!\n");
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	}
+
+	/* Set notifications pending */
+	vm_notifications_set(receiver_locked, plat_ffa_is_vm_id(sender_vm_id),
+			     notifications, vcpu_id, is_per_vcpu);
+	dlog_verbose("Set the notifications: %x.\n", notifications);
+
+	ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+
+out:
+	vm_unlock(&receiver_locked);
+
+	return ret;
+}
+
+static struct ffa_value api_ffa_notification_get_success_return(
+	ffa_notifications_bitmap_t from_sp, ffa_notifications_bitmap_t from_vm,
+	ffa_notifications_bitmap_t from_framework)
+{
+	return (struct ffa_value){
+		.func = FFA_SUCCESS_32,
+		.arg1 = 0U,
+		.arg2 = (uint32_t)from_sp,
+		.arg3 = (uint32_t)(from_sp >> 32),
+		.arg4 = (uint32_t)from_vm,
+		.arg5 = (uint32_t)(from_vm >> 32),
+		.arg6 = (uint32_t)from_framework,
+		.arg7 = (uint32_t)(from_framework >> 32),
+	};
+}
+
+struct ffa_value api_ffa_notification_get(ffa_vm_id_t receiver_vm_id,
+					  ffa_vcpu_index_t vcpu_id,
+					  uint32_t flags, struct vcpu *current)
+{
+	/* TODO: get framework notifications, when these are supported. */
+	ffa_notifications_bitmap_t sp_notifications = 0;
+	ffa_notifications_bitmap_t vm_notifications = 0;
+	struct vm_locked receiver_locked;
+	struct ffa_value ret;
+
+	/*
+	 * Following check should capture wrong uses of the interface, depending
+	 * on whether Hafnium is SPMC or hypervisor.
+	 * On the rest of the function it is assumed this condition is met.
+	 */
+	if (!plat_ffa_is_notification_get_valid(current, receiver_vm_id)) {
+		dlog_verbose("Invalid use of notifications get interface.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/*
+	 * This check assumes receiver is the current VM, and has been enforced
+	 * by `plat_ffa_is_notifications_get_valid`.
+	 */
+	receiver_locked = plat_ffa_vm_find_locked(receiver_vm_id);
+
+	/*
+	 * `plat_ffa_is_notifications_get_valid` ensures following is never
+	 * true.
+	 */
+	CHECK(receiver_locked.vm != NULL);
+
+	if (receiver_locked.vm->vcpu_count <= vcpu_id ||
+	    (receiver_locked.vm->vcpu_count != 1 &&
+	     cpu_index(current->cpu) != vcpu_id)) {
+		dlog_verbose("Invalid VCPU ID!\n");
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	}
+
+	if ((flags & FFA_NOTIFICATION_FLAG_BITMAP_SP) != 0U) {
+		/*
+		 * TODO: For hypervisor, forward call to SPMC to get VM's
+		 * notifications from SPs.
+		 */
+		sp_notifications = vm_notifications_get_pending_and_clear(
+			receiver_locked, false, vcpu_id);
+	}
+
+	if ((flags & FFA_NOTIFICATION_FLAG_BITMAP_VM) != 0U) {
+		vm_notifications = vm_notifications_get_pending_and_clear(
+			receiver_locked, true, vcpu_id);
+	}
+
+	ret = api_ffa_notification_get_success_return(sp_notifications,
+						      vm_notifications, 0);
+
+out:
+	vm_unlock(&receiver_locked);
+
+	return ret;
+}