blob: 017077b56f65b87af5a85ae00c1f7bdb9aa2bed4 [file] [log] [blame]
/*
* Copyright 2024 The Hafnium Authors.
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/BSD-3-Clause.
*/
#include "hf/hf_ipi.h"
#include "hf/cpu.h"
#include "hf/ffa/notifications.h"
#include "hf/plat/interrupts.h"
/** Interrupt priority for Inter-Processor Interrupt. */
#define IPI_PRIORITY 0x0U
/**
* Initialize the IPI SGI.
*/
void hf_ipi_init_interrupt(void)
{
/* Configure as a Secure SGI. */
struct interrupt_descriptor ipi_desc = {
.interrupt_id = HF_IPI_INTID,
.type = INT_DESC_TYPE_SGI,
.sec_state = INT_DESC_SEC_STATE_S,
.priority = IPI_PRIORITY,
.valid = true,
.enabled = true,
};
plat_interrupts_configure_interrupt(ipi_desc);
}
/**
* Returns the next target_vcpu with a pending IPI and removes it from
* the list for the current CPU to show it has been retrieved.
* The running vCPU is prioritised to prevent it being put into
* the PREEMPTED state before it has handled it's IPI, this could happen in
* the case a vCPU in the WAITING state also has a pending IPI.
* In the case of a spurious IPI physical interrupt, where the target
* vCPUs have already handled their pending IPIs return NULL.
*/
struct vcpu *hf_ipi_get_pending_target_vcpu(struct vcpu *current)
{
struct list_entry *list;
struct vcpu *target_vcpu;
/* Lock the CPU the list belongs to. */
sl_lock(&current->cpu->lock);
/*
* Check if the current vcpu has a pending interrupt,
* if so prioritise this.
*/
if (!list_empty(&current->ipi_list_node)) {
list = &current->ipi_list_node;
} else {
/*
* If the current cpu doesn't have a pending IPI check other
* vcpus on the current CPU.
*/
list = &current->cpu->pending_ipis;
if (list_empty(list)) {
target_vcpu = NULL;
goto out;
}
/*
* The list is circular, the root element does not belong to a
* vCPU but is used to track if the list is empty and if not
* point to the first vCPU with a pending IPI.
*/
list = list->next;
}
/*
* The next vCPU with a pending IPI has been retrieved to be handled
* so remove it from the list.
*/
list_remove(list);
target_vcpu = CONTAINER_OF(list, struct vcpu, ipi_list_node);
out:
sl_unlock(&current->cpu->lock);
return target_vcpu;
}
/**
* Send and record the IPI for the target vCPU.
*/
void hf_ipi_send_interrupt(struct vm *vm, ffa_vcpu_index_t target_vcpu_index)
{
struct vcpu *target_vcpu = vm_get_vcpu(vm, target_vcpu_index);
struct cpu *target_cpu = target_vcpu->cpu;
sl_lock(&target_cpu->lock);
/*
* Since vCPUs are pinned to a physical cpu they can only belong
* to one list. Therefore check if the vCPU is in a list. If not
* add it and send the IPI SGI.
*/
if (list_empty(&target_vcpu->ipi_list_node)) {
list_prepend(&target_cpu->pending_ipis,
&target_vcpu->ipi_list_node);
plat_interrupts_send_sgi(HF_IPI_INTID, target_cpu, true);
}
sl_unlock(&target_cpu->lock);
}
/**
* Enum to track the next SRI action that should be performed for an IPI to
* a vCPU in the WAITING state.
*/
enum ipi_sri_action {
/* First entry into the handling function. */
IPI_SRI_ACTION_INIT,
/* For a waiting state trigger and SRI not delayed. */
IPI_SRI_ACTION_NOT_DELAYED,
/*
* For a waiting state set delayed SRI to prioritize a running vCPU,
* preventing the running vCPU becoming preempted.
*/
IPI_SRI_ACTION_DELAYED,
/* SRI already set. */
IPI_SRI_ACTION_NONE,
};
/**
* IPI IRQ handling for each vCPU state, the ipi_sri_action is used to know
* which SRI action to use when there is a vCPU in the WAITING state.
* Elements of the list of vCPUs on the CPU with pending IPIs will be traversed
* and depending of the state of each, the handling specific to IPIs will be
* taken:
* - RUNNING: Set the ipi_sri_action to IPI_SRI_ACTION_DELAYED, so if an SRI
* is required for a different vCPU, the running (current) vCPU will still
* handle the IPI. Return false so that the normal secure interrupt handling
* continues.
* - WAITING: If the ipi_sri_action is IPI_SRI_ACTION_NONE, an SRI has either
* already been triggered or set to delayed so we don't need to do anything.
* Otherwise:
* - If the running vCPU has a pending IPI, the ipi_sri_action will be
* IPI_SRI_ACTION_DELAYED so set the SRI to delayed, this means the SRI
* will be triggered on the next world switch to NWd and the running
* vCPU will not be stopped before it has handled it's IPI. Set the
* ipi_sri_action to IPI_SRI_ACTION_NONE, as we only need to set the
* SRI once.
* - If the running vCPU does not have a pending IPI, the ipi_sri_action
* will either be IPI_SRI_ACTION_INIT, if we are in the head of the list,
* or IPI_SRI_ACTION_NOT_DELAYED, in these cases we want to trigger the
* SRI immediately, so the NWd can schedule the target vCPU to handle
* the IPI. Set the ipi_sri_action to IPI_SRI_ACTION_NONE as we only need
* to trigger the SRI once.
* - PREEMPTED/BLOCKED:
* - If it's the head of the list (indicated by
* IPI_SRI_ACTION_INIT), return false and allow normal secure interrupt
* handling to handle the interrupt as usual.
* - Otherwise queue the interrupt for the vCPU.
* Returns True if the IPI SGI has been fully handled.
* False if further secure interrupt handling is required, this will
* only be the case for the target vCPU head of the pending ipi list, if it
* is in the RUNNING, PREEMPTED or BLOCKED state.
*/
static bool hf_ipi_handle_list_element(struct vcpu_locked target_vcpu_locked,
enum ipi_sri_action *ipi_sri_action)
{
bool ret = true;
struct vcpu *target_vcpu = target_vcpu_locked.vcpu;
assert(ipi_sri_action != NULL);
vcpu_virt_interrupt_inject(target_vcpu_locked, HF_IPI_INTID);
switch (target_vcpu->state) {
case VCPU_STATE_RUNNING:
if (*ipi_sri_action != IPI_SRI_ACTION_INIT) {
panic("%s: If present the RUNNING vCPU should be the "
"first to be handled.\n",
__func__);
}
/*
* Any SRI should be delayed to prioritize the running vCPU,
* preventing it from entering the PREEMPTED state by the SRI
* before the IPI is handled.
*/
*ipi_sri_action = IPI_SRI_ACTION_DELAYED;
ret = false;
break;
case VCPU_STATE_WAITING:
if (*ipi_sri_action == IPI_SRI_ACTION_INIT ||
*ipi_sri_action == IPI_SRI_ACTION_NOT_DELAYED) {
/*
* The current target vCPU is either the first element
* in the pending list or there is not running vCPU in
* the list, so we are ok to trigger the SRI
* immediately.
*/
ffa_notifications_sri_trigger_not_delayed(
target_vcpu->cpu);
} else if (*ipi_sri_action == IPI_SRI_ACTION_DELAYED) {
/*
* Otherwise a running vCPU has a pending IPI so set a
* delayed SRI, so as not to preempt the running vCPU
* before it is able to handle it's IPI.
*/
ffa_notifications_sri_set_delayed(target_vcpu->cpu);
}
*ipi_sri_action = IPI_SRI_ACTION_NONE;
break;
case VCPU_STATE_BLOCKED:
case VCPU_STATE_PREEMPTED:
if (*ipi_sri_action == IPI_SRI_ACTION_INIT) {
/*
* The current target vCPU is the top of the list of
* pending IPIs so allow it to be handled by the default
* secure interrupt handling. Change the state to
* IPI_SRI_ACTION_NOT_DELAYED since there now can't be
* any running vCPUs with pending IPIs (it would have
* been the head of the list) so we are safe to trigger
* the SRI for any waiting vCPUs immediately.
*/
*ipi_sri_action = IPI_SRI_ACTION_NOT_DELAYED;
ret = false;
}
break;
default:
dlog_error(
"%s: unexpected state: %u handling an IPI for [%x %u]",
__func__, target_vcpu->state, target_vcpu->vm->id,
vcpu_index(target_vcpu));
}
return ret;
}
/**
* IPI IRQ specific handling for the secure interrupt.
*/
bool hf_ipi_handle(struct vcpu_locked target_vcpu_locked)
{
enum ipi_sri_action ipi_sri_action = IPI_SRI_ACTION_INIT;
bool ret = true;
struct vcpu *current = target_vcpu_locked.vcpu;
ret = hf_ipi_handle_list_element(target_vcpu_locked, &ipi_sri_action);
/*
* Clear the pending ipi list, handling the ipi for the remaining
* target vCPUs.
*/
for (struct vcpu *target_vcpu = hf_ipi_get_pending_target_vcpu(current);
target_vcpu != NULL;
target_vcpu = hf_ipi_get_pending_target_vcpu(target_vcpu)) {
if (target_vcpu != current) {
target_vcpu_locked = vcpu_lock(target_vcpu);
}
hf_ipi_handle_list_element(target_vcpu_locked, &ipi_sri_action);
if (target_vcpu != current) {
vcpu_unlock(&target_vcpu_locked);
}
}
return ret;
}