feat(memory share): retrieve multiple borrowers

The handling of FFA_MEM_RETRIEVE_REQ is adapted to comply with multiple
borrowers use:
- Validate the given permissions by the sender, against those requested
by the borrower on call to FFA_MEM_RETRIEVE_REQ.
- Validate also the remaining borrowers specified at receive request,
that these are the same as those specified by the sender.
- Validate state of the retrieve request per receiver specified by the
sender.

Change-Id: I613b785ccd061638ca679d8c2df64b6862daffa4
Signed-off-by: J-Alves <joao.alves@arm.com>
diff --git a/src/ffa_memory.c b/src/ffa_memory.c
index 8c8ad8c..6da0459 100644
--- a/src/ffa_memory.c
+++ b/src/ffa_memory.c
@@ -2223,6 +2223,226 @@
 	}
 }
 
+/*
+ * Gets the receiver's access permissions from 'struct ffa_memory_region' and
+ * returns its index in the receiver's array. If receiver's ID doesn't exist
+ * in the array, return the region's 'receiver_count'.
+ */
+static uint32_t ffa_memory_region_get_receiver(
+	struct ffa_memory_region *memory_region, ffa_vm_id_t receiver)
+{
+	struct ffa_memory_access *receivers;
+	uint32_t i;
+
+	assert(memory_region != NULL);
+
+	receivers = memory_region->receivers;
+
+	for (i = 0U; i < memory_region->receiver_count; i++) {
+		if (receivers[i].receiver_permissions.receiver == receiver) {
+			break;
+		}
+	}
+
+	return i;
+}
+
+/**
+ * Validates the retrieved permissions against those specified by the lender
+ * of memory share operation. Optionally can help set the permissions to be used
+ * for the S2 mapping, through the `permissions` argument.
+ * Returns true if permissions are valid, false otherwise.
+ */
+static bool ffa_memory_retrieve_is_memory_access_valid(
+	enum ffa_data_access sent_data_access,
+	enum ffa_data_access requested_data_access,
+	enum ffa_instruction_access sent_instruction_access,
+	enum ffa_instruction_access requested_instruction_access,
+	ffa_memory_access_permissions_t *permissions)
+{
+	switch (sent_data_access) {
+	case FFA_DATA_ACCESS_NOT_SPECIFIED:
+	case FFA_DATA_ACCESS_RW:
+		if (requested_data_access == FFA_DATA_ACCESS_NOT_SPECIFIED ||
+		    requested_data_access == FFA_DATA_ACCESS_RW) {
+			if (permissions != NULL) {
+				ffa_set_data_access_attr(permissions,
+							 FFA_DATA_ACCESS_RW);
+			}
+			break;
+		}
+		/* Intentional fall-through. */
+	case FFA_DATA_ACCESS_RO:
+		if (requested_data_access == FFA_DATA_ACCESS_NOT_SPECIFIED ||
+		    requested_data_access == FFA_DATA_ACCESS_RO) {
+			if (permissions != NULL) {
+				ffa_set_data_access_attr(permissions,
+							 FFA_DATA_ACCESS_RO);
+			}
+			break;
+		}
+		dlog_verbose(
+			"Invalid data access requested; sender specified "
+			"permissions %#x but receiver requested %#x.\n",
+			sent_data_access, requested_data_access);
+		return false;
+	case FFA_DATA_ACCESS_RESERVED:
+		panic("Got unexpected FFA_DATA_ACCESS_RESERVED. Should be "
+		      "checked before this point.");
+	}
+
+	switch (sent_instruction_access) {
+	case FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED:
+	case FFA_INSTRUCTION_ACCESS_X:
+		if (requested_instruction_access ==
+			    FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED ||
+		    requested_instruction_access == FFA_INSTRUCTION_ACCESS_X) {
+			if (permissions != NULL) {
+				ffa_set_instruction_access_attr(
+					permissions, FFA_INSTRUCTION_ACCESS_X);
+			}
+			break;
+		}
+	case FFA_INSTRUCTION_ACCESS_NX:
+		if (requested_instruction_access ==
+			    FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED ||
+		    requested_instruction_access == FFA_INSTRUCTION_ACCESS_NX) {
+			if (permissions != NULL) {
+				ffa_set_instruction_access_attr(
+					permissions, FFA_INSTRUCTION_ACCESS_NX);
+			}
+			break;
+		}
+		dlog_verbose(
+			"Invalid instruction access requested; sender "
+			"specified permissions %#x but receiver requested "
+			"%#x.\n",
+			sent_instruction_access, requested_instruction_access);
+		return false;
+	case FFA_INSTRUCTION_ACCESS_RESERVED:
+		panic("Got unexpected FFA_INSTRUCTION_ACCESS_RESERVED. Should "
+		      "be checked before this point.");
+	}
+
+	return true;
+}
+
+/**
+ * Validate the receivers' permissions in the retrieve request against those
+ * specified by the lender.
+ * In the `permissions` argument returns the permissions to set at S2 for the
+ * caller to the FFA_MEMORY_RETRIEVE_REQ.
+ * Returns FFA_SUCCESS if all specified permissions are valid.
+ */
+static struct ffa_value ffa_memory_retrieve_validate_memory_access_list(
+	struct ffa_memory_region *memory_region,
+	struct ffa_memory_region *retrieve_request, ffa_vm_id_t to_vm_id,
+	ffa_memory_access_permissions_t *permissions)
+{
+	uint32_t retrieve_receiver_index;
+
+	assert(permissions != NULL);
+
+	if (retrieve_request->receiver_count != memory_region->receiver_count) {
+		dlog_verbose(
+			"Retrieve request should contain same list of "
+			"borrowers, as specified by the lender.\n");
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	retrieve_receiver_index = retrieve_request->receiver_count;
+
+	/* Should be populated with the permissions of the retriever. */
+	*permissions = 0;
+
+	for (uint32_t i = 0U; i < retrieve_request->receiver_count; i++) {
+		ffa_memory_access_permissions_t sent_permissions;
+		struct ffa_memory_access *current_receiver =
+			&retrieve_request->receivers[i];
+		ffa_memory_access_permissions_t requested_permissions =
+			current_receiver->receiver_permissions.permissions;
+		ffa_vm_id_t current_receiver_id =
+			current_receiver->receiver_permissions.receiver;
+		bool found_to_id = current_receiver_id == to_vm_id;
+
+		/*
+		 * Find the current receiver in the transaction descriptor from
+		 * sender.
+		 */
+		uint32_t mem_region_receiver_index =
+			ffa_memory_region_get_receiver(memory_region,
+						       current_receiver_id);
+
+		if (mem_region_receiver_index ==
+		    memory_region->receiver_count) {
+			dlog_verbose("%s: receiver %x not found\n", __func__,
+				     current_receiver_id);
+			return ffa_error(FFA_DENIED);
+		}
+
+		sent_permissions =
+			memory_region->receivers[mem_region_receiver_index]
+				.receiver_permissions.permissions;
+
+		if (found_to_id) {
+			retrieve_receiver_index = i;
+		}
+
+		/*
+		 * Since we are traversing the list of receivers, save the index
+		 * of the caller. As it needs to be there.
+		 */
+
+		if (current_receiver->composite_memory_region_offset != 0U) {
+			dlog_verbose(
+				"Retriever specified address ranges not "
+				"supported (got offset %d).\n",
+				current_receiver
+					->composite_memory_region_offset);
+			return ffa_error(FFA_INVALID_PARAMETERS);
+		}
+
+		/*
+		 * Check permissions from sender against permissions requested
+		 * by receiver.
+		 */
+		if (!ffa_memory_retrieve_is_memory_access_valid(
+			    ffa_get_data_access_attr(sent_permissions),
+			    ffa_get_data_access_attr(requested_permissions),
+			    ffa_get_instruction_access_attr(sent_permissions),
+			    ffa_get_instruction_access_attr(
+				    requested_permissions),
+			    found_to_id ? permissions : NULL)) {
+			return ffa_error(FFA_DENIED);
+		}
+
+		/*
+		 * Can't request PM to clear memory if only provided with RO
+		 * permissions.
+		 */
+		if (found_to_id &&
+		    (ffa_get_data_access_attr(*permissions) ==
+		     FFA_DATA_ACCESS_RO) &&
+		    (retrieve_request->flags & FFA_MEMORY_REGION_FLAG_CLEAR) !=
+			    0U) {
+			dlog_verbose(
+				"Receiver has RO permissions can not request "
+				"clear.\n");
+			return ffa_error(FFA_DENIED);
+		}
+	}
+
+	if (retrieve_receiver_index == retrieve_request->receiver_count) {
+		dlog_verbose(
+			"Retrieve request does not contain caller's (%x) "
+			"permissions\n",
+			to_vm_id);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	return (struct ffa_value){.func = FFA_SUCCESS_32};
+}
+
 struct ffa_value ffa_memory_retrieve(struct vm_locked to_locked,
 				     struct ffa_memory_region *retrieve_request,
 				     uint32_t retrieve_request_length,
@@ -2237,12 +2457,6 @@
 		retrieve_request->flags &
 		FFA_MEMORY_REGION_TRANSACTION_TYPE_MASK;
 	struct ffa_memory_region *memory_region;
-	ffa_memory_access_permissions_t sent_permissions;
-	enum ffa_data_access sent_data_access;
-	enum ffa_instruction_access sent_instruction_access;
-	ffa_memory_access_permissions_t requested_permissions;
-	enum ffa_data_access requested_data_access;
-	enum ffa_instruction_access requested_instruction_access;
 	ffa_memory_access_permissions_t permissions;
 	uint32_t memory_to_attributes;
 	struct share_states_locked share_states;
@@ -2251,6 +2465,7 @@
 	struct ffa_composite_memory_region *composite;
 	uint32_t total_length;
 	uint32_t fragment_length;
+	uint32_t receiver_index;
 
 	dump_share_states();
 
@@ -2263,15 +2478,6 @@
 		return ffa_error(FFA_INVALID_PARAMETERS);
 	}
 
-	if (retrieve_request->receiver_count != 1) {
-		dlog_verbose(
-			"Multi-way memory sharing not supported (got %d "
-			"receivers descriptors on FFA_MEM_RETRIEVE_REQ, "
-			"expected 1).\n",
-			retrieve_request->receiver_count);
-		return ffa_error(FFA_INVALID_PARAMETERS);
-	}
-
 	share_states = share_states_lock();
 	if (!get_share_state(share_states, handle, &share_state)) {
 		dlog_verbose("Invalid handle %#x for FFA_MEM_RETRIEVE_REQ.\n",
@@ -2280,10 +2486,41 @@
 		goto out;
 	}
 
+	if (!share_state->sending_complete) {
+		dlog_verbose(
+			"Memory with handle %#x not fully sent, can't "
+			"retrieve.\n",
+			handle);
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	}
+
 	memory_region = share_state->memory_region;
 	CHECK(memory_region != NULL);
 
 	/*
+	 * Find receiver index in the receivers list specified by the sender.
+	 */
+	receiver_index =
+		ffa_memory_region_get_receiver(memory_region, to_locked.vm->id);
+
+	if (receiver_index == memory_region->receiver_count) {
+		dlog_verbose(
+			"Incorrect receiver VM ID %x for FFA_MEM_RETRIEVE_REQ, "
+			"for handle %#x.\n",
+			to_locked.vm->id, handle);
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	}
+
+	if (share_state->retrieved_fragment_count[receiver_index] != 0U) {
+		dlog_verbose("Memory with handle %#x already retrieved.\n",
+			     handle);
+		ret = ffa_error(FFA_DENIED);
+		goto out;
+	}
+
+	/*
 	 * Check that the transaction type expected by the receiver is correct,
 	 * if it has been specified.
 	 */
@@ -2321,59 +2558,8 @@
 		goto out;
 	}
 
-	if (retrieve_request->receivers[0].receiver_permissions.receiver !=
-	    to_locked.vm->id) {
-		dlog_verbose(
-			"Retrieve request receiver VM ID %d didn't match "
-			"caller of FFA_MEM_RETRIEVE_REQ.\n",
-			retrieve_request->receivers[0]
-				.receiver_permissions.receiver);
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
-	}
-
-	if (memory_region->receivers[0].receiver_permissions.receiver !=
-	    to_locked.vm->id) {
-		dlog_verbose(
-			"Incorrect receiver VM ID %d for FFA_MEM_RETRIEVE_REQ, "
-			"expected %d for handle %#x.\n",
-			to_locked.vm->id,
-			memory_region->receivers[0]
-				.receiver_permissions.receiver,
-			handle);
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
-	}
-
-	if (!share_state->sending_complete) {
-		dlog_verbose(
-			"Memory with handle %#x not fully sent, can't "
-			"retrieve.\n",
-			handle);
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
-	}
-
-	if (share_state->retrieved_fragment_count[0] != 0) {
-		dlog_verbose("Memory with handle %#x already retrieved.\n",
-			     handle);
-		ret = ffa_error(FFA_DENIED);
-		goto out;
-	}
-
-	if (retrieve_request->receivers[0].composite_memory_region_offset !=
-	    0) {
-		dlog_verbose(
-			"Retriever specified address ranges not supported (got "
-			"offset %d).\n",
-			retrieve_request->receivers[0]
-				.composite_memory_region_offset);
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
-	}
-
 	if ((retrieve_request->flags &
-	     FFA_MEMORY_REGION_ADDRESS_RANGE_HINT_VALID) != 0) {
+	     FFA_MEMORY_REGION_ADDRESS_RANGE_HINT_VALID) != 0U) {
 		dlog_verbose(
 			"Retriever specified 'address range alignment hint'"
 			" not supported.\n");
@@ -2413,8 +2599,7 @@
 	 * FFA_MEM_LEND/FFA_MEM_DONATE, else return FFA_DENIED.
 	 */
 	if ((retrieve_request->flags & FFA_MEMORY_REGION_FLAG_CLEAR) != 0U &&
-	    (share_state->memory_region->flags &
-	     FFA_MEMORY_REGION_FLAG_CLEAR) == 0U) {
+	    (memory_region->flags & FFA_MEMORY_REGION_FLAG_CLEAR) == 0U) {
 		dlog_verbose(
 			"Borrower needs memory cleared. Sender needs to set "
 			"flag for clearing memory.\n");
@@ -2422,88 +2607,13 @@
 		goto out;
 	}
 
-	/*
-	 * Check permissions from sender against permissions requested by
-	 * receiver.
-	 */
-	sent_permissions =
-		memory_region->receivers[0].receiver_permissions.permissions;
-	sent_data_access = ffa_get_data_access_attr(sent_permissions);
-	sent_instruction_access =
-		ffa_get_instruction_access_attr(sent_permissions);
-	requested_permissions =
-		retrieve_request->receivers[0].receiver_permissions.permissions;
-	requested_data_access = ffa_get_data_access_attr(requested_permissions);
-	requested_instruction_access =
-		ffa_get_instruction_access_attr(requested_permissions);
-	permissions = 0;
-
-	if ((sent_data_access == FFA_DATA_ACCESS_RO ||
-	     requested_permissions == FFA_DATA_ACCESS_RO) &&
-	    (retrieve_request->flags & FFA_MEMORY_REGION_FLAG_CLEAR) != 0U) {
-		dlog_verbose(
-			"Receiver has RO permissions can not request clear.\n");
-		ret = ffa_error(FFA_DENIED);
+	ret = ffa_memory_retrieve_validate_memory_access_list(
+		memory_region, retrieve_request, to_locked.vm->id,
+		&permissions);
+	if (ret.func != FFA_SUCCESS_32) {
 		goto out;
 	}
 
-	switch (sent_data_access) {
-	case FFA_DATA_ACCESS_NOT_SPECIFIED:
-	case FFA_DATA_ACCESS_RW:
-		if (requested_data_access == FFA_DATA_ACCESS_NOT_SPECIFIED ||
-		    requested_data_access == FFA_DATA_ACCESS_RW) {
-			ffa_set_data_access_attr(&permissions,
-						 FFA_DATA_ACCESS_RW);
-			break;
-		}
-		/* Intentional fall-through. */
-	case FFA_DATA_ACCESS_RO:
-		if (requested_data_access == FFA_DATA_ACCESS_NOT_SPECIFIED ||
-		    requested_data_access == FFA_DATA_ACCESS_RO) {
-			ffa_set_data_access_attr(&permissions,
-						 FFA_DATA_ACCESS_RO);
-			break;
-		}
-		dlog_verbose(
-			"Invalid data access requested; sender specified "
-			"permissions %#x but receiver requested %#x.\n",
-			sent_permissions, requested_permissions);
-		ret = ffa_error(FFA_DENIED);
-		goto out;
-	case FFA_DATA_ACCESS_RESERVED:
-		panic("Got unexpected FFA_DATA_ACCESS_RESERVED. Should be "
-		      "checked before this point.");
-	}
-	switch (sent_instruction_access) {
-	case FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED:
-	case FFA_INSTRUCTION_ACCESS_X:
-		if (requested_instruction_access ==
-			    FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED ||
-		    requested_instruction_access == FFA_INSTRUCTION_ACCESS_X) {
-			ffa_set_instruction_access_attr(
-				&permissions, FFA_INSTRUCTION_ACCESS_X);
-			break;
-		}
-	case FFA_INSTRUCTION_ACCESS_NX:
-		if (requested_instruction_access ==
-			    FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED ||
-		    requested_instruction_access == FFA_INSTRUCTION_ACCESS_NX) {
-			ffa_set_instruction_access_attr(
-				&permissions, FFA_INSTRUCTION_ACCESS_NX);
-			break;
-		}
-		dlog_verbose(
-			"Invalid instruction access requested; sender "
-			"specified permissions %#x but receiver requested "
-			"%#x.\n",
-			sent_permissions, requested_permissions);
-		ret = ffa_error(FFA_DENIED);
-		goto out;
-	case FFA_INSTRUCTION_ACCESS_RESERVED:
-		panic("Got unexpected FFA_INSTRUCTION_ACCESS_RESERVED. Should "
-		      "be checked before this point.");
-	}
-
 	if (ffa_get_memory_type_attr(retrieve_request->attributes) !=
 	    FFA_MEMORY_NOT_SPECIFIED_MEM) {
 		/*
@@ -2554,8 +2664,8 @@
 	to_locked.vm->mailbox.recv_func = FFA_MEM_RETRIEVE_RESP_32;
 	to_locked.vm->mailbox.state = MAILBOX_STATE_READ;
 
-	share_state->retrieved_fragment_count[0] = 1;
-	if (share_state->retrieved_fragment_count[0] ==
+	share_state->retrieved_fragment_count[receiver_index] = 1;
+	if (share_state->retrieved_fragment_count[receiver_index] ==
 	    share_state->fragment_count) {
 		ffa_memory_retrieve_complete(share_states, share_state,
 					     page_pool);