seL4/src/arch/x86/smp/ipi.c
Indan Zupancic c8ae3010bc x86,SMP: Fix VMCheckBoundNotification IPI handling
When a notification is bound to an IRQ that arrives on a different
core than where the VCPU is running, x86 uses a special IPI to
notify the other core about this. (For performance reasons you
would try to avoid this setup. Nevertheless, it should work.)

When an IpiRemoteCall_VMCheckBoundNotification arrives during a
VM exit, the notification reply set by VMCheckBoundNotification()
gets overwritten by the handleVmexit() reply, leading to lost
notification events. This happens when VMCheckBoundNotification()
gets called by the IPI handling code within NODE_LOCK_SYS.

As there is no way to postpone the VM exit handling and the IPI
code doesn't know whether it races with a VM exit, doing nothing
if the current task is the target is the safest choice:

Either the IPI itself caused a VM exit, or there was a VM exit
happening already.

To handle the first case, explicitly call VMCheckBoundNotification()
in handleVmexit(). This must be done while holding the kernel lock,
as the other core can release the lock any moment after it received
our IPI reply.

In the latter case, pending notifications will be detected and
returned to user space by the next seL4_VMEnter() call.

Fix tested by Alessandro Legnani.

Resolves issue #1148.

Signed-off-by: Indan Zupancic <indan@nul.nu>
2026-05-21 11:50:29 +01:00

119 lines
3.7 KiB
C

/*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <config.h>
#include <mode/smp/ipi.h>
#include <smp/ipi.h>
#include <smp/lock.h>
#ifdef ENABLE_SMP_SUPPORT
void handleRemoteCall(IpiRemoteCall_t call, word_t arg0, word_t arg1, word_t arg2, bool_t irqPath)
{
/* we gets spurious irq_remote_call_ipi calls, e.g. when handling IPI
* in lock while hardware IPI is pending. Guard against spurious IPIs! */
if (clh_is_ipi_pending(getCurrentCPUIndex())) {
switch (call) {
case IpiRemoteCall_Stall:
ipiStallCoreCallback(irqPath);
break;
case IpiRemoteCall_InvalidatePageStructureCacheASID:
invalidateLocalPageStructureCacheASID(arg0, arg1);
break;
case IpiRemoteCall_InvalidateTranslationSingle:
invalidateLocalTranslationSingle(arg0);
break;
case IpiRemoteCall_InvalidateTranslationSingleASID:
invalidateLocalTranslationSingleASID(arg0, arg1);
break;
case IpiRemoteCall_InvalidateTranslationAll:
invalidateLocalTranslationAll();
break;
case IpiRemoteCall_switchFpuOwner:
switchLocalFpuOwner((tcb_t *)arg0);
break;
#ifdef CONFIG_VTX
case IpiRemoteCall_ClearCurrentVCPU:
clearCurrentVCPU();
break;
case IpiRemoteCall_VMCheckBoundNotification:
tcb_t *tcb = (tcb_t *)arg0;
/* For a running VM notifications are already checked by handleVmexit */
if (tcb != NODE_STATE(ksCurThread)) {
VMCheckBoundNotification();
}
break;
#endif
default:
Mode_handleRemoteCall((IpiModeRemoteCall_t)call, arg0, arg1, arg2);
break;
}
big_kernel_lock.node[getCurrentCPUIndex()].ipi = 0;
ipi_wait();
}
}
/* make sure all cpu IDs for number of core fit in bitwise word */
compile_assert(invalid_number_of_supported_nodes, CONFIG_MAX_NUM_NODES <= wordBits);
#ifdef CONFIG_USE_LOGICAL_IDS
static void x86_ipi_send_mask(interrupt_t ipi, word_t mask, bool_t isBlocking)
{
word_t nr_target_clusters = 0;
word_t target_clusters[CONFIG_MAX_NUM_NODES];
do {
int core = wordBits - 1 - clzl(mask);
target_clusters[nr_target_clusters] = 0;
/* get mask of all cores in bitmask which are in same cluster as 'core' */
word_t sub_mask = mask & cpu_mapping.other_indexes_in_cluster[core];
target_clusters[nr_target_clusters] |= cpu_mapping.index_to_logical_id[core];
if (isBlocking) {
big_kernel_lock.node[core].ipi = 1;
}
/* check if there is any other core in this cluster */
while (sub_mask) {
int index = wordBits - 1 - clzl(sub_mask);
target_clusters[nr_target_clusters] |= cpu_mapping.index_to_logical_id[index];
if (isBlocking) {
big_kernel_lock.node[index].ipi = 1;
}
sub_mask &= ~BIT(index);
}
mask &= ~(cpu_mapping.other_indexes_in_cluster[core] | BIT(core));
nr_target_clusters++;
} while (mask != 0);
/* broadcast IPIs to clusters... */
IPI_ICR_BARRIER;
for (int i = 0; i < nr_target_clusters; i++) {
apic_send_ipi_cluster(ipi, target_clusters[i]);
}
}
#endif /* CONFIG_USE_LOGICAL_IDS */
void ipi_send_mask(irq_t ipi, word_t mask, bool_t isBlocking)
{
interrupt_t interrupt_ipi = ipi + IRQ_INT_OFFSET;
#ifdef CONFIG_USE_LOGICAL_IDS
x86_ipi_send_mask(interrupt_ipi, mask, isBlocking);
#else
generic_ipi_send_mask(interrupt_ipi, mask, isBlocking);
#endif /* CONFIG_USE_LOGICAL_IDS */
}
#endif /* ENABLE_SMP_SUPPORT */