[PATCH v2] KVM: arm64: Honour the SPMC FF-A RX/TX buffer size limits

Kim Mankyum via B4 Relay <[email protected]>
Newsgroups org.infradead.lists.linux-arm-kernel,dev.linux.lists.kvmarm,org.kernel.feeds.b4-sent,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Kim Mankyum <[email protected]>

pKVM currently sizes its FF-A RX/TX buffers according to PAGE_SIZE:
do_ffa_rxtx_map() rejects any FFA_RXTX_MAP request from the host whose
page count does not match the hyp buffers' full PAGE_SIZE capacity,
and FFA_FEATURES for FFA_RXTX_MAP never tells the host otherwise.
hyp_ffa_post_init() already queries the SPMC's minimum RX/TX buffer
size, but only for a feasibility check.

This breaks when PAGE_SIZE is larger than the RX/TX buffer size the
SPMC actually supports. For example, an FF-A 1.2 SPMC advertising both
a minimum and a maximum RX/TX buffer size of 4K rejects the 16K
FFA_RXTX_MAP request that pKVM consequently forwards to the SPMC on a
16K kernel.

Compute the RX/TX buffer size pKVM and the SPMC both support in
hyp_ffa_post_init(), from the SPMC's advertised minimum and (FF-A 1.2
onwards) maximum sizes, capped at the hyp buffers' capacity; below
FF-A 1.2 the maximum field is undefined, so fall back to the minimum.
Store it in hyp_ffa_rxtx_sz, report it to the host via
FFA_FEATURES(FFA_RXTX_MAP), and require the host's FFA_RXTX_MAP
request to match it exactly, as before. The other buffer-size bound
checks in this file are updated to use hyp_ffa_rxtx_sz too, since that
is the amount of the hyp buffers actually visible to the SPMC once it
is smaller than PAGE_SIZE.

Host page ownership remains PAGE_SIZE-granular: do_ffa_rxtx_map()
still shares and pins the entire host page backing each RX/TX buffer.
Such pages leave the plain PKVM_PAGE_OWNED state, so any subsequent
host FF-A share/lend on any part of them is rejected by
__pkvm_host_share_ffa(). The part of a page not visible to the SPMC
therefore stays pinned but is never exposed to it.

Fixes: 9d0c6a9af9e3 ("KVM: arm64: Handle FFA_RXTX_MAP and FFA_RXTX_UNMAP calls from the host")
Suggested-by: Sebastian Ene <[email protected]>
Signed-off-by: Kim Mankyum <[email protected]>
---
Changes in v2:
- Rework the fix to negotiate the RX/TX buffer size with the SPMC
  instead of relaxing the FFA_RXTX_MAP page-count validation.
- Account for the maximum RX/TX buffer size advertised since FF-A 1.2.
- Handle FFA_FEATURES(FFA_RXTX_MAP) in pKVM so the host discovers the
  negotiated size.
- Use the negotiated size for the SPMC-facing buffer bounds.
- Keep host page sharing and pinning PAGE_SIZE-granular, addressing the
  partial-page sharing concern raised in v1.

Link to v1: https://patch.msgid.link/[email protected]
---
 arch/arm64/kvm/hyp/nvhe/ffa.c | 62 ++++++++++++++++++++++++++++++++++++++-----
 include/linux/arm_ffa.h       |  7 +++++
 2 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c
index a327c2bbb6b6..c3379d1e8fd7 100644
--- a/arch/arm64/kvm/hyp/nvhe/ffa.c
+++ b/arch/arm64/kvm/hyp/nvhe/ffa.c
@@ -71,6 +71,15 @@ static u32 hyp_ffa_version;
 static bool has_version_negotiated;
 static hyp_spinlock_t version_lock;
 
+/*
+ * Size, in bytes, of the RX/TX buffers used by the pKVM FF-A proxy: the
+ * portion of the (fixed, KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) hyp buffers
+ * that is actually mapped into the SPMC. Negotiated with the SPMC in
+ * hyp_ffa_post_init() and, since it is what the host must in turn provide,
+ * also reported to the host via FFA_FEATURES.
+ */
+static size_t hyp_ffa_rxtx_sz;
+
 static void ffa_to_smccc_error(struct arm_smccc_1_2_regs *res, u64 ffa_errno)
 {
 	*res = (struct arm_smccc_1_2_regs) {
@@ -239,7 +248,7 @@ static void do_ffa_rxtx_map(struct arm_smccc_1_2_regs *res,
 	int ret = 0;
 	void *rx_virt, *tx_virt;
 
-	if (npages != (KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) / FFA_PAGE_SIZE) {
+	if (npages != hyp_ffa_rxtx_sz / FFA_PAGE_SIZE) {
 		ret = FFA_RET_INVALID_PARAMETERS;
 		goto out;
 	}
@@ -421,7 +430,7 @@ static void do_ffa_mem_frag_tx(struct arm_smccc_1_2_regs *res,
 	int ret = FFA_RET_INVALID_PARAMETERS;
 	u32 nr_ranges;
 
-	if (fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE)
+	if (fraglen > hyp_ffa_rxtx_sz)
 		goto out;
 
 	if (fraglen % sizeof(*buf))
@@ -484,7 +493,7 @@ static void __do_ffa_mem_xfer(const u64 func_id,
 	size_t mem_region_len = FFA_MEM_REGION_SZ(hyp_ffa_version);
 
 	if (addr_mbz || npages_mbz || fraglen > len ||
-	    fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) {
+	    fraglen > hyp_ffa_rxtx_sz) {
 		ret = FFA_RET_INVALID_PARAMETERS;
 		goto out;
 	}
@@ -619,7 +628,7 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res,
 	 * bogus.
 	 */
 	if (offset + CONSTITUENTS_OFFSET(0) > len ||
-	    fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) {
+	    fraglen > hyp_ffa_rxtx_sz) {
 		ret = FFA_RET_ABORTED;
 		ffa_rx_release(res);
 		goto out_unlock;
@@ -723,6 +732,26 @@ static bool do_ffa_features(struct arm_smccc_1_2_regs *res,
 	}
 
 	switch (id) {
+	case FFA_RXTX_MAP:
+	case FFA_FN64_RXTX_MAP:
+		switch (hyp_ffa_rxtx_sz) {
+		case SZ_4K:
+			prop = FFA_FEAT_RXTX_MIN_SZ_4K;
+			break;
+		case SZ_16K:
+			prop = FFA_FEAT_RXTX_MIN_SZ_16K;
+			break;
+		case SZ_64K:
+			prop = FFA_FEAT_RXTX_MIN_SZ_64K;
+			break;
+		default:
+			ret = FFA_RET_NOT_SUPPORTED;
+		}
+
+		if (!ret && hyp_ffa_version >= FFA_VERSION_1_2)
+			prop |= FIELD_PREP(FFA_FEAT_RXTX_MAX_SZ_MASK,
+					    hyp_ffa_rxtx_sz / FFA_PAGE_SIZE);
+		goto out_handled;
 	case FFA_MEM_SHARE:
 	case FFA_FN64_MEM_SHARE:
 	case FFA_MEM_LEND:
@@ -741,7 +770,8 @@ static bool do_ffa_features(struct arm_smccc_1_2_regs *res,
 
 static int hyp_ffa_post_init(void)
 {
-	size_t min_rxtx_sz;
+	size_t min_rxtx_sz, max_rxtx_sz = 0;
+	size_t capacity = KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE;
 	struct arm_smccc_1_2_regs res;
 
 	hyp_smccc_1_2_smc(&(struct arm_smccc_1_2_regs){
@@ -774,9 +804,27 @@ static int hyp_ffa_post_init(void)
 		return -EINVAL;
 	}
 
-	if (min_rxtx_sz > PAGE_SIZE)
+	if (min_rxtx_sz > capacity)
 		return -EOPNOTSUPP;
 
+	/*
+	 * The maximum RX/TX buffer size was only added to FFA_FEATURES in
+	 * FF-A 1.2; the field is undefined on earlier versions, so treat it
+	 * as unavailable there and settle for the (guaranteed supported)
+	 * minimum size instead of guessing.
+	 */
+	if (hyp_ffa_version < FFA_VERSION_1_2) {
+		hyp_ffa_rxtx_sz = min_rxtx_sz;
+		return 0;
+	}
+
+	max_rxtx_sz = FIELD_GET(FFA_FEAT_RXTX_MAX_SZ_MASK, res.a2) * FFA_PAGE_SIZE;
+	if (max_rxtx_sz && max_rxtx_sz < min_rxtx_sz)
+		max_rxtx_sz = min_rxtx_sz;
+
+	/* A maximum of 0 means the SPMC does not enforce an upper bound. */
+	hyp_ffa_rxtx_sz = min(max_rxtx_sz ?: capacity, capacity);
+
 	return 0;
 }
 
@@ -868,7 +916,7 @@ static void do_ffa_part_get(struct arm_smccc_1_2_regs *res,
 	}
 
 	copy_sz = partition_sz * count;
-	if (copy_sz > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) {
+	if (copy_sz > hyp_ffa_rxtx_sz) {
 		ffa_to_smccc_res(res, FFA_RET_ABORTED);
 		goto out_unlock;
 	}
diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h
index e71d83ee0aef..a70d087174af 100644
--- a/include/linux/arm_ffa.h
+++ b/include/linux/arm_ffa.h
@@ -130,6 +130,13 @@
 #define FFA_FEAT_RXTX_MIN_SZ_16K	2
 #define FFA_FEAT_RXTX_MIN_SZ_MASK	GENMASK(1, 0)
 
+/*
+ * Maximum buffer size supported by the callee, expressed in units of
+ * FFA_PAGE_SIZE, as returned by an FFA_FEATURES query for FFA_RXTX_MAP.
+ * A value of 0 means no maximum size is enforced.
+ */
+#define FFA_FEAT_RXTX_MAX_SZ_MASK	GENMASK(31, 16)
+
 /* FFA Bus/Device/Driver related */
 struct ffa_device {
 	u32 id;

---
base-commit: cb8a75eec0877810b50aa1c5a833f929525cd2ee
change-id: 20260820-master-572418a358ab

Best regards,
--  
Kim Mankyum <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.