feat(mm): add `get_mode_partial`

Add the `get_mode_partial` function for querying the mode of a range of
pages, and returning the longest subrange of pages that have the same
mode. This is needed for the new behaviour of `FFA_MEM_PERM_GET` added
in v1.3.

Change-Id: I9bdfd7f76c45faec7793dd0023e70409ce5bea0f
Signed-off-by: Karl Meakin <karl.meakin@arm.com>
diff --git a/src/mm.c b/src/mm.c
index 74002c5..f53cfeb 100644
--- a/src/mm.c
+++ b/src/mm.c
@@ -1142,6 +1142,26 @@
 	return success;
 }
 
+bool mm_vm_get_mode_partial(const struct mm_ptable *ptable, ipaddr_t begin,
+			    ipaddr_t end, mm_mode_t *mode, ipaddr_t *end_ret)
+{
+	struct mm_get_attrs_state ret;
+	bool success;
+
+	ret = mm_get_attrs(ptable, ipa_addr(begin), ipa_addr(end));
+	success = ret.got_attrs;
+
+	if (success && mode != NULL) {
+		*mode = arch_mm_stage2_attrs_to_mode(ret.attrs);
+	}
+
+	if (success && end_ret != NULL) {
+		*end_ret = ret.mismatch ? ipa_init(ret.mismatch) : end;
+	}
+
+	return success;
+}
+
 /**
  * Gets the mode of the given range of virtual addresses if they
  * are mapped with the same mode.
@@ -1166,6 +1186,28 @@
 	return success;
 }
 
+bool mm_get_mode_partial(const struct mm_ptable *ptable, vaddr_t begin,
+			 vaddr_t end, mm_mode_t *mode, vaddr_t *end_ret)
+{
+	struct mm_get_attrs_state ret;
+	bool success;
+
+	assert(ptable->stage1);
+
+	ret = mm_get_attrs(ptable, va_addr(begin), va_addr(end));
+	success = ret.got_attrs;
+
+	if (success && mode != NULL) {
+		*mode = arch_mm_stage1_attrs_to_mode(ret.attrs);
+	}
+
+	if (success && end_ret != NULL) {
+		*end_ret = ret.mismatch ? va_init(ret.mismatch) : end;
+	}
+
+	return success;
+}
+
 static struct mm_stage1_locked mm_stage1_lock_unsafe(void)
 {
 	return (struct mm_stage1_locked){.ptable = &ptable};
diff --git a/src/mm_test.cc b/src/mm_test.cc
index d060139..22e5dc0 100644
--- a/src/mm_test.cc
+++ b/src/mm_test.cc
@@ -980,6 +980,66 @@
 	EXPECT_THAT(read_mode, Eq(mode));
 }
 
+TEST_F(mm, get_mode_partial)
+{
+	constexpr mm_mode_t mode0 = MM_MODE_R;
+	constexpr mm_mode_t mode1 = MM_MODE_W;
+	constexpr mm_mode_t mode2 = MM_MODE_X;
+
+	mm_mode_t ret_mode;
+
+	const paddr_t page0_start = pa_init(0);
+	const paddr_t page0_end = pa_init(PAGE_SIZE * 1);
+	const paddr_t page1_start = pa_init(PAGE_SIZE * 1);
+	const paddr_t page1_end = pa_init(PAGE_SIZE * 2);
+	const paddr_t page2_start = pa_init(PAGE_SIZE * 2);
+	const paddr_t page2_end = pa_init(PAGE_SIZE * 3);
+	ipaddr_t end_ret;
+
+	ASSERT_TRUE(mm_vm_identity_map(&ptable, page0_start, page0_end, mode0,
+				       &ppool, nullptr));
+	ASSERT_TRUE(mm_vm_identity_map(&ptable, page1_start, page1_end, mode1,
+				       &ppool, nullptr));
+	ASSERT_TRUE(mm_vm_identity_map(&ptable, page2_start, page2_end, mode2,
+				       &ppool, nullptr));
+
+	EXPECT_TRUE(mm_vm_get_mode(&ptable, ipa_from_pa(page0_start),
+				   ipa_from_pa(page0_end), &ret_mode));
+	EXPECT_THAT(ret_mode, Eq(mode0));
+
+	EXPECT_TRUE(mm_vm_get_mode(&ptable, ipa_from_pa(page1_start),
+				   ipa_from_pa(page1_end), &ret_mode));
+	EXPECT_THAT(ret_mode, Eq(mode1));
+
+	EXPECT_TRUE(mm_vm_get_mode(&ptable, ipa_from_pa(page2_start),
+				   ipa_from_pa(page2_end), &ret_mode));
+	EXPECT_THAT(ret_mode, Eq(mode2));
+
+	EXPECT_FALSE(mm_vm_get_mode(&ptable, ipa_from_pa(page0_start),
+				    ipa_from_pa(page2_end), nullptr));
+	EXPECT_FALSE(mm_vm_get_mode(&ptable, ipa_from_pa(page0_start),
+				    ipa_from_pa(page1_end), nullptr));
+
+	EXPECT_TRUE(mm_vm_get_mode_partial(&ptable, ipa_from_pa(page0_start),
+					   ipa_from_pa(page1_end), &ret_mode,
+					   &end_ret));
+	EXPECT_EQ(ipa_addr(end_ret), ipa_addr(ipa_from_pa(page1_start)));
+	EXPECT_EQ(ret_mode, mode0);
+
+	EXPECT_TRUE(mm_vm_get_mode_partial(&ptable, ipa_from_pa(page1_start),
+					   ipa_from_pa(page2_end), &ret_mode,
+					   &end_ret));
+	EXPECT_EQ(ipa_addr(end_ret), ipa_addr(ipa_from_pa(page2_start)));
+	EXPECT_EQ(ret_mode, mode1);
+
+	EXPECT_TRUE(mm_vm_get_mode_partial(
+		&ptable, ipa_from_pa(page2_start),
+		ipa_from_pa(pa_add(page2_end, 2 * PAGE_SIZE)), &ret_mode,
+		&end_ret));
+	EXPECT_EQ(ipa_addr(end_ret), ipa_addr(ipa_from_pa(page2_end)));
+	EXPECT_EQ(ret_mode, mode2);
+}
+
 /**
  * Anything out of range fail to retrieve the mode.
  */