test(notifications): unit tests for info get functions

This patch includes a set of unit tests around the function
'vm_notifications_info_get_pending', which is at the core of the
handling of FFA_NOTIFICATIONS_INFO_GET.

Change-Id: I1b48dbc1b883d51a86f7c0359d349dd5d0e1b607
Signed-off-by: J-Alves <joao.alves@arm.com>
diff --git a/src/vm_test.cc b/src/vm_test.cc
index c45252a..4d47c54 100644
--- a/src/vm_test.cc
+++ b/src/vm_test.cc
@@ -29,6 +29,7 @@
 using ::testing::SizeIs;
 
 using struct_vm = struct vm;
+using struct_vm_locked = struct vm_locked;
 
 constexpr size_t TEST_HEAP_SIZE = PAGE_SIZE * 32;
 const int TOP_LEVEL = arch_mm_stage2_max_level();
@@ -208,7 +209,7 @@
 
 /**
  * Validates updates and check functions for binding notifications, namely the
- * configuration of bindings of global and per VCPU notifications.
+ * configuration of bindings of global and per-vCPU notifications.
  */
 TEST_F(vm, vm_notification_bind_per_vcpu_vs_global)
 {
@@ -237,13 +238,13 @@
 		current_vm_locked, is_from_vm, dummy_sender->id, global,
 		false));
 
-	/* Check validation of per vcpu notifications bindings. */
+	/* Check validation of per-vCPU notifications bindings. */
 	EXPECT_TRUE(vm_notifications_validate_binding(
 		current_vm_locked, is_from_vm, dummy_sender->id, per_vcpu,
 		true));
 
 	/**
-	 * Check that global notifications are not validated as per VCPU, and
+	 * Check that global notifications are not validated as per-vCPU, and
 	 * vice-versa.
 	 */
 	EXPECT_FALSE(vm_notifications_validate_binding(
@@ -345,4 +346,337 @@
 	vm_unlock(&current_vm_locked);
 }
 
+/**
+ * Validates simple getting of notifications info for global notifications.
+ */
+TEST_F(vm, vm_notifications_info_get_global)
+{
+	ffa_notifications_bitmap_t to_set = 0xFU;
+	ffa_notifications_bitmap_t got;
+
+	/**
+	 * Following set of variables that are also expected to be used when
+	 * handling FFA_NOTIFICATION_INFO_GET.
+	 */
+	uint16_t ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t ids_count = 0;
+	uint32_t lists_count = 0;
+	enum notifications_info_get_state current_state = INIT;
+
+	CHECK(vm_get_count() >= 2);
+
+	for (unsigned int i = 0; i < 2; i++) {
+		struct_vm *current_vm = vm_find_index(0);
+		struct vm_locked current_vm_locked = vm_lock(current_vm);
+		struct notifications *notifications =
+			&current_vm->notifications.from_sp;
+		const bool is_from_vm = false;
+
+		vm_notifications_set(current_vm_locked, is_from_vm, to_set, 0,
+				     false);
+
+		vm_notifications_info_get_pending(
+			current_vm_locked, is_from_vm, ids, &ids_count,
+			lists_sizes, &lists_count,
+			FFA_NOTIFICATIONS_INFO_GET_MAX_IDS, &current_state);
+
+		/*
+		 * Here the number of IDs and list count should be the same.
+		 * As we are testing with Global notifications, this is
+		 * expected.
+		 */
+		EXPECT_EQ(ids_count, i + 1);
+		EXPECT_EQ(lists_count, i + 1);
+		EXPECT_EQ(lists_sizes[i], 0);
+		EXPECT_EQ(to_set, notifications->global.info_get_retrieved);
+
+		/* Action must be reset to initial state for each VM. */
+		current_state = INIT;
+
+		/*
+		 * Check that getting pending notifications gives the expected
+		 * return and cleans the 'pending' and 'info_get_retrieved'
+		 * bitmaps.
+		 */
+		got = vm_notifications_get_pending_and_clear(current_vm_locked,
+							     is_from_vm, 0);
+		EXPECT_EQ(got, to_set);
+
+		EXPECT_EQ(notifications->global.info_get_retrieved, 0U);
+		EXPECT_EQ(notifications->global.pending, 0U);
+
+		vm_unlock(&current_vm_locked);
+	}
+}
+
+/**
+ * Validates simple getting of notifications info for per-vCPU notifications.
+ */
+TEST_F(vm, vm_notifications_info_get_per_vcpu)
+{
+	const ffa_notifications_bitmap_t per_vcpu = 0xFU;
+	ffa_notifications_bitmap_t got;
+
+	/*
+	 * Following set of variables that are also expected to be used when
+	 * handling ffa_notification_info_get.
+	 */
+	uint16_t ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t ids_count = 0;
+	uint32_t lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t lists_count = 0;
+	enum notifications_info_get_state current_state = INIT;
+
+	CHECK(vm_get_count() >= 2);
+
+	for (unsigned int i = 0; i < 2; i++) {
+		struct_vm *current_vm = vm_find_index(0);
+		struct vm_locked current_vm_locked = vm_lock(current_vm);
+		struct notifications *notifications =
+			&current_vm->notifications.from_sp;
+		const bool is_from_vm = false;
+
+		vm_notifications_set(current_vm_locked, is_from_vm, per_vcpu, 0,
+				     true);
+
+		vm_notifications_info_get_pending(
+			current_vm_locked, is_from_vm, ids, &ids_count,
+			lists_sizes, &lists_count,
+			FFA_NOTIFICATIONS_INFO_GET_MAX_IDS, &current_state);
+
+		/*
+		 * Here the number of IDs and list count should be the same.
+		 * As we are testing with Global notifications, this is
+		 * expected.
+		 */
+		EXPECT_EQ(ids_count, (i + 1) * 2);
+		EXPECT_EQ(lists_count, i + 1);
+		EXPECT_EQ(lists_sizes[i], 1);
+		EXPECT_EQ(per_vcpu,
+			  notifications->per_vcpu[0].info_get_retrieved);
+
+		/* Action must be reset to initial state for each VM. */
+		current_state = INIT;
+
+		/*
+		 * Check that getting pending notifications gives the expected
+		 * return and cleans the 'pending' and 'info_get_retrieved'
+		 * bitmaps.
+		 */
+		got = vm_notifications_get_pending_and_clear(current_vm_locked,
+							     is_from_vm, 0);
+		EXPECT_EQ(got, per_vcpu);
+
+		EXPECT_EQ(notifications->per_vcpu[0].info_get_retrieved, 0U);
+		EXPECT_EQ(notifications->per_vcpu[0].pending, 0U);
+
+		vm_unlock(&current_vm_locked);
+	}
+}
+
+/**
+ * Validate getting of notifications information if all VCPUs have notifications
+ * pending.
+ */
+TEST_F(vm, vm_notifications_info_get_per_vcpu_all_vcpus)
+{
+	struct_vm *current_vm = nullptr;
+	struct vm_locked current_vm_locked;
+	const ffa_vcpu_count_t vcpu_count = MAX_CPUS;
+	ffa_notifications_bitmap_t got;
+	const ffa_notifications_bitmap_t global = 0xF0000;
+
+	/*
+	 * Following set of variables that are also expected to be used when
+	 * handling ffa_notification_info_get.
+	 */
+	struct notifications *notifications;
+	const bool is_from_sp = false;
+	uint16_t ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t ids_count = 0;
+	uint32_t lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t lists_count = 0;
+	enum notifications_info_get_state current_state = INIT;
+
+	EXPECT_TRUE(vm_init_next(vcpu_count, &ppool, &current_vm, false));
+	current_vm_locked = vm_lock(current_vm);
+	notifications = &current_vm->notifications.from_sp;
+
+	for (unsigned int i = 0; i < vcpu_count; i++) {
+		vm_notifications_set(current_vm_locked, is_from_sp,
+				     FFA_NOTIFICATION_MASK(i), i, true);
+	}
+
+	/*
+	 * Adding a global notification should not change the list of IDs,
+	 * because global notifications only require the VM ID to be included in
+	 * the list, at least once.
+	 */
+	vm_notifications_set(current_vm_locked, is_from_sp, global, 0, false);
+
+	vm_notifications_info_get_pending(current_vm_locked, is_from_sp, ids,
+					  &ids_count, lists_sizes, &lists_count,
+					  FFA_NOTIFICATIONS_INFO_GET_MAX_IDS,
+					  &current_state);
+
+	/*
+	 * This test has been conceived for the expected MAX_CPUS 4.
+	 * All VCPUs have notifications of the same VM, to be broken down in 2
+	 * lists with 3 VCPU IDs, and 1 VCPU ID respectively.
+	 * The list of IDs should look like: {<vm_id>, 0, 1, 2, <vm_id>, 3}.
+	 */
+	CHECK(MAX_CPUS == 4);
+	EXPECT_EQ(ids_count, 6U);
+	EXPECT_EQ(lists_count, 2U);
+	EXPECT_EQ(lists_sizes[0], 3);
+	EXPECT_EQ(lists_sizes[1], 1);
+
+	for (unsigned int i = 0; i < vcpu_count; i++) {
+		got = vm_notifications_get_pending_and_clear(current_vm_locked,
+							     is_from_sp, i);
+
+		/*
+		 * The first call to vm_notifications_get_pending_and_clear
+		 * should also include the global notifications on the return.
+		 */
+		ffa_notifications_bitmap_t to_check =
+			(i != 0) ? FFA_NOTIFICATION_MASK(i)
+				 : FFA_NOTIFICATION_MASK(i) | global;
+
+		EXPECT_EQ(got, to_check);
+
+		EXPECT_EQ(notifications->per_vcpu[i].pending, 0);
+		EXPECT_EQ(notifications->per_vcpu[i].info_get_retrieved, 0);
+	}
+
+	vm_unlock(&current_vm_locked);
+}
+
+/**
+ * Validate change of state from 'vm_notifications_info_get_pending', when the
+ * list of IDs is full.
+ */
+TEST_F(vm, vm_notifications_info_get_full_per_vcpu)
+{
+	struct_vm *current_vm = vm_find_index(0);
+	struct vm_locked current_vm_locked = vm_lock(current_vm);
+	struct notifications *notifications =
+		&current_vm->notifications.from_sp;
+	const bool is_from_vm = false;
+	ffa_notifications_bitmap_t got = 0;
+
+	/*
+	 * Following set of variables that are also expected to be used when
+	 * handling ffa_notification_info_get.
+	 * For this 'ids_count' has been initialized such that it indicates
+	 * there is no space in the list for a per-vCPU notification (VM ID and
+	 * VCPU ID).
+	 */
+	uint16_t ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t ids_count = FFA_NOTIFICATIONS_INFO_GET_MAX_IDS - 1;
+	uint32_t lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t lists_count = 10;
+	enum notifications_info_get_state current_state = INIT;
+	CHECK(vm_get_count() >= 2);
+
+	vm_notifications_set(current_vm_locked, is_from_vm,
+			     FFA_NOTIFICATION_MASK(1), 0, true);
+
+	/* Call function to get notifications info, with only per-vCPU set. */
+	vm_notifications_info_get_pending(current_vm_locked, is_from_vm, ids,
+					  &ids_count, lists_sizes, &lists_count,
+					  FFA_NOTIFICATIONS_INFO_GET_MAX_IDS,
+					  &current_state);
+
+	/*
+	 * Verify that as soon as there isn't space to do the required
+	 * insertion in the list, the 'vm_notifications_get_pending_and_clear'
+	 * returns and changes list state to FULL.
+	 * In this case returning, because it would need to add two IDs (VM ID
+	 * and VCPU ID).
+	 */
+	EXPECT_EQ(current_state, FULL);
+	EXPECT_EQ(ids_count, FFA_NOTIFICATIONS_INFO_GET_MAX_IDS - 1);
+	EXPECT_EQ(notifications->per_vcpu[0].info_get_retrieved, 0U);
+
+	/*
+	 * At this point there is still room for the information of a global
+	 * notification (only VM ID to be added). Reset 'current_state'
+	 * for the insertion to happen at the last position of the array.
+	 */
+	current_state = INIT;
+
+	/* Setting global notification */
+	vm_notifications_set(current_vm_locked, is_from_vm,
+			     FFA_NOTIFICATION_MASK(2), 0, false);
+
+	vm_notifications_info_get_pending(current_vm_locked, is_from_vm, ids,
+					  &ids_count, lists_sizes, &lists_count,
+					  FFA_NOTIFICATIONS_INFO_GET_MAX_IDS,
+					  &current_state);
+
+	/*
+	 * Now List must be full, the set global notification must be part of
+	 * 'info_get_retrieved', and the 'current_state' should be set to FULL
+	 * due to the pending per-vCPU notification in VCPU 0.
+	 */
+	EXPECT_EQ(ids_count, FFA_NOTIFICATIONS_INFO_GET_MAX_IDS);
+	EXPECT_EQ(current_state, FULL);
+	EXPECT_EQ(notifications->global.info_get_retrieved,
+		  FFA_NOTIFICATION_MASK(2));
+
+	got = vm_notifications_get_pending_and_clear(current_vm_locked,
+						     is_from_vm, 0);
+	EXPECT_EQ(got, FFA_NOTIFICATION_MASK(1) | FFA_NOTIFICATION_MASK(2));
+
+	vm_unlock(&current_vm_locked);
+}
+
+TEST_F(vm, vm_notifications_info_get_full_global)
+{
+	struct_vm *current_vm = vm_find_index(0);
+	struct vm_locked current_vm_locked = vm_lock(current_vm);
+	ffa_notifications_bitmap_t got;
+	struct notifications *notifications;
+	const bool is_from_vm = false;
+	/*
+	 * Following set of variables that are also expected to be used when
+	 * handling ffa_notification_info_get.
+	 * For this 'ids_count' has been initialized such that it indicates
+	 * there is no space in the list for a global notification (VM ID only).
+	 */
+	uint16_t ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t ids_count = FFA_NOTIFICATIONS_INFO_GET_MAX_IDS;
+	uint32_t lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
+	uint32_t lists_count = 10;
+	enum notifications_info_get_state current_state = INIT;
+
+	CHECK(vm_get_count() >= 1);
+
+	current_vm = vm_find_index(0);
+
+	notifications = &current_vm->notifications.from_sp;
+
+	/* Set global notification. */
+	vm_notifications_set(current_vm_locked, is_from_vm,
+			     FFA_NOTIFICATION_MASK(10), 0, false);
+
+	/* Get notifications info for the given notifications. */
+	vm_notifications_info_get_pending(current_vm_locked, is_from_vm, ids,
+					  &ids_count, lists_sizes, &lists_count,
+					  FFA_NOTIFICATIONS_INFO_GET_MAX_IDS,
+					  &current_state);
+
+	/* Expect 'info_get_retrieved' bitmap to be 0. */
+	EXPECT_EQ(notifications->global.info_get_retrieved, 0U);
+	EXPECT_EQ(notifications->global.pending, FFA_NOTIFICATION_MASK(10));
+	EXPECT_EQ(ids_count, FFA_NOTIFICATIONS_INFO_GET_MAX_IDS);
+	EXPECT_EQ(current_state, FULL);
+
+	got = vm_notifications_get_pending_and_clear(current_vm_locked,
+						     is_from_vm, 0);
+	vm_unlock(&current_vm_locked);
+}
+
 } /* namespace */