* Re: [PATCH] net: mana: cap HWC init max message size to HW_CHANNEL_MAX_REQUEST_SIZE
From: Erni Sri Satya Vennela @ 2026-07-20 20:07 UTC (permalink / raw)
To: Michael Bommarito
Cc: Haiyang Zhang, Dexuan Cui, Long Li, K . Y . Srinivasan, Wei Liu,
Andrew Lunn, Jakub Kicinski, Paolo Abeni, netdev, linux-hyperv,
linux-kernel, stable
In-Reply-To: <20260711150628.2914205-1-michael.bommarito@gmail.com>
On Sat, Jul 11, 2026 at 11:06:28AM -0400, Michael Bommarito wrote:
> mana_hwc_init_event_handler() in hw_channel.c stores device-advertised
> HWC_INIT_DATA_MAX_REQUEST and HWC_INIT_DATA_MAX_RESPONSE values
> without bounds checking. mana_hwc_alloc_dma_buf() later computes the
> DMA buffer size as MANA_PAGE_ALIGN(q_depth * max_msg_size) in 32-bit
> arithmetic. A malicious device returning a large max_msg_size causes
> the product to wrap, allocating a small buffer while laying out
> q_depth request slots at the unwrapped stride, placing slots outside
> the allocation.
I don't think the described data flow actually
exists in the current tree, so the security framing looks inaccurate.
Please check the comment below.
>
> Impact: a compromised hypervisor device model or malicious MANA PCI
> device can cause out-of-bounds DMA buffer writes during HWC channel
> initialization. A reproducer is available on request.
>
> Clamp both values to HW_CHANNEL_MAX_REQUEST_SIZE (4096), consistent
> with the cap already applied at the channel-create callsite.
>
> Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
> drivers/net/ethernet/microsoft/mana/hw_channel.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> index 48a9acea4ab6c..a0916b50cffce 100644
> --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> @@ -152,10 +152,14 @@ static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self,
> break;
>
> case HWC_INIT_DATA_MAX_REQUEST:
> + if (val == 0 || val > HW_CHANNEL_MAX_REQUEST_SIZE)
> + val = HW_CHANNEL_MAX_REQUEST_SIZE;
> hwc->hwc_init_max_req_msg_size = val;
> break;
>
> case HWC_INIT_DATA_MAX_RESPONSE:
> + if (val == 0 || val > HW_CHANNEL_MAX_REQUEST_SIZE)
> + val = HW_CHANNEL_MAX_REQUEST_SIZE;
> hwc->hwc_init_max_resp_msg_size = val;
> break;
>
The clamp is applied to hwc->hwc_init_max_req_msg_size and
hwc->hwc_init_max_resp_msg_size. Tracing where those two fields are
consumed:
mana_hwc_init_event_handler()
|
mana_hwc_establish_channel() // copies them out to *max_req_msg_size
| and *max_resp_msg_size
mana_hwc_create_channel() // passes those locals only to
| mana_hwc_test_channel()
mana_hwc_test_channel() // passed as parameters but never
used them
The DMA buffers that alloc_dma_buf() sizes are created from
mana_hwc_init_queues(), which is called with the compile-time constants:
err = mana_hwc_init_queues(hwc, HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH,
HW_CHANNEL_MAX_REQUEST_SIZE,
HW_CHANNEL_MAX_RESPONSE_SIZE);
Therefore, q_depth * max_msg_size cannot wrap from a device-controlled
value here.
Thanks,
Vennela
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH] net: mana: cap HWC init max message size to HW_CHANNEL_MAX_REQUEST_SIZE
From: Michael Bommarito @ 2026-07-21 0:40 UTC (permalink / raw)
To: Erni Sri Satya Vennela
Cc: Haiyang Zhang, Dexuan Cui, Long Li, K . Y . Srinivasan, Wei Liu,
Andrew Lunn, Jakub Kicinski, Paolo Abeni, netdev, linux-hyperv,
linux-kernel, stable
In-Reply-To: <al6ABAMHeE76yldL@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net>
On Mon, Jul 20, 2026 at 4:07 PM Erni Sri Satya Vennela
<ernis@linux.microsoft.com> wrote:
> I don't think the described data flow actually
> exists in the current tree, so the security framing looks inaccurate.
> Please check the comment below.
Agreed, I got mixed up on that. Sorry for the noise
Thanks,
Mike
^ permalink raw reply
* Re: [PATCH v9 8/8] drm/gpusvm: Use hmm_range_fault_unlocked_timeout() for range faults
From: Matthew Brost @ 2026-07-21 0:49 UTC (permalink / raw)
To: Stanislav Kinsburskii
Cc: airlied, akhilesh, akpm, corbet, dakr, david, decui, haiyangz,
jgg, kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
wei.liu, dri-devel, intel-xe, linux-mm, linux-doc, linux-hyperv,
linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178413941236.1155966.14485211391150425997.stgit@skinsburskii>
On Wed, Jul 15, 2026 at 11:16:52AM -0700, Stanislav Kinsburskii wrote:
> Several GPU SVM paths take mmap_read_lock() only to call hmm_range_fault()
> and open-code mmu interval sequence setup before each HMM walk. They also
> retry -EBUSY until HMM_RANGE_DEFAULT_TIMEOUT expires.
>
> Use hmm_range_fault_unlocked_timeout() for those faults. The HMM helper now
> owns mmap_lock acquisition and refreshes range->notifier_seq for its
> internal retries, while GPU SVM keeps its existing driver-lock validation
> with mmu_interval_read_retry() after a successful fault.
>
> Pass HMM_RANGE_DEFAULT_TIMEOUT as the helper retry budget for each HMM
> fault attempt. This scopes the timeout to repeated HMM notifier retries
> while preserving the outer retry loops that restart when the interval is
> invalidated before GPU SVM updates or consumes the mapping state.
>
This part doesn't seem right for get_pages(), see below.
> Leave drm_gpusvm_check_pages() on hmm_range_fault() because that path is
> called with the mmap lock already held by its caller.
>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@gmail.com>
> ---
> drivers/gpu/drm/drm_gpusvm.c | 61 +++++-------------------------------------
> 1 file changed, 7 insertions(+), 54 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c
> index 958cb605aedd..de5bbfe58ee9 100644
> --- a/drivers/gpu/drm/drm_gpusvm.c
> +++ b/drivers/gpu/drm/drm_gpusvm.c
> @@ -773,8 +773,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,
> .end = end,
> .dev_private_owner = dev_private_owner,
> };
> - unsigned long timeout =
> - jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> + unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> enum drm_gpusvm_scan_result state = DRM_GPUSVM_SCAN_UNPOPULATED, new_state;
> unsigned long *pfns;
> unsigned long npages = npages_in_range(start, end);
> @@ -788,22 +787,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,
> hmm_range.hmm_pfns = pfns;
>
> retry:
> - hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
> - mmap_read_lock(range->gpusvm->mm);
> -
> - while (true) {
> - err = hmm_range_fault(&hmm_range);
> - if (err == -EBUSY) {
> - if (time_after(jiffies, timeout))
> - break;
> -
> - hmm_range.notifier_seq =
> - mmu_interval_read_begin(notifier);
> - continue;
> - }
> - break;
> - }
> - mmap_read_unlock(range->gpusvm->mm);
> + err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
> if (err)
> goto err_free;
>
> @@ -1406,8 +1390,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
> .dev_private_owner = ctx->device_private_page_owner,
> };
> void *zdd;
> - unsigned long timeout =
> - jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> + unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> unsigned long i, j;
> unsigned long npages = npages_in_range(pages_start, pages_end);
> unsigned long num_dma_mapped;
> @@ -1422,9 +1405,6 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
> struct dma_iova_state *state = &svm_pages->state;
>
> retry:
> - if (time_after(jiffies, timeout))
> - return -EBUSY;
> -
I think that by deleting the code above, you have changed this function's
semantics by removing the hard cap of HMM_RANGE_DEFAULT_TIMEOUT. This
code was added because, on some non-production platforms, the timing in
this function could cause it to livelock.
Is there any reason this was remove aside from timeout variable not
being a deadline now? You likely should add the deadline back in.
Matt
> hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
> if (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))
> goto set_seqno;
> @@ -1439,21 +1419,7 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
> }
>
> hmm_range.hmm_pfns = pfns;
> - while (true) {
> - mmap_read_lock(mm);
> - err = hmm_range_fault(&hmm_range);
> - mmap_read_unlock(mm);
> -
> - if (err == -EBUSY) {
> - if (time_after(jiffies, timeout))
> - break;
> -
> - hmm_range.notifier_seq =
> - mmu_interval_read_begin(notifier);
> - continue;
> - }
> - break;
> - }
> + err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
> mmput(mm);
> if (err)
> goto err_free;
> @@ -1720,8 +1686,7 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,
> .end = drm_gpusvm_range_end(range),
> .dev_private_owner = NULL,
> };
> - unsigned long timeout =
> - jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> + unsigned long timeout = msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
> unsigned long *pfns;
> unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
> drm_gpusvm_range_end(range));
> @@ -1736,24 +1701,12 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,
> return -ENOMEM;
>
> hmm_range.hmm_pfns = pfns;
> - while (!time_after(jiffies, timeout)) {
> - hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
> - if (time_after(jiffies, timeout)) {
> - err = -ETIME;
> - break;
> - }
> -
> - mmap_read_lock(mm);
> - err = hmm_range_fault(&hmm_range);
> - mmap_read_unlock(mm);
> - if (err != -EBUSY)
> - break;
> - }
> + err = hmm_range_fault_unlocked_timeout(&hmm_range, timeout);
>
> kvfree(pfns);
> mmput(mm);
>
> - return err;
> + return err == -EBUSY ? -ETIME : err;
> }
> EXPORT_SYMBOL_GPL(drm_gpusvm_range_evict);
>
>
>
^ permalink raw reply
* [PATCH rdma-next] RDMA/mana_ib: drain QP references after partial table insertion
From: Leon Romanovsky @ 2026-07-21 15:00 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jason Gunthorpe, Leon Romanovsky,
Shiraz Saleem
Cc: linux-rdma, linux-hyperv, linux-kernel
From: Leon Romanovsky <leonro@nvidia.com>
mana_table_store_ud_qp() publishes a QP at its send-queue id before
inserting the receive-queue id, dropping the XArray lock between the two
xa_insert_irq() calls. A concurrent completion handler can look up the QP
and take a transient reference. When the second insertion fails, the
rollback erased only the send-queue entry and returned, leaving both the
initial table reference and the transient reference outstanding while RDMA
core frees the QP, causing a use-after-free.
Drain the reference as normal destruction does: drop the initial reference
and wait for qp->free, releasing the QP only after every concurrent lookup
returns its reference.
Fixes: 8001e9257eca ("RDMA/mana_ib: extend mana QP table")
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
---
Konstantin,
I saw this in one of the Sashiko runs, and I believe the AI is
correct. The reference can be acquired through the
GDMA_EQE_RNIC_QP_FATAL event.
Thanks
---
drivers/infiniband/hw/mana/qp.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/infiniband/hw/mana/qp.c b/drivers/infiniband/hw/mana/qp.c
index b5ff07e34eb7..c52f3ec14032 100644
--- a/drivers/infiniband/hw/mana/qp.c
+++ b/drivers/infiniband/hw/mana/qp.c
@@ -472,6 +472,12 @@ static void mana_table_remove_rc_qp(struct mana_ib_dev *mdev, struct mana_ib_qp
xa_erase_irq(&mdev->qp_table_wq, qp->ibqp.qp_num);
}
+static void mana_table_drain_qp_ref(struct mana_ib_qp *qp)
+{
+ mana_put_qp_ref(qp);
+ wait_for_completion(&qp->free);
+}
+
static int mana_table_store_ud_qp(struct mana_ib_dev *mdev, struct mana_ib_qp *qp)
{
u32 qids = qp->ud_qp.queues[MANA_UD_SEND_QUEUE].id | MANA_SENDQ_MASK;
@@ -490,6 +496,7 @@ static int mana_table_store_ud_qp(struct mana_ib_dev *mdev, struct mana_ib_qp *q
remove_sq:
xa_erase_irq(&mdev->qp_table_wq, qids);
+ mana_table_drain_qp_ref(qp);
return err;
}
@@ -537,8 +544,7 @@ static void mana_table_remove_qp(struct mana_ib_dev *mdev,
qp->ibqp.qp_type);
return;
}
- mana_put_qp_ref(qp);
- wait_for_completion(&qp->free);
+ mana_table_drain_qp_ref(qp);
}
static int mana_ib_create_rc_qp(struct ib_qp *ibqp, struct ib_pd *ibpd,
---
base-commit: 74f49255492a62658f36bf2578d7916f1c6ffad1
change-id: 20260721-if-mana-table-store-qp-qids-partiall-afa14f023261
Best regards,
--
Leon Romanovsky <leonro@nvidia.com>
^ permalink raw reply related
* Re: [PATCH v2] Drivers: hv: vmbus: add VTL2 redirect connection ID
From: Saurabh Singh Sengar @ 2026-07-21 15:11 UTC (permalink / raw)
To: Hardik Garg
Cc: K. Y. Srinivasan, Haiyang Zhang, Wei Liu, Dexuan Cui,
Michael Kelley, linux-hyperv, linux-kernel
In-Reply-To: <20260717001837.635756-1-hargar@linux.microsoft.com>
On Fri, Jul 17, 2026 at 12:18:37AM +0000, Hardik Garg wrote:
> VMBus sends CHANNELMSG_INITIATE_CONTACT through a Hyper-V message
> connection ID. Older protocol versions use VMBUS_MESSAGE_CONNECTION_ID,
> while protocol version 5.0 and newer normally use
> VMBUS_MESSAGE_CONNECTION_ID_4.
>
> For a VTL2 kernel using VMBus protocol 5.0 or newer, the host
> may expect INITIATE_CONTACT on either the redirect connection ID or
> VMBUS_MESSAGE_CONNECTION_ID_4. There is no capability indication that
> identifies which ID is active, so the driver must determine it at runtime.
>
> During VMBus negotiation, the redirect ID is tried first because it is
> used by VTL2 configurations with VMBus redirection enabled. If the
> redirect ID is unavailable, the host rejects it synchronously with
> HV_STATUS_INVALID_CONNECTION_ID, allowing fallback to the standard ID.
>
> Return a distinct error for an invalid Initiate Contact connection ID so
> this fallback does not mask other post-message failures or
> protocol-version rejections. Preserve the existing connection ID
> selection for older protocol versions or when running below VTL2.
>
> Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> ---
>
> Notes (v2-changelog):
> Changes in v2:
> - Replace the connection ID array and loop with a single redirect
> attempt, falling back to VMBUS_MESSAGE_CONNECTION_ID_4 only on
> -ENXIO.
> - Remove redundant connection-state and timeout checks and the
> unreachable return after the loop.
> - Simplify the commit message to describe runtime connection ID
> selection without relay and message-port implementation details.
> - Clarify the protocol 5.0 helper and VTL2 redirect comments, remove
> redundant comments, and use an exact VTL2 check.
>
> Link to v1: https://lore.kernel.org/r/20260714213838.3367103-1-hargar@linux.microsoft.com
>
> drivers/hv/connection.c | 47 +++++++++++++++++++++++----------------
> drivers/hv/hyperv_vmbus.h | 2 ++
> 2 files changed, 30 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
> index b5b322ce16df4..0fd50d4cb5739 100644
> --- a/drivers/hv/connection.c
> +++ b/drivers/hv/connection.c
> @@ -72,7 +72,8 @@ module_param(max_version, uint, S_IRUGO);
> MODULE_PARM_DESC(max_version,
> "Maximal VMBus protocol version which can be negotiated");
>
> -int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
> +static int vmbus_try_connection_id(struct vmbus_channel_msginfo *msginfo,
> + u32 version, u32 connection_id)
> {
> int ret = 0;
> struct vmbus_channel_initiate_contact *msg;
> @@ -87,20 +88,20 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
> msg->vmbus_version_requested = version;
>
> /*
> - * VMBus protocol 5.0 (VERSION_WIN10_V5) and higher require that we must
> - * use VMBUS_MESSAGE_CONNECTION_ID_4 for the Initiate Contact Message,
> - * and for subsequent messages, we must use the Message Connection ID
> - * field in the host-returned Version Response Message. And, with
> - * VERSION_WIN10_V5 and higher, we don't use msg->interrupt_page, but we
> - * tell the host explicitly that we still use VMBUS_MESSAGE_SINT(2) for
> - * compatibility.
> + * For VMBus protocol 5.0 (VERSION_WIN10_V5) and higher, use the
> + * caller-supplied connection_id for the Initiate Contact message so
> + * the caller can implement the required retry scheme. For subsequent
> + * messages, use the Message Connection ID field in the host-returned
> + * Version Response message. With VERSION_WIN10_V5 and higher, we don't
> + * use msg->interrupt_page, but tell the host explicitly that we still
> + * use VMBUS_MESSAGE_SINT(2) for compatibility.
> *
> * On old hosts, we should always use VMBUS_MESSAGE_CONNECTION_ID (1).
> */
> if (version >= VERSION_WIN10_V5) {
> msg->msg_sint = VMBUS_MESSAGE_SINT;
> msg->msg_vtl = ms_hyperv.vtl;
> - vmbus_connection.msg_conn_id = VMBUS_MESSAGE_CONNECTION_ID_4;
> + vmbus_connection.msg_conn_id = connection_id;
> } else {
> msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
> vmbus_connection.msg_conn_id = VMBUS_MESSAGE_CONNECTION_ID;
> @@ -165,6 +166,22 @@ int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
> return ret;
> }
>
> +int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, u32 version)
> +{
> + int ret;
> +
> + /* Try the redirect ID first for VTL2 with VMBus protocol 5.0+. */
> + if (version >= VERSION_WIN10_V5 && ms_hyperv.vtl == 2) {
> + ret = vmbus_try_connection_id(msginfo, version,
> + VMBUS_MESSAGE_CONNECTION_ID_REDIRECT);
> + if (ret != -ENXIO)
> + return ret;
> + }
> +
> + return vmbus_try_connection_id(msginfo, version,
> + VMBUS_MESSAGE_CONNECTION_ID_4);
> +}
> +
> /*
> * vmbus_connect - Sends a connect request on the partition service connection
> */
> @@ -457,18 +474,10 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep)
>
> switch (ret) {
> case HV_STATUS_INVALID_CONNECTION_ID:
> - /*
> - * See vmbus_negotiate_version(): VMBus protocol 5.0
> - * and higher require that we must use
> - * VMBUS_MESSAGE_CONNECTION_ID_4 for the Initiate
> - * Contact message, but on old hosts that only
> - * support VMBus protocol 4.0 or lower, here we get
> - * HV_STATUS_INVALID_CONNECTION_ID and we should
> - * return an error immediately without retrying.
> - */
> + /* Allow INITIATE_CONTACT to try another connection ID. */
> hdr = buffer;
> if (hdr->msgtype == CHANNELMSG_INITIATE_CONTACT)
> - return -EINVAL;
> + return -ENXIO;
> /*
> * We could get this if we send messages too
> * frequently.
> diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
> index 05a36854389af..87f8aca1a9fb5 100644
> --- a/drivers/hv/hyperv_vmbus.h
> +++ b/drivers/hv/hyperv_vmbus.h
> @@ -110,6 +110,8 @@ struct hv_input_post_message {
> enum {
> VMBUS_MESSAGE_CONNECTION_ID = 1,
> VMBUS_MESSAGE_CONNECTION_ID_4 = 4,
> + /* VTL2 redirect connection ID for INITIATE_CONTACT. */
> + VMBUS_MESSAGE_CONNECTION_ID_REDIRECT = 0x800074,
> VMBUS_MESSAGE_PORT_ID = 1,
> VMBUS_EVENT_CONNECTION_ID = 2,
> VMBUS_EVENT_PORT_ID = 2,
> --
> 2.34.1
Reviewed-by: Saurabh Sengar <ssengar@linux.microsoft.com>
^ permalink raw reply
* Re: [PATCH v9 1/8] mm/hmm: move page fault handling out of walk callbacks
From: David Hildenbrand (Arm) @ 2026-07-21 15:19 UTC (permalink / raw)
To: Stanislav Kinsburskii, airlied, akhilesh, akpm, corbet, dakr,
decui, haiyangz, jgg, kees, kys, leon, liam, lizhi.hou, ljs,
longli, lyude, maarten.lankhorst, mamin506, mhocko, mripard,
nouveau, ogabbay, oleg, rppt, shuah, simona, skhan, surenb,
tzimmermann, vbabka, wei.liu
Cc: dri-devel, intel-xe, linux-mm, linux-doc, linux-hyperv,
linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178413935809.1155966.6398279131483632373.stgit@skinsburskii>
On 7/15/26 20:15, Stanislav Kinsburskii wrote:
> hmm_range_fault() currently triggers page faults from inside the page-table
> walk callbacks: hmm_vma_walk_pmd(), hmm_vma_walk_pud(),
> hmm_vma_walk_hugetlb_entry() and the pte-level helper all call
> hmm_vma_fault(), which in turn calls handle_mm_fault() while the walker
> still holds nested locks. The pte spinlock is dropped explicitly by each
> caller, and the hugetlb path manually drops and retakes
> hugetlb_vma_lock_read around the fault to dodge a deadlock against the walk
> framework's unconditional unlock.
>
> This layering does not extend cleanly to fault handlers that may release
> mmap_lock (VM_FAULT_RETRY, VM_FAULT_COMPLETED). If the lock is dropped
> while walk_page_range() is mid-traversal, the VMA can be freed before the
> walk framework's matching hugetlb_vma_unlock_read(), turning that unlock
> into a use-after-free.
>
> Split the responsibilities the way get_user_pages() does. Walk callbacks
> become inspect-only: when they detect a range that needs to be faulted in,
> they record it in struct hmm_vma_walk and return a private sentinel
> (HMM_FAULT_PENDING). The outer loop in hmm_range_fault() then drops out of
> walk_page_range(), invokes a new helper hmm_do_fault() that calls
> handle_mm_fault() with only mmap_lock held, and restarts the walk so the
> now-present entries are collected into hmm_pfns.
>
> No functional change for existing callers. As a side effect the hugetlb
> callback no longer needs the hugetlb_vma_{un}lock_read dance, and every
> fault-path exit from the callbacks now releases the pte spinlock on a
> single, common path. This refactor is also a precursor for adding an
> unlockable variant of hmm_range_fault() in a follow-up patch.
>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@gmail.com>
> ---
[...]
>
> pfn = pud_pfn(pud) + ((addr & ~PUD_MASK) >> PAGE_SHIFT);
> @@ -564,21 +561,8 @@ static int hmm_vma_walk_hugetlb_entry(pte_t *pte, unsigned long hmask,
> required_fault =
> hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, cpu_flags);
> if (required_fault) {
> - int ret;
> -
> spin_unlock(ptl);
> - hugetlb_vma_unlock_read(vma);
> - /*
> - * Avoid deadlock: drop the vma lock before calling
> - * hmm_vma_fault(), which will itself potentially take and
> - * drop the vma lock. This is also correct from a
> - * protection point of view, because there is no further
> - * use here of either pte or ptl after dropping the vma
> - * lock.
> - */
> - ret = hmm_vma_fault(addr, end, required_fault, walk);
> - hugetlb_vma_lock_read(vma);
> - return ret;
> + return hmm_record_fault(addr, end, required_fault, walk);
Yes, that looks much better, as discussed. The downside is another vma_lookup()
in hmm_do_fault().
> }
>
> pfn = pte_pfn(entry) + ((start & ~hmask) >> PAGE_SHIFT);
> @@ -637,6 +621,44 @@ static const struct mm_walk_ops hmm_walk_ops = {
> .walk_lock = PGWALK_RDLOCK,
> };
>
> +/*
> + * hmm_do_fault - fault in a range recorded by a walk callback
> + *
> + * Called from the outer loop in hmm_range_fault() after a callback
> + * returned HMM_FAULT_PENDING. At this point we hold only mmap_lock;
> + * the page-table spinlock and any hugetlb_vma_lock acquired by the walk
> + * framework have already been released by the unwind.
> + *
> + * Returns -EBUSY on success (all pages faulted, caller should re-walk).
> + * Returns a negative errno on failure.
> + */
> +static int hmm_do_fault(struct mm_struct *mm,
> + struct hmm_vma_walk *hmm_vma_walk)
> +{
> + unsigned long addr = hmm_vma_walk->last;
> + unsigned long end = hmm_vma_walk->end;
> + unsigned int required_fault = hmm_vma_walk->required_fault;
end and required_fault could be const.
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply
* [PATCH v2 0/2] Drivers: hv: vmbus: Small cleanups
From: Sebastian Andrzej Siewior @ 2026-07-21 15:32 UTC (permalink / raw)
To: linux-hyperv, linux-rt-devel, linux-kernel
Cc: K. Y. Srinivasan, Dexuan Cui, Haiyang Zhang, Jan Kiszka, Long Li,
Michael Kelley, Wei Liu, Sebastian Andrzej Siewior
Replacing the lockdep_hardirq_threaded() annotation which does not
belong in drivers and removing the vmbus_irq_initialized which seems
redundant.
v1…v2: https://lore.kernel.org/all/20260401151517.1743555-1-bigeasy@linutronix.de/
- Added a comment next to the invocation of __vmbus_isr()
- Added the "Drivers:" prefix to the subject line
Sebastian Andrzej Siewior (2):
Drivers: hv: vmbus: Replace lockdep_hardirq_threaded() with lockdep
annotation
Drivers: hv: vmbus: Remove vmbus_irq_initialized
drivers/hv/vmbus_drv.c | 27 ++++++++++++++++-----------
1 file changed, 16 insertions(+), 11 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH v2 1/2] Drivers: hv: vmbus: Replace lockdep_hardirq_threaded() with lockdep annotation
From: Sebastian Andrzej Siewior @ 2026-07-21 15:32 UTC (permalink / raw)
To: linux-hyperv, linux-rt-devel, linux-kernel
Cc: K. Y. Srinivasan, Dexuan Cui, Haiyang Zhang, Jan Kiszka, Long Li,
Michael Kelley, Wei Liu, Sebastian Andrzej Siewior
In-Reply-To: <20260721153216.382536-1-bigeasy@linutronix.de>
lockdep_hardirq_threaded() is supposed to be used within IRQ core code
and not within drivers. It is not obvious from within the driver, that
this is the only interrupt service routing and that it is not shared
handler.
Replace lockdep_hardirq_threaded() with a lockdep annotation limiting
threaded context on PREEMPT_RT to __vmbus_isr().
Fixes: f8e6343b7a89c ("Drivers: hv: vmbus: Use kthread for vmbus interrupts on PREEMPT_RT")
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
drivers/hv/vmbus_drv.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 23206640c6139..44877664d9d08 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -1384,8 +1384,19 @@ void vmbus_isr(void)
if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
vmbus_irqd_wake();
} else {
- lockdep_hardirq_threaded();
+ static DEFINE_WAIT_OVERRIDE_MAP(vmbus_map, LD_WAIT_CONFIG);
+
+ /*
+ * vmbus_isr is never force-threaded and always invoked at hard
+ * IRQ level. __vmbus_isr() below can acquire a spinlock_t
+ * which becomes a sleeping lock and must not be acquired in
+ * this context. Therefore on PREEMPT_RT this will be threaded
+ * via vmbus_irqd_wake(). On non-PREEMPT the annotation lets
+ * lockdep know that acquiring a spinlock_t is not an issue.
+ */
+ lock_map_acquire_try(&vmbus_map);
__vmbus_isr();
+ lock_map_release(&vmbus_map);
}
}
EXPORT_SYMBOL_FOR_MODULES(vmbus_isr, "mshv_vtl");
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/2] Drivers: hv: vmbus: Remove vmbus_irq_initialized
From: Sebastian Andrzej Siewior @ 2026-07-21 15:32 UTC (permalink / raw)
To: linux-hyperv, linux-rt-devel, linux-kernel
Cc: K. Y. Srinivasan, Dexuan Cui, Haiyang Zhang, Jan Kiszka, Long Li,
Michael Kelley, Wei Liu, Sebastian Andrzej Siewior
In-Reply-To: <20260721153216.382536-1-bigeasy@linutronix.de>
vmbus_irq_initialized is only true if the registration of the per-CPU
threads succeeded. If it failed, the whole registration aborts and the
vmbus_exit() path is never called.
Remove vmbus_irq_initialized.
Reviewed-by: Michael Kelley <mhklinux@outlook.com>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
drivers/hv/vmbus_drv.c | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 44877664d9d08..95a2c6049854d 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -1369,8 +1369,6 @@ static void run_vmbus_irqd(unsigned int cpu)
__vmbus_isr();
}
-static bool vmbus_irq_initialized;
-
static struct smp_hotplug_thread vmbus_irq_threads = {
.store = &vmbus_irqd,
.setup = vmbus_irqd_setup,
@@ -1497,11 +1495,10 @@ static int vmbus_bus_init(void)
* the VMbus interrupt handler.
*/
- if (IS_ENABLED(CONFIG_PREEMPT_RT) && !vmbus_irq_initialized) {
+ if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
ret = smpboot_register_percpu_thread(&vmbus_irq_threads);
if (ret)
goto err_kthread;
- vmbus_irq_initialized = true;
}
if (vmbus_irq == -1) {
@@ -1545,10 +1542,8 @@ static int vmbus_bus_init(void)
else
free_percpu_irq(vmbus_irq, &vmbus_evt);
err_setup:
- if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) {
+ if (IS_ENABLED(CONFIG_PREEMPT_RT))
smpboot_unregister_percpu_thread(&vmbus_irq_threads);
- vmbus_irq_initialized = false;
- }
err_kthread:
bus_unregister(&hv_bus);
return ret;
@@ -3045,10 +3040,9 @@ static void __exit vmbus_exit(void)
hv_remove_vmbus_handler();
else
free_percpu_irq(vmbus_irq, &vmbus_evt);
- if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) {
+ if (IS_ENABLED(CONFIG_PREEMPT_RT))
smpboot_unregister_percpu_thread(&vmbus_irq_threads);
- vmbus_irq_initialized = false;
- }
+
for_each_online_cpu(cpu) {
struct hv_per_cpu_context *hv_cpu
= per_cpu_ptr(hv_context.cpu_context, cpu);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v9 2/8] mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support
From: David Hildenbrand (Arm) @ 2026-07-21 15:42 UTC (permalink / raw)
To: Stanislav Kinsburskii, airlied, akhilesh, akpm, corbet, dakr,
decui, haiyangz, jgg, kees, kys, leon, liam, lizhi.hou, ljs,
longli, lyude, maarten.lankhorst, mamin506, mhocko, mripard,
nouveau, ogabbay, oleg, rppt, shuah, simona, skhan, surenb,
tzimmermann, vbabka, wei.liu
Cc: dri-devel, intel-xe, linux-mm, linux-doc, linux-hyperv,
linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178413936536.1155966.8918127042760531802.stgit@skinsburskii>
On 7/15/26 20:16, Stanislav Kinsburskii wrote:
> hmm_range_fault() requires the caller to hold the mmap read lock for the
> duration of the call. This is incompatible with mappings whose fault
> handler may release the mmap lock, notably userfaultfd-managed regions,
> where handle_mm_fault() can return VM_FAULT_RETRY or VM_FAULT_COMPLETED
> after dropping the lock. Drivers that need to populate device page tables
> for such mappings have no way to do so today.
>
> Add hmm_range_fault_unlocked_timeout() for callers that do not need to hold
> mmap_lock across any work outside the HMM fault itself. The helper takes
> mmap_read_lock_killable() internally, calls the common HMM fault
> implementation, and releases the lock before returning if it is still held.
> The timeout is specified in jiffies; passing 0 retries indefinitely, while
> a non-zero timeout makes the helper return -EBUSY when the retry budget
> expires.
The timeout does not / cannot affect how long we might be stuck without the mmap
lock in e.g., the userfaultfd handler. And also not how long it would take to
actually grab the mmap lock.
That's expected with the timeout, right?
[...]
Nothing else jumped at me.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH v2 1/2] Drivers: hv: vmbus: Replace lockdep_hardirq_threaded() with lockdep annotation
From: sashiko-bot @ 2026-07-21 15:47 UTC (permalink / raw)
To: Sebastian Andrzej Siewior; +Cc: linux-hyperv, linux-kernel, linux-rt-devel
In-Reply-To: <20260721153216.382536-2-bigeasy@linutronix.de>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The clock event timer interrupt (HVMSG_TIMER_EXPIRED) is incorrectly processed in a kthread on PREEMPT_RT, violating the architectural requirement that the system tick must run in hardirq context.
--
--- Patch [1]: [PATCH v2 1/2] Drivers: hv: vmbus: Replace lockdep_hardirq_threaded() with lockdep annotation ---
commit f939a4b9727e456e689d5d7488e6095b1358e5b5
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Drivers: hv: vmbus: Replace lockdep_hardirq_threaded() with lockdep annotation
This commit replaces the use of lockdep_hardirq_threaded() with a lockdep
annotation. This limits the threaded context on PREEMPT_RT to __vmbus_isr()
and clarifies that the ISR is not a shared handler.
> diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
> index 23206640c6139..44877664d9d08 100644
> --- a/drivers/hv/vmbus_drv.c
> +++ b/drivers/hv/vmbus_drv.c
> @@ -1384,8 +1384,19 @@ void vmbus_isr(void)
> if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
> vmbus_irqd_wake();
[Severity: Critical]
This isn't a bug introduced by this patch, but is it safe to defer all
VMbus interrupt processing to the vmbus_irqd kthread on PREEMPT_RT?
When booting a PREEMPT_RT kernel on a Hyper-V host using legacy timer mode,
the kthread handles the hypervisor timer message and invokes the timer ISR:
vmbus_message_sched()
if (msg->header.message_type == HVMSG_TIMER_EXPIRED) {
hv_stimer0_isr();
This executes the clock event handler in thread context, which breaks the
requirement that the system tick must run in hardirq context:
hv_stimer0_isr()
ce = this_cpu_ptr(hv_clock_event);
ce->event_handler(ce);
Because get_irq_regs() returns NULL when called from a kthread, calling
tick_handle_periodic() and then
update_process_times(user_mode(get_irq_regs())) will dereference a NULL
pointer and panic the kernel.
Alternatively, if high-resolution timers are used, tick_nohz_handler() sees
a NULL regs pointer and skips tick_sched_handle(), completely stalling the
scheduler tick and RCU updates, freezing the system.
> } else {
> - lockdep_hardirq_threaded();
> + static DEFINE_WAIT_OVERRIDE_MAP(vmbus_map, LD_WAIT_CONFIG);
> +
> + /*
> + * vmbus_isr is never force-threaded and always invoked at hard
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721153216.382536-1-bigeasy@linutronix.de?part=1
^ permalink raw reply
* Re: [PATCH v2 2/2] Drivers: hv: vmbus: Remove vmbus_irq_initialized
From: sashiko-bot @ 2026-07-21 15:49 UTC (permalink / raw)
To: Sebastian Andrzej Siewior; +Cc: linux-kernel, linux-hyperv, linux-rt-devel
In-Reply-To: <20260721153216.382536-3-bigeasy@linutronix.de>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Removal of vmbus_irq_initialized flag introduces a NULL pointer dereference in smpboot_unregister_percpu_thread().
Pre-existing issues:
- [High] Unconditional cleanup in vmbus_exit() leads to multiple NULL pointer dereferences if module initialization was bypassed.
--
--- Patch [2]: [PATCH v2 2/2] Drivers: hv: vmbus: Remove vmbus_irq_initialized ---
commit 5d2e18e4c080ed3a0d4cb94c834ff4580a336e21
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Drivers: hv: vmbus: Remove vmbus_irq_initialized
This commit removes the vmbus_irq_initialized flag, relying on the
assumption that if per-CPU thread registration fails, the whole
registration aborts and vmbus_exit() is never called.
> diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
> index 44877664d9d08..95a2c6049854d 100644
> --- a/drivers/hv/vmbus_drv.c
> +++ b/drivers/hv/vmbus_drv.c
[ ... ]
> @@ -3045,10 +3040,9 @@ static void __exit vmbus_exit(void)
> hv_remove_vmbus_handler();
> else
> free_percpu_irq(vmbus_irq, &vmbus_evt);
> - if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) {
> + if (IS_ENABLED(CONFIG_PREEMPT_RT))
> smpboot_unregister_percpu_thread(&vmbus_irq_threads);
> - vmbus_irq_initialized = false;
> - }
[Severity: High]
Will this unconditionally unregister uninitialized threads if the module is
loaded and unloaded without executing vmbus_bus_init()?
The commit message states that vmbus_exit() is never called if registration
fails. However, hv_acpi_init() can return early and bypass vmbus_bus_init()
completely:
hv_acpi_init() {
...
if (hv_root_partition() && !hv_nested)
return 0;
...
}
If the module loads via this early return path and is later unloaded,
vmbus_exit() will execute. Calling smpboot_unregister_percpu_thread() on
the zero-initialized vmbus_irq_threads.list will trigger a NULL pointer
dereference in list_del().
>
> for_each_online_cpu(cpu) {
> struct hv_per_cpu_context *hv_cpu
> = per_cpu_ptr(hv_context.cpu_context, cpu);
>
> tasklet_kill(&hv_cpu->msg_dpc);
[Severity: High]
This is a pre-existing issue, but won't the same early return in
hv_acpi_init() cause multiple crashes during teardown as well?
If vmbus_bus_init() is completely bypassed and the module is subsequently
unloaded, vmbus_exit() performs unconditional cleanup on uninitialized
resources.
For example, tasklet_kill() will attempt to access hv_cpu derived from an
unallocated hv_context.cpu_context. Similarly, unregister_syscore() called
at the start of vmbus_exit() will also dereference a NULL pointer since it
was never registered.
> }
> hv_debug_rm_all_dir();
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721153216.382536-1-bigeasy@linutronix.de?part=2
^ permalink raw reply
* Re: [PATCH v9 2/8] mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support
From: Stanislav Kinsburskii @ 2026-07-21 16:49 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: airlied, akhilesh, akpm, corbet, dakr, decui, haiyangz, jgg, kees,
kys, leon, liam, lizhi.hou, ljs, longli, lyude, maarten.lankhorst,
mamin506, mhocko, mripard, nouveau, ogabbay, oleg, rppt, shuah,
simona, skhan, surenb, tzimmermann, vbabka, wei.liu, dri-devel,
intel-xe, linux-mm, linux-doc, linux-hyperv, linux-kernel,
linux-kselftest, linux-rdma
In-Reply-To: <3ed2043b-88e7-4a86-9efb-fe788191e8e8@kernel.org>
On Tue, Jul 21, 2026 at 05:42:53PM +0200, David Hildenbrand (Arm) wrote:
> On 7/15/26 20:16, Stanislav Kinsburskii wrote:
> > hmm_range_fault() requires the caller to hold the mmap read lock for the
> > duration of the call. This is incompatible with mappings whose fault
> > handler may release the mmap lock, notably userfaultfd-managed regions,
> > where handle_mm_fault() can return VM_FAULT_RETRY or VM_FAULT_COMPLETED
> > after dropping the lock. Drivers that need to populate device page tables
> > for such mappings have no way to do so today.
> >
> > Add hmm_range_fault_unlocked_timeout() for callers that do not need to hold
> > mmap_lock across any work outside the HMM fault itself. The helper takes
> > mmap_read_lock_killable() internally, calls the common HMM fault
> > implementation, and releases the lock before returning if it is still held.
> > The timeout is specified in jiffies; passing 0 retries indefinitely, while
> > a non-zero timeout makes the helper return -EBUSY when the retry budget
> > expires.
>
> The timeout does not / cannot affect how long we might be stuck without the mmap
> lock in e.g., the userfaultfd handler. And also not how long it would take to
> actually grab the mmap lock.
>
> That's expected with the timeout, right?
>
> [...]
>
You are right. Ideally, the timeout should account for the time needed
to acquire the lock.
However, this timeout is not strict, especially with userfaultfd. It is
more of a sentinel to make sure the kernel does not get stuck for too
long and that progress is being made.
The default value used by most callers is 1000 ms, so accounting for the
time needed to acquire the mmap_lock does not really make much sense to
me yet, given the sentinel goal.
Thanks,
Stanislav
>
> Nothing else jumped at me.
>
> --
> Cheers,
>
> David
^ permalink raw reply
* RE: [PATCH v2 4/6] Drivers: hv: Mark shared memory as decrypted for CCA Realms
From: Kameron Carr @ 2026-07-21 18:43 UTC (permalink / raw)
To: 'Michael Kelley', kys, haiyangz, wei.liu, decui, longli
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <SN6PR02MB41571AB004406F331E167203D4EB2@SN6PR02MB4157.namprd02.prod.outlook.com>
On Friday, June 26, 2026 8:04 AM, Michael Kelley wrote:
> From: Kameron Carr <kameroncarr@linux.microsoft.com> Sent: Friday, June
> 26, 2026 4:09 AM
> >
> > On Thursday, June 25, 2026 11:59 AM, Michael Kelley wrote:
> > > From: Kameron Carr <kameroncarr@linux.microsoft.com> Sent: Thursday,
> > > June 25, 2026 10:35 AM
> > > > We need to round up the memory allocated for the input/output pages
> to
> > > > the nearest PAGE_SIZE, since set_memory_decrypted() requires the
size
> to
> > > > be a multiple of PAGE_SIZE. This only has an effect on ARM VMs that
> are
> > > > using PAGE_SIZE larger than 4K.
> > >
> > > I think this change resulted from a Sashiko comment. My understanding
is
> > > that the ARM CCA architecture only supports CCA guests with 4 KiB page
> > > size. Is that still the case, or has that restriction been lifted in a
later
> version
> > > of the architecture? I'm in favor of handling the larger page sizes,
if only
> for
> > > future proofing. But I wondered whether your intent is to always
support
> > > > 4 KiB page sizes even if CCA doesn't support them now. Another way
to
> > > put it: In reviewing code, should I flag issues related to page sizes
> 4 KiB?
> >
> > I think you might be right. I'm looking at RMM spec 2.0 beta 2, and the
RMI
> > can have granule size 4KB, 16KB, 64KB, but the RSI is restricted to
granule
> size
> > 4KB.
> >
> > I'm open to suggestion on best way to move forward.
>
> The best approach probably depends on whether the 4 KiB restriction is
> likely to be lifted in a future version of the CCA architecture, and I
don't have
> any insight into that.
>
> If it is likely to be lifted, then doing the initial implementation to
support
> larger page sizes probably makes sense (which is what you've done here).
> It's less work than going back and adding later. But the commit message
> and/or code comments should indicate that the larger page size support
> is future-proofing work, so that someone doesn't get the wrong idea that
> it should work with larger page sizes now.
>
> The alternate approach is to not do any larger page size support now,
> and to explicitly state that the code is assuming the current restriction
> of 4 KiB page size only.
>
> Whichever approach is chosen should be used consistently so there's
> not a mishmash.
I talked with some people from ARM. My current understanding is that the
4KB granule size for the RSI does not mean the guest is restricted to 4KB
page size. The granule size is just the alignment requirement and larger
page sizes will always be 4KB aligned.
I tested 64KB page size on CCA Realm and it booted successfully.
I will be treating large page sizes as a valid scenario. Please flag any
issues you spot related to page size > 4KB.
Regards,
Kameron
^ permalink raw reply
* RE: [EXTERNAL] [PATCH rdma-next] RDMA/mana_ib: drain QP references after partial table insertion
From: Long Li @ 2026-07-21 19:29 UTC (permalink / raw)
To: Leon Romanovsky, Konstantin Taranov, Jason Gunthorpe,
Shiraz Saleem
Cc: linux-rdma@vger.kernel.org, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <20260721-if-mana-table-store-qp-qids-partiall-v1-1-8fb3d2d2b559@nvidia.com>
>
> From: Leon Romanovsky <leonro@nvidia.com>
>
> mana_table_store_ud_qp() publishes a QP at its send-queue id before
> inserting the receive-queue id, dropping the XArray lock between the two
> xa_insert_irq() calls. A concurrent completion handler can look up the QP
> and take a transient reference. When the second insertion fails, the rollback
> erased only the send-queue entry and returned, leaving both the initial table
> reference and the transient reference outstanding while RDMA core frees the
> QP, causing a use-after-free.
>
> Drain the reference as normal destruction does: drop the initial reference
> and wait for qp->free, releasing the QP only after every concurrent lookup
> returns its reference.
>
> Fixes: 8001e9257eca ("RDMA/mana_ib: extend mana QP table")
> Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
Reviewed-by: Long Li <longli@microsoft.com>
> ---
> Konstantin,
>
> I saw this in one of the Sashiko runs, and I believe the AI is correct. The
> reference can be acquired through the GDMA_EQE_RNIC_QP_FATAL event.
>
> Thanks
> ---
> drivers/infiniband/hw/mana/qp.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/infiniband/hw/mana/qp.c
> b/drivers/infiniband/hw/mana/qp.c index b5ff07e34eb7..c52f3ec14032
> 100644
> --- a/drivers/infiniband/hw/mana/qp.c
> +++ b/drivers/infiniband/hw/mana/qp.c
> @@ -472,6 +472,12 @@ static void mana_table_remove_rc_qp(struct
> mana_ib_dev *mdev, struct mana_ib_qp
> xa_erase_irq(&mdev->qp_table_wq, qp->ibqp.qp_num); }
>
> +static void mana_table_drain_qp_ref(struct mana_ib_qp *qp) {
> + mana_put_qp_ref(qp);
> + wait_for_completion(&qp->free);
> +}
> +
> static int mana_table_store_ud_qp(struct mana_ib_dev *mdev, struct
> mana_ib_qp *qp) {
> u32 qids = qp->ud_qp.queues[MANA_UD_SEND_QUEUE].id |
> MANA_SENDQ_MASK; @@ -490,6 +496,7 @@ static int
> mana_table_store_ud_qp(struct mana_ib_dev *mdev, struct mana_ib_qp *q
>
> remove_sq:
> xa_erase_irq(&mdev->qp_table_wq, qids);
> + mana_table_drain_qp_ref(qp);
> return err;
> }
>
> @@ -537,8 +544,7 @@ static void mana_table_remove_qp(struct
> mana_ib_dev *mdev,
> qp->ibqp.qp_type);
> return;
> }
> - mana_put_qp_ref(qp);
> - wait_for_completion(&qp->free);
> + mana_table_drain_qp_ref(qp);
> }
>
> static int mana_ib_create_rc_qp(struct ib_qp *ibqp, struct ib_pd *ibpd,
>
> ---
> base-commit: 74f49255492a62658f36bf2578d7916f1c6ffad1
> change-id: 20260721-if-mana-table-store-qp-qids-partiall-afa14f023261
>
> Best regards,
> --
> Leon Romanovsky <leonro@nvidia.com>
^ permalink raw reply
* [RFC PATCH 0/2] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses
From: Kameron Carr @ 2026-07-21 19:56 UTC (permalink / raw)
To: decui, haiyangz, kys, longli, wei.liu, mhklinux
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
linux-kernel, netdev
Some confidential computing implementations only accept linear-map
addresses in set_memory_decrypted(); for example, Arm CCA Realms reject
vmalloc()/vmap() addresses. netvsc currently allocates its large
send/receive buffers via vzalloc() and relies on vmbus_establish_gpadl()
to decrypt them, which fails on those implementations.
This fix allocates the buffers as a list of physically-contiguous chunks,
decrypts each chunk on its direct-map address, and vmap() them together.
Here netvsc fully owns the encryption lifecycle and the vmbus layer must
not call set_memory_decrypted() on establish or set_memory_encrypted() on
teardown.
Kameron Carr (2):
Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted()
hv_netvsc: back GPADL buffers with kmalloc + decrypt + vmap
drivers/hv/channel.c | 123 ++++++++++-----
drivers/net/hyperv/hyperv_net.h | 18 ++-
drivers/net/hyperv/netvsc.c | 271 ++++++++++++++++++++++++++++----
drivers/net/hyperv/netvsc_drv.c | 6 +
include/linux/hyperv.h | 11 ++
5 files changed, 354 insertions(+), 75 deletions(-)
base-commit: a4ffc59238be84dd1c26bf1c001543e832674fc6
--
2.45.4
^ permalink raw reply
* [RFC PATCH 1/2] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted()
From: Kameron Carr @ 2026-07-21 19:56 UTC (permalink / raw)
To: decui, haiyangz, kys, longli, wei.liu, mhklinux
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
linux-kernel, netdev
In-Reply-To: <20260721195633.1438361-1-kameroncarr@linux.microsoft.com>
Refactor vmbus_establish_gpadl() to separate the encryption lifecycle
from the rest of the GPADL establishment logic.
Add a new vmbus_establish_gpadl_caller_decrypted() for callers that want
to decrypt their own buffers.
No functional change for existing callers.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
drivers/hv/channel.c | 123 +++++++++++++++++++++++++++--------------
include/linux/hyperv.h | 11 ++++
2 files changed, 93 insertions(+), 41 deletions(-)
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 6821f225248b1..0166367a4df37 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -426,7 +426,13 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)
}
/*
- * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer
+ * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer.
+ *
+ * This function only handles the GPADL handshake with the host. The caller
+ * is responsible for ensuring that @kbuffer is in the encryption state the
+ * host expects (host-visible / "decrypted" for confidential VMs); this
+ * function never calls set_memory_decrypted() or set_memory_encrypted().
+ * It also leaves gpadl->decrypted untouched, so the caller must set it.
*
* @channel: a channel
* @type: the type of the corresponding GPADL, only meaningful for the guest.
@@ -434,7 +440,7 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)
* @size: page-size multiple
* @send_offset: the offset (in bytes) where the send ring buffer starts,
* should be 0 for BUFFER type gpadl
- * @gpadl_handle: some funky thing
+ * @gpadl: out parameter receiving the established GPADL handle/buffer/size
*/
static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
enum hv_gpadl_type type, void *kbuffer,
@@ -454,30 +460,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
(atomic_inc_return(&vmbus_connection.next_gpadl_handle) - 1);
ret = create_gpadl_header(type, kbuffer, size, send_offset, &msginfo);
- if (ret) {
- gpadl->decrypted = false;
+ if (ret)
return ret;
- }
-
- gpadl->decrypted = !((channel->co_external_memory && type == HV_GPADL_BUFFER) ||
- (channel->co_ring_buffer && type == HV_GPADL_RING));
- if (gpadl->decrypted) {
- /*
- * The "decrypted" flag being true assumes that set_memory_decrypted() succeeds.
- * But if it fails, the encryption state of the memory is unknown. In that case,
- * leave "decrypted" as true to ensure the memory is leaked instead of going back
- * on the free list.
- */
- ret = set_memory_decrypted((unsigned long)kbuffer,
- PFN_UP(size));
- if (ret) {
- dev_warn(&channel->device_obj->device,
- "Failed to set host visibility for new GPADL %d.\n",
- ret);
- vmbus_free_channel_msginfo(msginfo);
- return ret;
- }
- }
init_completion(&msginfo->waitevent);
msginfo->waiting_channel = channel;
@@ -553,19 +537,47 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
vmbus_free_channel_msginfo(msginfo);
+ return ret;
+}
- if (ret) {
- /*
- * If set_memory_encrypted() fails, the decrypted flag is
- * left as true so the memory is leaked instead of being
- * put back on the free list.
- */
- if (gpadl->decrypted) {
- if (!set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))
- gpadl->decrypted = false;
+/*
+ * vmbus_establish_gpadl_and_decrypt - Manage the encryption lifecycle of
+ * @kbuffer around a __vmbus_establish_gpadl() call.
+ *
+ * Decrypts @kbuffer (making it host-visible) unless the channel was created
+ * with confidential memory for @type, then establishes the GPADL. On
+ * establish failure the buffer is re-encrypted so the caller can free it.
+ *
+ * Sets gpadl->decrypted; see the comment on struct vmbus_gpadl::decrypted.
+ */
+static int vmbus_establish_gpadl_and_decrypt(struct vmbus_channel *channel,
+ enum hv_gpadl_type type,
+ void *kbuffer, u32 size,
+ u32 send_offset,
+ struct vmbus_gpadl *gpadl)
+{
+ bool decrypt = !((channel->co_external_memory && type == HV_GPADL_BUFFER) ||
+ (channel->co_ring_buffer && type == HV_GPADL_RING));
+ int ret;
+
+ if (decrypt) {
+ ret = set_memory_decrypted((unsigned long)kbuffer, PFN_UP(size));
+ if (ret) {
+ dev_warn(&channel->device_obj->device,
+ "Failed to set host visibility for new GPADL %d.\n",
+ ret);
+ /* Encryption state unknown; signal caller to leak. */
+ gpadl->decrypted = true;
+ return ret;
}
}
+ gpadl->decrypted = decrypt;
+ ret = __vmbus_establish_gpadl(channel, type, kbuffer, size, send_offset,
+ gpadl);
+ if (ret && decrypt &&
+ !set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))
+ gpadl->decrypted = false;
return ret;
}
@@ -580,11 +592,40 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
u32 size, struct vmbus_gpadl *gpadl)
{
- return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer, size,
- 0U, gpadl);
+ return vmbus_establish_gpadl_and_decrypt(channel, HV_GPADL_BUFFER,
+ kbuffer, size, 0U, gpadl);
}
EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
+/*
+ * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer
+ * whose encryption state is managed by the caller.
+ *
+ * @channel: a channel
+ * @kbuffer: a buffer that the caller has already transitioned to host-visible
+ * (decrypted) via set_memory_decrypted() or an equivalent mechanism.
+ * @size: page-size multiple
+ * @gpadl: out parameter receiving the established GPADL
+ *
+ * Unlike vmbus_establish_gpadl(), this function does not call
+ * set_memory_decrypted() on @kbuffer, and the matching vmbus_teardown_gpadl()
+ * call will not call set_memory_encrypted() on it. The caller is responsible
+ * for the full encryption lifecycle of @kbuffer; on return gpadl->decrypted
+ * is set to false to record that vmbus does not own the encryption state.
+ */
+int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+ void *kbuffer, u32 size,
+ struct vmbus_gpadl *gpadl)
+{
+ int ret = __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer,
+ size, 0U, gpadl);
+
+ /* Caller owns @kbuffer's encryption; teardown must not touch it. */
+ gpadl->decrypted = false;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
+
/**
* request_arr_init - Allocates memory for the requestor array. Each slot
* keeps track of the next available slot in the array. Initially, each
@@ -685,11 +726,11 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
/* Establish the gpadl for the ring buffer */
newchannel->ringbuffer_gpadlhandle.gpadl_handle = 0;
- err = __vmbus_establish_gpadl(newchannel, HV_GPADL_RING,
- page_address(newchannel->ringbuffer_page),
- (send_pages + recv_pages) << PAGE_SHIFT,
- newchannel->ringbuffer_send_offset << PAGE_SHIFT,
- &newchannel->ringbuffer_gpadlhandle);
+ err = vmbus_establish_gpadl_and_decrypt(newchannel, HV_GPADL_RING,
+ page_address(newchannel->ringbuffer_page),
+ (send_pages + recv_pages) << PAGE_SHIFT,
+ newchannel->ringbuffer_send_offset << PAGE_SHIFT,
+ &newchannel->ringbuffer_gpadlhandle);
if (err)
goto error_clean_ring;
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 964f1be8150c5..9c6b1b7794b48 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -781,6 +781,12 @@ struct vmbus_gpadl {
u32 gpadl_handle;
u32 size;
void *buffer;
+ /*
+ * Only used if the vmbus layer owns the encryption lifecycle of
+ * @buffer.
+ * Indicates @buffer must be re-encrypted at teardown or that it
+ * must be leaked because re-encryption failed.
+ */
bool decrypted;
};
@@ -1205,6 +1211,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel,
u32 size,
struct vmbus_gpadl *gpadl);
+extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+ void *kbuffer,
+ u32 size,
+ struct vmbus_gpadl *gpadl);
+
extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
struct vmbus_gpadl *gpadl);
--
2.45.4
^ permalink raw reply related
* [RFC PATCH 2/2] hv_netvsc: back GPADL buffers with kmalloc + decrypt + vmap
From: Kameron Carr @ 2026-07-21 19:56 UTC (permalink / raw)
To: decui, haiyangz, kys, longli, wei.liu, mhklinux
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
linux-kernel, netdev
In-Reply-To: <20260721195633.1438361-1-kameroncarr@linux.microsoft.com>
Allocate the buffer as a list of physically-contiguous chunks using
alloc_pages_node() starting at MAX_PAGE_ORDER and falling back to smaller
orders. Each chunk is transitioned to host-visible via
set_memory_decrypted() on its direct-map address, then all chunks are
stitched together via vmap() into a single virtually-contiguous address.
Use vmbus_establish_gpadl_caller_decrypted() so the vmbus layer doesn't
try to decrypt the virtual address. At teardown, vunmap() the range and
re-encrypt and free each chunk individually; any chunk that fails
re-encryption is leaked to prevent accidentally freeing decrypted memory.
Because vunmap() and set_memory_encrypted() must run in process context,
replace the rcu_head/call_rcu() pair used to defer free_netvsc_device()
with rcu_work/queue_rcu_work().
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
drivers/net/hyperv/hyperv_net.h | 18 ++-
drivers/net/hyperv/netvsc.c | 271 ++++++++++++++++++++++++++++----
drivers/net/hyperv/netvsc_drv.c | 6 +
3 files changed, 261 insertions(+), 34 deletions(-)
diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
index 7397c693f984a..b0c4cb0f7a4ce 100644
--- a/drivers/net/hyperv/hyperv_net.h
+++ b/drivers/net/hyperv/hyperv_net.h
@@ -220,6 +220,8 @@ struct net_device_context;
extern u32 netvsc_ring_bytes;
+int netvsc_workqueue_init(void);
+void netvsc_workqueue_destroy(void);
struct netvsc_device *netvsc_device_add(struct hv_device *device,
const struct netvsc_device_info *info);
int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);
@@ -1147,6 +1149,16 @@ struct netvsc_channel {
struct netvsc_stats_rx rx_stats;
};
+/* A physically-contiguous chunk of netvsc buffer.
+ *
+ * The chunk list is preserved so each chunk can be individually freed at
+ * teardown.
+ */
+struct netvsc_buf_chunk {
+ struct page *page;
+ unsigned int order;
+};
+
/* Per netvsc device */
struct netvsc_device {
u32 nvsp_version;
@@ -1158,6 +1170,8 @@ struct netvsc_device {
/* Receive buffer allocated by us but manages by NetVSP */
void *recv_buf;
u32 recv_buf_size; /* allocated bytes */
+ struct netvsc_buf_chunk *recv_buf_chunks;
+ u32 recv_buf_chunk_cnt;
struct vmbus_gpadl recv_buf_gpadl_handle;
u32 recv_section_cnt;
u32 recv_section_size;
@@ -1166,6 +1180,8 @@ struct netvsc_device {
/* Send buffer allocated by us */
void *send_buf;
u32 send_buf_size;
+ struct netvsc_buf_chunk *send_buf_chunks;
+ u32 send_buf_chunk_cnt;
struct vmbus_gpadl send_buf_gpadl_handle;
u32 send_section_cnt;
u32 send_section_size;
@@ -1193,7 +1209,7 @@ struct netvsc_device {
struct netvsc_channel chan_table[VRSS_CHANNEL_MAX];
- struct rcu_head rcu;
+ struct rcu_work rwork;
};
/* NdisInitialize message */
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c
index 59e95341f9b1e..2505d10e22010 100644
--- a/drivers/net/hyperv/netvsc.c
+++ b/drivers/net/hyperv/netvsc.c
@@ -14,6 +14,8 @@
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/set_memory.h>
#include <linux/slab.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
@@ -28,6 +30,8 @@
#include "hyperv_net.h"
#include "netvsc_trace.h"
+static struct workqueue_struct *netvsc_wq;
+
/*
* Switch the data path from the synthetic interface to the VF
* interface.
@@ -125,6 +129,194 @@ static void netvsc_subchan_work(struct work_struct *w)
rtnl_unlock();
}
+/*
+ * netvsc_free_buf_pages - release a netvsc send/receive buffer.
+ *
+ * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a
+ * failed allocation)
+ * @chunks: chunks array from netvsc_alloc_buf_pages(), or NULL
+ * @chunk_cnt: number of entries in @chunks
+ *
+ * When @chunks is NULL the buffer is a plain vzalloc() allocation.
+ *
+ * Otherwise tear down the vmap, and for each chunk re-encrypt and free
+ * the underlying pages. Any chunk that cannot be re-encrypted is leaked.
+ */
+static void netvsc_free_buf_pages(void *addr,
+ struct netvsc_buf_chunk *chunks,
+ u32 chunk_cnt)
+{
+ u32 i;
+
+ if (!chunks) {
+ vfree(addr);
+ return;
+ }
+
+ vunmap(addr);
+
+ for (i = 0; i < chunk_cnt; i++) {
+ unsigned long vaddr =
+ (unsigned long)page_address(chunks[i].page);
+ unsigned int order = chunks[i].order;
+
+ if (set_memory_encrypted(vaddr, 1U << order))
+ continue;
+ __free_pages(chunks[i].page, order);
+ }
+
+ kvfree(chunks);
+}
+
+/*
+ * netvsc_alloc_buf_pages - allocate a virtually-contiguous, host-visible
+ * buffer for the netvsc send/receive area.
+ *
+ * @node: NUMA node hint (NUMA_NO_NODE for any node)
+ * @size: requested buffer size in bytes (rounded up to PAGE_SIZE)
+ * @chunks_out: on success, set to the array of underlying chunks
+ * @chunk_cnt_out: on success, set to the number of chunks
+ *
+ * Allocates the buffer as a series of physically-contiguous chunks,
+ * starting at MAX_PAGE_ORDER and falling back to smaller orders on
+ * allocation failure. Each chunk is transitioned to host-visible via
+ * set_memory_decrypted() on its direct-map address, then all chunks are
+ * combined into a virtually-contiguous range via vmap().
+ *
+ * Return: the vmap()ed virtual address, or NULL on failure.
+ */
+static void *netvsc_alloc_buf_pages(int node, u32 size,
+ struct netvsc_buf_chunk **chunks_out,
+ u32 *chunk_cnt_out)
+{
+ u32 nr_pages = PFN_UP(size);
+ struct netvsc_buf_chunk *chunks = NULL;
+ struct page **pages = NULL;
+ unsigned int order;
+ u32 chunk_cnt = 0;
+ u32 page_idx = 0;
+ u32 remaining = nr_pages;
+ void *addr;
+ u32 i;
+ int ret;
+
+ *chunks_out = NULL;
+ *chunk_cnt_out = 0;
+
+ if (!nr_pages)
+ return NULL;
+
+ /* Worst case: every chunk is a single page. */
+ chunks = kvmalloc_array(nr_pages, sizeof(*chunks),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!chunks)
+ goto err;
+
+ pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ goto err;
+
+ /*
+ * @order monotonically decreases across iterations
+ *
+ * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing, but try
+ * harder at order 0 since that is the final fallback.
+ */
+ order = min_t(unsigned int, MAX_PAGE_ORDER, ilog2(nr_pages));
+ while (remaining) {
+ struct page *page;
+ gfp_t gfp;
+
+ order = min_t(unsigned int, order, ilog2(remaining));
+
+ for (;;) {
+ gfp = GFP_KERNEL | __GFP_ZERO;
+ if (order)
+ gfp |= __GFP_NORETRY | __GFP_NOWARN;
+ page = alloc_pages_node(node, gfp, order);
+ if (page)
+ break;
+ if (!order)
+ goto err;
+ order--;
+ }
+
+ ret = set_memory_decrypted((unsigned long)page_address(page),
+ 1U << order);
+ if (ret) {
+ /*
+ * set_memory_decrypted() failed; the page state is
+ * unknown so it must be leaked rather than freed.
+ */
+ goto err;
+ }
+
+ chunks[chunk_cnt].page = page;
+ chunks[chunk_cnt].order = order;
+ chunk_cnt++;
+
+ for (i = 0; i < (1U << order); i++)
+ pages[page_idx++] = page + i;
+
+ remaining -= 1U << order;
+ }
+
+ addr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL));
+ if (!addr)
+ goto err;
+
+ kvfree(pages);
+ *chunks_out = chunks;
+ *chunk_cnt_out = chunk_cnt;
+ return addr;
+
+err:
+ kvfree(pages);
+ netvsc_free_buf_pages(NULL, chunks, chunk_cnt);
+ return NULL;
+}
+
+static void __free_netvsc_device(struct netvsc_device *nvdev)
+{
+ int i;
+
+ kfree(nvdev->extension);
+
+ netvsc_free_buf_pages(nvdev->recv_buf, nvdev->recv_buf_chunks,
+ nvdev->recv_buf_chunk_cnt);
+ netvsc_free_buf_pages(nvdev->send_buf, nvdev->send_buf_chunks,
+ nvdev->send_buf_chunk_cnt);
+ bitmap_free(nvdev->send_section_map);
+
+ for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
+ xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
+ kfree(nvdev->chan_table[i].recv_buf);
+ vfree(nvdev->chan_table[i].mrc.slots);
+ }
+
+ kfree(nvdev);
+}
+
+static void free_netvsc_device(struct work_struct *w)
+{
+ struct rcu_work *rwork = to_rcu_work(w);
+
+ __free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));
+}
+
+int netvsc_workqueue_init(void)
+{
+ netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0);
+
+ return netvsc_wq ? 0 : -ENOMEM;
+}
+
+void netvsc_workqueue_destroy(void)
+{
+ rcu_barrier();
+ destroy_workqueue(netvsc_wq);
+}
+
static struct netvsc_device *alloc_net_device(void)
{
struct netvsc_device *net_device;
@@ -143,36 +335,18 @@ static struct netvsc_device *alloc_net_device(void)
init_completion(&net_device->channel_init_wait);
init_waitqueue_head(&net_device->subchan_open);
INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
+ INIT_RCU_WORK(&net_device->rwork, free_netvsc_device);
return net_device;
}
-static void free_netvsc_device(struct rcu_head *head)
-{
- struct netvsc_device *nvdev
- = container_of(head, struct netvsc_device, rcu);
- int i;
-
- kfree(nvdev->extension);
-
- if (!nvdev->recv_buf_gpadl_handle.decrypted)
- vfree(nvdev->recv_buf);
- if (!nvdev->send_buf_gpadl_handle.decrypted)
- vfree(nvdev->send_buf);
- bitmap_free(nvdev->send_section_map);
-
- for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
- xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
- kfree(nvdev->chan_table[i].recv_buf);
- vfree(nvdev->chan_table[i].mrc.slots);
- }
-
- kfree(nvdev);
-}
-
static void free_netvsc_device_rcu(struct netvsc_device *nvdev)
{
- call_rcu(&nvdev->rcu, free_netvsc_device);
+ /*
+ * Defer the actual free to process context: vunmap() and
+ * set_memory_encrypted() cannot run from RCU softirq context.
+ */
+ queue_rcu_work(netvsc_wq, &nvdev->rwork);
}
static void netvsc_revoke_recv_buf(struct hv_device *device,
@@ -351,7 +525,20 @@ static int netvsc_init_buf(struct hv_device *device,
buf_size = min_t(unsigned int, buf_size,
NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);
- net_device->recv_buf = vzalloc(buf_size);
+ if (device->channel->co_external_memory) {
+ /* Confidential VM Bus leaves buffer encrypted */
+ net_device->recv_buf_chunks = NULL;
+ net_device->recv_buf_chunk_cnt = 0;
+ net_device->recv_buf = vzalloc(buf_size);
+ } else {
+ /* Otherwise, allocate decrypted buffer */
+ net_device->recv_buf =
+ netvsc_alloc_buf_pages(cpu_to_node(device->channel->target_cpu),
+ buf_size,
+ &net_device->recv_buf_chunks,
+ &net_device->recv_buf_chunk_cnt);
+ }
+
if (!net_device->recv_buf) {
netdev_err(ndev,
"unable to allocate receive buffer of size %u\n",
@@ -367,9 +554,10 @@ static int netvsc_init_buf(struct hv_device *device,
* channel. Note: This call uses the vmbus connection rather
* than the channel to establish the gpadl handle.
*/
- ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf,
- buf_size,
- &net_device->recv_buf_gpadl_handle);
+ ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+ net_device->recv_buf,
+ buf_size,
+ &net_device->recv_buf_gpadl_handle);
if (ret != 0) {
netdev_err(ndev,
"unable to establish receive buffer's gpadl\n");
@@ -457,7 +645,19 @@ static int netvsc_init_buf(struct hv_device *device,
buf_size = device_info->send_sections * device_info->send_section_size;
buf_size = round_up(buf_size, PAGE_SIZE);
- net_device->send_buf = vzalloc(buf_size);
+ if (device->channel->co_external_memory) {
+ /* Confidential VM Bus leaves buffer encrypted */
+ net_device->send_buf_chunks = NULL;
+ net_device->send_buf_chunk_cnt = 0;
+ net_device->send_buf = vzalloc(buf_size);
+ } else {
+ /* Otherwise, allocate decrypted buffer */
+ net_device->send_buf =
+ netvsc_alloc_buf_pages(cpu_to_node(device->channel->target_cpu),
+ buf_size,
+ &net_device->send_buf_chunks,
+ &net_device->send_buf_chunk_cnt);
+ }
if (!net_device->send_buf) {
netdev_err(ndev, "unable to allocate send buffer of size %u\n",
buf_size);
@@ -470,9 +670,10 @@ static int netvsc_init_buf(struct hv_device *device,
* channel. Note: This call uses the vmbus connection rather
* than the channel to establish the gpadl handle.
*/
- ret = vmbus_establish_gpadl(device->channel, net_device->send_buf,
- buf_size,
- &net_device->send_buf_gpadl_handle);
+ ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+ net_device->send_buf,
+ buf_size,
+ &net_device->send_buf_gpadl_handle);
if (ret != 0) {
netdev_err(ndev,
"unable to establish send buffer's gpadl\n");
@@ -1863,7 +2064,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,
netif_napi_del(&net_device->chan_table[0].napi);
cleanup2:
- free_netvsc_device(&net_device->rcu);
+ /*
+ * net_device was never published, so we don't need to wait for an
+ * RCU grace period -- call the free routine synchronously.
+ */
+ __free_netvsc_device(net_device);
return ERR_PTR(ret);
}
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index ee5ab5ceb2be2..1d43c73fd73f1 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)
{
unregister_netdevice_notifier(&netvsc_netdev_notifier);
vmbus_driver_unregister(&netvsc_drv);
+ netvsc_workqueue_destroy();
}
static int __init netvsc_drv_init(void)
{
int ret;
+ ret = netvsc_workqueue_init();
+ if (ret)
+ return ret;
+
if (ring_size < RING_SIZE_MIN) {
ring_size = RING_SIZE_MIN;
pr_info("Increased ring_size to %u (min allowed)\n",
@@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)
err_vmbus_reg:
unregister_netdevice_notifier(&netvsc_netdev_notifier);
+ netvsc_workqueue_destroy();
return ret;
}
--
2.45.4
^ permalink raw reply related
* [PATCH v3 0/6] arm64: hyperv: Add Realm support for Hyper-V
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
Realms (CoCo VMs on ARM) require host calls to be routed through the RMM
(Realm Management Monitor) via the RSI (Realm Service Interface). This
series implements most of the necessary changes to support Realms on
Hyper-V.
One required change is not included in this series. The two buffers
allocated via vzalloc() in netvsc_init_buf() cannot be decrypted in
vmbus_establish_gpadl(). Currently only linearly mapped memory can be
decrypted.
This patch series was tested by booting a Realm on Cobalt 200 running
Windows. I tested 4KB and 64KB page size. I replaced vzalloc() in
netvsc_init_buf() with alloc_pages_node() -> set_memory_decrypted() ->
vmap() in my testing as a workaround for the issue mentioned above [1].
Changes since v1 [2]:
Patch 1: Add explicit padding to the RSI host call structure
Patch 3: Change from a per-cpu pointer lazily allocated to an array
of host call structs indexed by cpu id
Patch 4: Align input_page + output_page allocation to PAGE_SIZE since
that is the smallest unit of memory that can be decrypted
~Remove KASAN tags before passing address to
set_memory_decrypted() since __is_lm_address() does pointer
arithmetic.~
Patch 5: Add a helper function to reduce repetition
Check for NULL before indexing into host call array
Changes since v2 [3]:
Patch 3: Remove hv_hostcall_free() and inline the logic for simplicity
Reorder edits to keep #include line at the end of the file
Patch 4: Remove kasan_reset_tag()
[1] https://lore.kernel.org/all/20260721195633.1438361-1-kameroncarr@linux.microsoft.com/
[2] https://lore.kernel.org/all/20260609181030.2378391-1-kameroncarr@linux.microsoft.com/
[3] https://lore.kernel.org/all/20260625173500.1995481-1-kameroncarr@linux.microsoft.com/
Kameron Carr (6):
arm64: rsi: Add RSI host call structure and helper function
firmware: smccc: Detect hypervisor via RSI host call in CCA Realms
arm64: hyperv: Add per-CPU RSI host call infrastructure for CCA Realms
Drivers: hv: Mark shared memory as decrypted for CCA Realms
arm64: hyperv: Route hypercalls through RSI host call in CCA Realms
arm64: hyperv: Implement hv_is_isolation_supported() for CCA Realms
arch/arm64/hyperv/hv_core.c | 155 +++++++++++++++++++++++-------
arch/arm64/hyperv/mshyperv.c | 38 +++++++-
arch/arm64/include/asm/mshyperv.h | 4 +
arch/arm64/include/asm/rsi_cmds.h | 22 +++++
arch/arm64/include/asm/rsi_smc.h | 7 ++
drivers/firmware/smccc/smccc.c | 41 +++++++-
drivers/hv/hv_common.c | 17 +++-
include/asm-generic/mshyperv.h | 1 +
8 files changed, 244 insertions(+), 41 deletions(-)
base-commit: a4ffc59238be84dd1c26bf1c001543e832674fc6
--
2.45.4
^ permalink raw reply
* [PATCH v3 1/6] arm64: rsi: Add RSI host call structure and helper function
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
Add struct rsi_host_call to rsi_smc.h, which represents the host call
data structure used by the Realm Management Monitor (RMM) for the
RSI_HOST_CALL interface. The structure contains a 16-bit immediate field
and 31 general-purpose register values, aligned to 256 bytes as required
by the CCA RMM specification.
Add rsi_host_call() static inline wrapper in rsi_cmds.h that invokes
SMC_RSI_HOST_CALL with the physical address of the host call structure.
This will be used by Hyper-V guest code to route hypercalls through the
RSI interface when running inside an Arm CCA Realm.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
arch/arm64/include/asm/rsi_cmds.h | 22 ++++++++++++++++++++++
arch/arm64/include/asm/rsi_smc.h | 7 +++++++
2 files changed, 29 insertions(+)
diff --git a/arch/arm64/include/asm/rsi_cmds.h b/arch/arm64/include/asm/rsi_cmds.h
index 2c8763876dfb7..9daf8008e5da2 100644
--- a/arch/arm64/include/asm/rsi_cmds.h
+++ b/arch/arm64/include/asm/rsi_cmds.h
@@ -88,6 +88,28 @@ static inline long rsi_set_addr_range_state(phys_addr_t start,
return res.a0;
}
+/**
+ * rsi_host_call - Make a Host call.
+ * @host_call_struct: IPA of host call structure
+ *
+ * This call will fail if the IPA of the host call structure:
+ * * is not aligned to 256 bytes,
+ * * is not protected / encrypted,
+ * * is RIPAS_EMPTY
+ *
+ * Returns:
+ * On success, returns RSI_SUCCESS.
+ * Otherwise, returns an error code.
+ */
+static inline unsigned long rsi_host_call(phys_addr_t host_call_struct)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(SMC_RSI_HOST_CALL, host_call_struct, 0, 0, 0, 0, 0, 0,
+ &res);
+ return res.a0;
+}
+
/**
* rsi_attestation_token_init - Initialise the operation to retrieve an
* attestation token.
diff --git a/arch/arm64/include/asm/rsi_smc.h b/arch/arm64/include/asm/rsi_smc.h
index e19253f96c940..9cc57b5be0c02 100644
--- a/arch/arm64/include/asm/rsi_smc.h
+++ b/arch/arm64/include/asm/rsi_smc.h
@@ -142,6 +142,13 @@ struct realm_config {
*/
} __aligned(0x1000);
+struct rsi_host_call {
+ u16 immediate;
+ u8 _padding[6];
+ u64 gprs[31];
+} __aligned(256);
+static_assert(sizeof(struct rsi_host_call) == 256);
+
#endif /* __ASSEMBLER__ */
/*
--
2.45.4
^ permalink raw reply related
* [PATCH v3 2/6] firmware: smccc: Detect hypervisor via RSI host call in CCA Realms
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
Modify arm_smccc_hypervisor_has_uuid() to check is_realm_world() and
use rsi_host_call() to query the hypervisor vendor UUID when inside a
Realm. The realm path is factored into a helper,
arm_smccc_realm_get_hypervisor_uuid(), that owns a file-static
rsi_host_call buffer (uuid_hc) serialized by a spinlock.
The RSI-specific includes, file-static state and helper are guarded
with CONFIG_ARM64 because <asm/rsi.h> does not exist on 32-bit ARM.
For non-Realm environments, the existing arm_smccc_1_1_invoke() path
is unchanged.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
drivers/firmware/smccc/smccc.c | 41 +++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c
index bdee057db2fd3..a876b7aa2dc99 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -12,6 +12,12 @@
#include <linux/platform_device.h>
#include <asm/archrandom.h>
+#ifdef CONFIG_ARM64
+#include <linux/cleanup.h>
+#include <linux/spinlock.h>
+#include <asm/rsi.h>
+#endif
+
static u32 smccc_version = ARM_SMCCC_VERSION_1_0;
static enum arm_smccc_conduit smccc_conduit = SMCCC_CONDUIT_NONE;
@@ -67,12 +73,45 @@ s32 arm_smccc_get_soc_id_revision(void)
}
EXPORT_SYMBOL_GPL(arm_smccc_get_soc_id_revision);
+#ifdef CONFIG_ARM64
+static struct rsi_host_call uuid_hc;
+static DEFINE_SPINLOCK(uuid_hc_lock);
+
+/*
+ * Helper function to get the hypervisor UUID via an RsiHostCall.
+ */
+static void arm_smccc_realm_get_hypervisor_uuid(struct arm_smccc_res *res)
+{
+ guard(spinlock_irqsave)(&uuid_hc_lock);
+
+ memset(&uuid_hc, 0, sizeof(uuid_hc));
+ uuid_hc.gprs[0] = ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID;
+
+ if (rsi_host_call(__pa_symbol(&uuid_hc)) != RSI_SUCCESS) {
+ res->a0 = SMCCC_RET_NOT_SUPPORTED;
+ return;
+ }
+
+ res->a0 = uuid_hc.gprs[0];
+ res->a1 = uuid_hc.gprs[1];
+ res->a2 = uuid_hc.gprs[2];
+ res->a3 = uuid_hc.gprs[3];
+}
+#endif
+
bool arm_smccc_hypervisor_has_uuid(const uuid_t *hyp_uuid)
{
struct arm_smccc_res res = {};
uuid_t uuid;
- arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, &res);
+#ifdef CONFIG_ARM64
+ if (is_realm_world())
+ arm_smccc_realm_get_hypervisor_uuid(&res);
+ else
+#endif
+ arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID,
+ &res);
+
if (res.a0 == SMCCC_RET_NOT_SUPPORTED)
return false;
--
2.45.4
^ permalink raw reply related
* [PATCH v3 3/6] arm64: hyperv: Add per-CPU RSI host call infrastructure for CCA Realms
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
Arm CCA Realms cannot issue Hyper-V hypercalls via HVC; the guest must
route them through the RSI_HOST_CALL interface, which takes the IPA of a
per-CPU rsi_host_call structure as its argument.
Add hv_hostcall_array as a per-CPU struct array and allocate it during
hyperv_init(). The allocation is gated on is_realm_world() so non-Realm
arm64 Hyper-V guests pay no memory cost.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
arch/arm64/hyperv/mshyperv.c | 28 ++++++++++++++++++++++++++--
arch/arm64/include/asm/mshyperv.h | 4 ++++
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/hyperv/mshyperv.c b/arch/arm64/hyperv/mshyperv.c
index 4fdc26ade1d74..d9010e64ddd7c 100644
--- a/arch/arm64/hyperv/mshyperv.c
+++ b/arch/arm64/hyperv/mshyperv.c
@@ -15,10 +15,15 @@
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/cpuhotplug.h>
+#include <linux/slab.h>
#include <asm/mshyperv.h>
+#include <asm/rsi.h>
static bool hyperv_initialized;
+struct rsi_host_call *hv_hostcall_array;
+EXPORT_SYMBOL_GPL(hv_hostcall_array);
+
int hv_get_hypervisor_version(union hv_hypervisor_version_info *info)
{
hv_get_vpreg_128(HV_REGISTER_HYPERVISOR_VERSION,
@@ -85,6 +90,20 @@ static int __init hyperv_init(void)
if (!hyperv_detect_via_acpi() && !hyperv_detect_via_smccc())
return 0;
+ /*
+ * The RSI host-call buffers are only ever used when
+ * is_realm_world() is true. Skip the allocation on non-Realm
+ * guests. A single contiguous array of nr_cpu_ids entries is
+ * allocated; each CPU indexes into it by its processor ID.
+ */
+ if (is_realm_world()) {
+ hv_hostcall_array = kcalloc(nr_cpu_ids,
+ sizeof(struct rsi_host_call),
+ GFP_KERNEL);
+ if (!hv_hostcall_array)
+ return -ENOMEM;
+ }
+
/* Setup the guest ID */
guest_id = hv_generate_guest_id(LINUX_VERSION_CODE);
hv_set_vpreg(HV_REGISTER_GUEST_OS_ID, guest_id);
@@ -106,13 +125,13 @@ static int __init hyperv_init(void)
ret = hv_common_init();
if (ret)
- return ret;
+ goto free_hostcall_mem;
ret = cpuhp_setup_state(CPUHP_AP_HYPERV_ONLINE, "arm64/hyperv_init:online",
hv_common_cpu_init, hv_common_cpu_die);
if (ret < 0) {
hv_common_free();
- return ret;
+ goto free_hostcall_mem;
}
if (ms_hyperv.priv_high & HV_ACCESS_PARTITION_ID)
@@ -125,6 +144,11 @@ static int __init hyperv_init(void)
hyperv_initialized = true;
return 0;
+
+free_hostcall_mem:
+ kfree(hv_hostcall_array);
+ hv_hostcall_array = NULL;
+ return ret;
}
early_initcall(hyperv_init);
diff --git a/arch/arm64/include/asm/mshyperv.h b/arch/arm64/include/asm/mshyperv.h
index b721d3134ab66..aed0aa526c8b4 100644
--- a/arch/arm64/include/asm/mshyperv.h
+++ b/arch/arm64/include/asm/mshyperv.h
@@ -61,6 +61,10 @@ static inline u64 hv_get_non_nested_msr(unsigned int reg)
ARM_SMCCC_OWNER_VENDOR_HYP, \
HV_SMCCC_FUNC_NUMBER)
+/* Per-CPU-indexed RSI host call structures for CCA Realms */
+struct rsi_host_call;
+extern struct rsi_host_call *hv_hostcall_array;
+
#include <asm-generic/mshyperv.h>
#endif
--
2.45.4
^ permalink raw reply related
* [PATCH v3 4/6] Drivers: hv: Mark shared memory as decrypted for CCA Realms
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
In hv_common_cpu_init(), the per-CPU hypercall input/output pages need
to be marked as decrypted (shared) for confidential VM isolation types.
This is already done for SNP and TDX isolation; extend the same handling
to Arm CCA Realm guests so that the host hypervisor can access the
shared hypercall buffers.
is_realm_world() is only declared in arch/arm64/include/asm/rsi.h, so
using it directly in the arch-neutral drivers/hv/hv_common.c would
break the x86 build. Introduce a Hyper-V-specific helper following the
established hv_isolation_type_snp() / hv_isolation_type_tdx() pattern.
On architectures other than arm64 the weak default keeps the existing
behaviour.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
arch/arm64/hyperv/mshyperv.c | 5 +++++
drivers/hv/hv_common.c | 17 +++++++++++++----
include/asm-generic/mshyperv.h | 1 +
3 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/hyperv/mshyperv.c b/arch/arm64/hyperv/mshyperv.c
index d9010e64ddd7c..8519f694f2897 100644
--- a/arch/arm64/hyperv/mshyperv.c
+++ b/arch/arm64/hyperv/mshyperv.c
@@ -158,3 +158,8 @@ bool hv_is_hyperv_initialized(void)
return hyperv_initialized;
}
EXPORT_SYMBOL_GPL(hv_is_hyperv_initialized);
+
+bool hv_isolation_type_cca(void)
+{
+ return is_realm_world();
+}
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 6b67ac6167891..0f9412fcb9571 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -476,6 +476,7 @@ int hv_common_cpu_init(unsigned int cpu)
u64 msr_vp_index;
gfp_t flags;
const int pgcount = hv_output_page_exists() ? 2 : 1;
+ const size_t alloc_size = ALIGN((size_t)pgcount * HV_HYP_PAGE_SIZE, PAGE_SIZE);
void *mem;
int ret = 0;
@@ -489,7 +490,7 @@ int hv_common_cpu_init(unsigned int cpu)
* online and then taken offline
*/
if (!*inputarg) {
- mem = kmalloc_array(pgcount, HV_HYP_PAGE_SIZE, flags);
+ mem = kmalloc(alloc_size, flags);
if (!mem)
return -ENOMEM;
@@ -499,14 +500,16 @@ int hv_common_cpu_init(unsigned int cpu)
}
if (!ms_hyperv.paravisor_present &&
- (hv_isolation_type_snp() || hv_isolation_type_tdx())) {
- ret = set_memory_decrypted((unsigned long)mem, pgcount);
+ (hv_isolation_type_snp() || hv_isolation_type_tdx() ||
+ hv_isolation_type_cca())) {
+ ret = set_memory_decrypted((unsigned long)mem,
+ alloc_size >> PAGE_SHIFT);
if (ret) {
/* It may be unsafe to free 'mem' */
return ret;
}
- memset(mem, 0x00, pgcount * HV_HYP_PAGE_SIZE);
+ memset(mem, 0x00, alloc_size);
}
/*
@@ -666,6 +669,12 @@ bool __weak hv_isolation_type_tdx(void)
}
EXPORT_SYMBOL_GPL(hv_isolation_type_tdx);
+bool __weak hv_isolation_type_cca(void)
+{
+ return false;
+}
+EXPORT_SYMBOL_GPL(hv_isolation_type_cca);
+
void __weak hv_setup_vmbus_handler(void (*handler)(void))
{
}
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index bf601d67cecb9..1fa79abce743c 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -79,6 +79,7 @@ u64 hv_do_fast_hypercall16(u16 control, u64 input1, u64 input2);
bool hv_isolation_type_snp(void);
bool hv_isolation_type_tdx(void);
+bool hv_isolation_type_cca(void);
/*
* On architectures where Hyper-V doesn't support AEOI (e.g., ARM64),
--
2.45.4
^ permalink raw reply related
* [PATCH v3 5/6] arm64: hyperv: Route hypercalls through RSI host call in CCA Realms
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
Modify the five hypercall wrapper functions to check is_realm_world()
and use the per-CPU rsi_host_call structure when inside a Realm.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
arch/arm64/hyperv/hv_core.c | 155 ++++++++++++++++++++++++++++--------
1 file changed, 121 insertions(+), 34 deletions(-)
diff --git a/arch/arm64/hyperv/hv_core.c b/arch/arm64/hyperv/hv_core.c
index e33a9e3c366a1..77cba08fca132 100644
--- a/arch/arm64/hyperv/hv_core.c
+++ b/arch/arm64/hyperv/hv_core.c
@@ -13,9 +13,41 @@
#include <linux/mm.h>
#include <linux/arm-smccc.h>
#include <linux/module.h>
+#include <linux/smp.h>
#include <asm-generic/bug.h>
#include <hyperv/hvhdk.h>
#include <asm/mshyperv.h>
+#include <asm/rsi.h>
+
+/*
+ * hv_do_rsi_hypercall - Helper function to invoke a hypercall from a
+ * Realm world using the RSI interface.
+ */
+static u64 hv_do_rsi_hypercall(u64 control, u64 input1, u64 input2)
+{
+ struct rsi_host_call *hostcall;
+ unsigned long flags;
+ u64 ret;
+
+ if (!hv_hostcall_array)
+ return HV_STATUS_INVALID_HYPERCALL_INPUT;
+
+ local_irq_save(flags);
+ hostcall = &hv_hostcall_array[smp_processor_id()];
+ memset(hostcall, 0, sizeof(*hostcall));
+ hostcall->gprs[0] = HV_FUNC_ID;
+ hostcall->gprs[1] = control;
+ hostcall->gprs[2] = input1;
+ hostcall->gprs[3] = input2;
+
+ if (rsi_host_call(virt_to_phys(hostcall)) == RSI_SUCCESS)
+ ret = hostcall->gprs[0];
+ else
+ ret = HV_STATUS_INVALID_HYPERCALL_INPUT;
+
+ local_irq_restore(flags);
+ return ret;
+}
/*
* hv_do_hypercall- Invoke the specified hypercall
@@ -29,8 +61,11 @@ u64 hv_do_hypercall(u64 control, void *input, void *output)
input_address = input ? virt_to_phys(input) : 0;
output_address = output ? virt_to_phys(output) : 0;
- arm_smccc_1_1_hvc(HV_FUNC_ID, control,
- input_address, output_address, &res);
+ if (is_realm_world())
+ return hv_do_rsi_hypercall(control, input_address, output_address);
+
+ arm_smccc_1_1_hvc(HV_FUNC_ID, control, input_address,
+ output_address, &res);
return res.a0;
}
EXPORT_SYMBOL_GPL(hv_do_hypercall);
@@ -48,6 +83,9 @@ u64 hv_do_fast_hypercall8(u16 code, u64 input)
control = (u64)code | HV_HYPERCALL_FAST_BIT;
+ if (is_realm_world())
+ return hv_do_rsi_hypercall(control, input, 0);
+
arm_smccc_1_1_hvc(HV_FUNC_ID, control, input, &res);
return res.a0;
}
@@ -65,6 +103,9 @@ u64 hv_do_fast_hypercall16(u16 code, u64 input1, u64 input2)
control = (u64)code | HV_HYPERCALL_FAST_BIT;
+ if (is_realm_world())
+ return hv_do_rsi_hypercall(control, input1, input2);
+
arm_smccc_1_1_hvc(HV_FUNC_ID, control, input1, input2, &res);
return res.a0;
}
@@ -76,24 +117,44 @@ EXPORT_SYMBOL_GPL(hv_do_fast_hypercall16);
void hv_set_vpreg(u32 msr, u64 value)
{
struct arm_smccc_res res;
+ struct rsi_host_call *hostcall;
+ unsigned long flags;
+ u64 status;
- arm_smccc_1_1_hvc(HV_FUNC_ID,
- HVCALL_SET_VP_REGISTERS | HV_HYPERCALL_FAST_BIT |
- HV_HYPERCALL_REP_COMP_1,
- HV_PARTITION_ID_SELF,
- HV_VP_INDEX_SELF,
- msr,
- 0,
- value,
- 0,
- &res);
+ if (is_realm_world()) {
+ local_irq_save(flags);
+ hostcall = &hv_hostcall_array[smp_processor_id()];
+ memset(hostcall, 0, sizeof(*hostcall));
+ hostcall->gprs[0] = HV_FUNC_ID;
+ hostcall->gprs[1] = HVCALL_SET_VP_REGISTERS |
+ HV_HYPERCALL_FAST_BIT |
+ HV_HYPERCALL_REP_COMP_1;
+ hostcall->gprs[2] = HV_PARTITION_ID_SELF;
+ hostcall->gprs[3] = HV_VP_INDEX_SELF;
+ hostcall->gprs[4] = msr;
+ hostcall->gprs[6] = value;
+
+ if (rsi_host_call(virt_to_phys(hostcall)) == RSI_SUCCESS)
+ status = hostcall->gprs[0];
+ else
+ status = HV_STATUS_INVALID_HYPERCALL_INPUT;
+ local_irq_restore(flags);
+ } else {
+ arm_smccc_1_1_hvc(HV_FUNC_ID,
+ HVCALL_SET_VP_REGISTERS |
+ HV_HYPERCALL_FAST_BIT |
+ HV_HYPERCALL_REP_COMP_1,
+ HV_PARTITION_ID_SELF, HV_VP_INDEX_SELF, msr,
+ 0, value, 0, &res);
+ status = res.a0;
+ }
/*
- * Something is fundamentally broken in the hypervisor if
- * setting a VP register fails. There's really no way to
- * continue as a guest VM, so panic.
+ * Something is fundamentally broken in the hypervisor (or, in a
+ * Realm, the RMM denied the host call) if setting a VP register
+ * fails. There's really no way to continue as a guest VM, so panic.
*/
- BUG_ON(!hv_result_success(res.a0));
+ BUG_ON(!hv_result_success(status));
}
EXPORT_SYMBOL_GPL(hv_set_vpreg);
@@ -108,29 +169,55 @@ void hv_get_vpreg_128(u32 msr, struct hv_get_vp_registers_output *result)
{
struct arm_smccc_1_2_regs args;
struct arm_smccc_1_2_regs res;
+ struct rsi_host_call *hostcall;
+ unsigned long flags;
+ u64 status;
- args.a0 = HV_FUNC_ID;
- args.a1 = HVCALL_GET_VP_REGISTERS | HV_HYPERCALL_FAST_BIT |
- HV_HYPERCALL_REP_COMP_1;
- args.a2 = HV_PARTITION_ID_SELF;
- args.a3 = HV_VP_INDEX_SELF;
- args.a4 = msr;
+ if (is_realm_world()) {
+ local_irq_save(flags);
+ hostcall = &hv_hostcall_array[smp_processor_id()];
+ memset(hostcall, 0, sizeof(*hostcall));
- /*
- * Use the SMCCC 1.2 interface because the results are in registers
- * beyond X0-X3.
- */
- arm_smccc_1_2_hvc(&args, &res);
+ hostcall->gprs[0] = HV_FUNC_ID;
+ hostcall->gprs[1] = HVCALL_GET_VP_REGISTERS |
+ HV_HYPERCALL_FAST_BIT |
+ HV_HYPERCALL_REP_COMP_1;
+ hostcall->gprs[2] = HV_PARTITION_ID_SELF;
+ hostcall->gprs[3] = HV_VP_INDEX_SELF;
+ hostcall->gprs[4] = msr;
+
+ if (rsi_host_call(virt_to_phys(hostcall)) == RSI_SUCCESS) {
+ status = hostcall->gprs[0];
+ result->as64.low = hostcall->gprs[6];
+ result->as64.high = hostcall->gprs[7];
+ } else {
+ status = HV_STATUS_INVALID_HYPERCALL_INPUT;
+ }
+ local_irq_restore(flags);
+ } else {
+ args.a0 = HV_FUNC_ID;
+ args.a1 = HVCALL_GET_VP_REGISTERS | HV_HYPERCALL_FAST_BIT |
+ HV_HYPERCALL_REP_COMP_1;
+ args.a2 = HV_PARTITION_ID_SELF;
+ args.a3 = HV_VP_INDEX_SELF;
+ args.a4 = msr;
+
+ /*
+ * Use the SMCCC 1.2 interface because the results are in
+ * registers beyond X0-X3.
+ */
+ arm_smccc_1_2_hvc(&args, &res);
+ status = res.a0;
+ result->as64.low = res.a6;
+ result->as64.high = res.a7;
+ }
/*
- * Something is fundamentally broken in the hypervisor if
- * getting a VP register fails. There's really no way to
- * continue as a guest VM, so panic.
+ * Something is fundamentally broken in the hypervisor (or, in a
+ * Realm, the RMM denied the host call) if getting a VP register
+ * fails. There's really no way to continue as a guest VM, so panic.
*/
- BUG_ON(!hv_result_success(res.a0));
-
- result->as64.low = res.a6;
- result->as64.high = res.a7;
+ BUG_ON(!hv_result_success(status));
}
EXPORT_SYMBOL_GPL(hv_get_vpreg_128);
--
2.45.4
^ permalink raw reply related
* [PATCH v3 6/6] arm64: hyperv: Implement hv_is_isolation_supported() for CCA Realms
From: Kameron Carr @ 2026-07-21 20:11 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui, longli, mhklinux
Cc: catalin.marinas, will, mark.rutland, lpieralisi, sudeep.holla,
arnd, thuth, linux-hyperv, linux-arm-kernel, linux-kernel,
linux-arch
In-Reply-To: <20260721201148.1441143-1-kameroncarr@linux.microsoft.com>
Provide an arm64 implementation of hv_is_isolation_supported() that
overrides the __weak default in drivers/hv/hv_common.c.
The implementation deliberately does not depend on
hv_is_hyperv_initialized() because hv_common_init() consults
hv_is_isolation_supported() before hyperv_initialized is set.
Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
arch/arm64/hyperv/mshyperv.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/hyperv/mshyperv.c b/arch/arm64/hyperv/mshyperv.c
index 8519f694f2897..e28612255a970 100644
--- a/arch/arm64/hyperv/mshyperv.c
+++ b/arch/arm64/hyperv/mshyperv.c
@@ -163,3 +163,8 @@ bool hv_isolation_type_cca(void)
{
return is_realm_world();
}
+
+bool hv_is_isolation_supported(void)
+{
+ return is_realm_world();
+}
--
2.45.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox